/*
 * Copyright DataStax, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.datastax.oss.driver.examples.mapper;

import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.PagingIterable;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.datastax.oss.driver.examples.mapper.killrvideo.KillrVideoMapper;
import com.datastax.oss.driver.examples.mapper.killrvideo.user.User;
import com.datastax.oss.driver.examples.mapper.killrvideo.user.UserDao;
import com.datastax.oss.driver.examples.mapper.killrvideo.video.LatestVideo;
import com.datastax.oss.driver.examples.mapper.killrvideo.video.UserVideo;
import com.datastax.oss.driver.examples.mapper.killrvideo.video.Video;
import com.datastax.oss.driver.examples.mapper.killrvideo.video.VideoByTag;
import com.datastax.oss.driver.examples.mapper.killrvideo.video.VideoDao;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Uses the driver's object mapper to interact with a schema.
 *
 * <p>We use the data model of the <a href="https://killrvideo.github.io/">KillrVideo</a> sample
 * application. The mapped entities and DAOs are in the {@link
 * com.datastax.oss.driver.examples.mapper.killrvideo} package. We only cover a subset of the data
 * model (ratings, stats, recommendations and comments are not covered).
 *
 * <p>Preconditions:
 *
 * <ul>
 *   <li>An Apache Cassandra(R) cluster is running and accessible through the contacts points
 *       identified by basic.contact-points (see application.conf).
 * </ul>
 *
 * <p>Side effects:
 *
 * <ul>
 *   <li>creates a new keyspace "killrvideo" in the session. If a keyspace with this name already
 *       exists, it will be reused;
 *   <li>creates the tables of the KillrVideo data model, if they don't already exist;
 *   <li>inserts a new user, or reuse the existing one if the email address is already taken;
 *   <li>inserts a video for that user.
 * </ul>
 *
 * @see <a href="https://docs.datastax.com/en/developer/java-driver/latest/manual/mapper/">Java
 *     Driver Mapper manual</a>
 */
@SuppressWarnings("CatchAndPrintStackTrace")
public class KillrVideoMapperExample {

  private static final CqlIdentifier KEYSPACE_ID = CqlIdentifier.fromCql("killrvideo");

  public static void main(String[] args) {

    try (CqlSession session = CqlSession.builder().build()) {

      maybeCreateSchema(session);

      KillrVideoMapper mapper =
          KillrVideoMapper.builder(session).withDefaultKeyspace(KEYSPACE_ID).build();

      // Create a new user
      UserDao userDao = mapper.userDao();

      User user = new User(Uuids.random(), "test", "user", "testuser@example.com", Instant.now());

      if (userDao.create(user, "fakePasswordForTests".toCharArray())) {
        System.out.println("Created " + user);
      } else {
        user = userDao.getByEmail("testuser@example.com");
        System.out.println("Reusing existing " + user);
      }

      // Creating another user with the same email should fail
      assert !userDao.create(
          new User(Uuids.random(), "test2", "user", "testuser@example.com", Instant.now()),
          "fakePasswordForTests2".toCharArray());

      // Simulate login attempts
      tryLogin(userDao, "testuser@example.com", "fakePasswordForTests");
      tryLogin(userDao, "testuser@example.com", "fakePasswordForTests2");

      // Insert a video
      VideoDao videoDao = mapper.videoDao();

      Video video = new Video();
      video.setUserid(user.getUserid());
      video.setName(
          "Getting Started with DataStax Apache Cassandra as a Service on DataStax Astra");
      video.setLocation("https://www.youtube.com/watch?v=68xzKpcZURA");
      Set<String> tags = new HashSet<>();
      tags.add("apachecassandra");
      tags.add("nosql");
      tags.add("hybridcloud");
      video.setTags(tags);

      videoDao.create(video);
      System.out.printf("Created video [%s] %s%n", video.getVideoid(), video.getName());

      // Check that associated denormalized tables have also been updated:
      PagingIterable<UserVideo> userVideos = videoDao.getByUser(user.getUserid());
      System.out.printf("Videos for %s %s:%n", user.getFirstname(), user.getLastname());
      for (UserVideo userVideo : userVideos) {
        System.out.printf("  [%s] %s%n", userVideo.getVideoid(), userVideo.getName());
      }

      PagingIterable<LatestVideo> latestVideos = videoDao.getLatest(todaysTimestamp());
      System.out.println("Latest videos:");
      for (LatestVideo latestVideo : latestVideos) {
        System.out.printf("  [%s] %s%n", latestVideo.getVideoid(), latestVideo.getName());
      }

      PagingIterable<VideoByTag> videosByTag = videoDao.getByTag("apachecassandra");
      System.out.println("Videos tagged with apachecassandra:");
      for (VideoByTag videoByTag : videosByTag) {
        System.out.printf("  [%s] %s%n", videoByTag.getVideoid(), videoByTag.getName());
      }

      // Update the existing video:
      Video template = new Video();
      template.setVideoid(video.getVideoid());
      template.setName(
          "Getting Started with DataStax Apache Cassandra® as a Service on DataStax Astra");
      videoDao.update(template);
      // Reload the whole entity and check the fields
      video = videoDao.get(video.getVideoid());
      System.out.printf("Updated name for video %s: %s%n", video.getVideoid(), video.getName());
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private static void tryLogin(UserDao userDao, String email, String password) {
    Optional<User> maybeUser = userDao.login(email, password.toCharArray());
    System.out.printf(
        "Logging in with %s/%s: %s%n",
        email, password, maybeUser.isPresent() ? "Success" : "Failure");
  }

  private static void maybeCreateSchema(CqlSession session) throws Exception {
    session.execute(
        SimpleStatement.newInstance(
                "CREATE KEYSPACE IF NOT EXISTS killrvideo WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}")
            .setExecutionProfileName("slow"));
    session.execute("USE killrvideo");
    for (String statement : getStatements("killrvideo_schema.cql")) {
      session.execute(SimpleStatement.newInstance(statement).setExecutionProfileName("slow"));
    }
  }

  private static List<String> getStatements(String fileName) throws Exception {
    Path path = Paths.get(ClassLoader.getSystemResource(fileName).toURI());
    String contents = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
    return Arrays.stream(contents.split(";"))
        .map(String::trim)
        .filter(s -> !s.isEmpty())
        .collect(Collectors.toList());
  }

  /**
   * KillrVideo uses a textual timestamp to partition recent video. Build the timestamp for today to
   * fetch our latest insertions.
   */
  private static String todaysTimestamp() {
    return DateTimeFormatter.ofPattern("yyyyMMdd").withZone(ZoneOffset.UTC).format(Instant.now());
  }
}
