package services;

import org.sql2o.Connection;

import java.util.Date;
import java.util.List;
import java.util.Map;

import org.sql2o.Sql2oException;
import services.UserService.User;

import services.SessionService.Session;
import services.SessionService.SessionNotFound;
import services.ExerciseService.Exercise;
import services.ExerciseService.ExerciseNotFound;

import static services.ExerciseService.getExerciseById;
import static services.SessionService.getSessionById;

public class SessionTryService {
  public static class SessionTryNotFound extends Exception {
    public SessionTryNotFound() {
      super("Session try not found");
    }
  }
  public static class SessionTryNotCreated extends Exception {
    public SessionTryNotCreated() {
      super("Session try was not created because of an internal error");
    }
  }
  public static class SessionTriesExceeded extends Exception {
    public SessionTriesExceeded() {
      super("Too many exercise answer attempts");
    }
  }
  public static class SessionTrySyntaxError extends Exception {
    public SessionTrySyntaxError() { super("Syntax error"); }
  }

  public static class SQLError extends Exception {
    public SQLError(String message) {
      super(message);
    }
  }
  public static class SessionTry {
    public int id;
    public int exercise;
    public int session;
    public String answer;
    public boolean correct;
    public Date startedAt;
    public Date finishedAt;

    public SessionTry(
      int exercise,
      int session,
      String answer,
      Date startedAt
    ) {
      this.exercise = exercise;
      this.session = session;
      this.answer = answer;
      this.startedAt = startedAt;
    }
  }

  public static SessionTry getSessionTryById(int id) throws SessionTryNotFound {
    String sql = "SELECT * FROM session_try where id = :id";

    try(Connection con = DatabaseService.getConnection()) {
      List<SessionTry> sessions = con
        .createQuery(sql)
        .addParameter("id", id)
        .addColumnMapping("started_at", "startedAt")
        .addColumnMapping("finished_at", "finishedAt")
        .executeAndFetch(SessionTry.class);

      if(sessions.size() == 0) {
        throw new SessionTryNotFound();
      }

      return sessions.get(0);
    }
  }
  public static List<SessionTry> getSessionTriesBySessionAndExercise(int id, int exercise) {
    String sql = "SELECT * FROM session_try WHERE session = :id AND exercise = :exercise";

    try(Connection con = DatabaseService.getConnection()) {
      List<SessionTry> sessionTries = con
        .createQuery(sql)
        .addParameter("id", id)
        .addParameter("exercise", exercise)
        .addColumnMapping("started_at", "startedAt")
        .addColumnMapping("finished_at", "finishedAt")
        .executeAndFetch(SessionTry.class);

      return sessionTries;
    }
  }

  public static SessionTry answerExercise(SessionTry sessionTry, User user)
          throws SessionTryNotCreated, SessionNotFound, SessionTriesExceeded, ExerciseNotFound,
                  SessionTrySyntaxError, SQLError {

    sessionTry.finishedAt = new Date();

    if(!isValidSyntax(sessionTry.answer)) {
      throw new SessionTrySyntaxError();
    }


    Session session = getSessionById(sessionTry.session);
    Exercise exercise = getExerciseById(sessionTry.exercise);

    List<SessionTry> previousTries =
      getSessionTriesBySessionAndExercise(sessionTry.session, sessionTry.exercise);

    if(previousTries.size() == session.maxTries) {
      throw new SessionTriesExceeded();
    }

    try(Connection con = DatabaseService.getConnection()) {
      List<Map<String,Object>> correctAnswer = con
        .createQuery(exercise.exampleAnswers.get(0))
        .executeAndFetchTable()
        .asList();

      List<Map<String,Object>> userAnswerResult = con
        .createQuery(sessionTry.answer)
        .executeAndFetchTable()
        .asList();

      sessionTry.correct = correctAnswer.equals(userAnswerResult);
    } catch (Sql2oException err) {
      sessionTry.correct = false;
      createSessionTry(sessionTry);
      throw new SQLError(err.getMessage());
    }
    sessionTry.correct = true;
    return createSessionTry(sessionTry);
  }

  public static SessionTry createSessionTry(SessionTry sessionTry) throws SessionTryNotCreated {
    String sql = "INSERT INTO session_try (exercise, session, answer, started_at, finished_at, correct) " +
            "values (:exercise, :session, :answer, :startedAt, :finishedAt, :correct)";

    try(Connection con = DatabaseService.getConnection()) {
      int id = con
        .createQuery(sql, true)
        .bind(sessionTry)
        .executeUpdate()
        .getKey(int.class);
      return SessionTryService.getSessionTryById(id);
    } catch (SessionTryNotFound err) {
      throw new SessionTryNotCreated();
    }
  }
  private static boolean isValidSyntax(String answer) throws SessionTrySyntaxError {
    if(answer.charAt((answer.length() -1)) != ';') {
      throw new SessionTrySyntaxError();
    }
    else {
      final char PAR1 = '(';
      final char PAR2 = ')';
      int parNum1 = 0;
      int parNum2 = 0;
      for (int i = 0; i < answer.length() - 1; ++i) {
        if (answer.charAt(i) == PAR1) {
          parNum1++;
        }
        if (answer.charAt(i) == PAR2) {
          parNum2++;
        }
      }
      return parNum1 == parNum2;
    }
  }
}


@@ -3123,219 +3123,95 @@
 ();%0A
-%0A
     
-if(!isValidSyntax(sessionTry.answer)) %7B%0A      throw new SessionTrySyntaxError();%0A    %7D%0A%0A%0A    Session session = getSessionById(sessionTry.session);%0A    Exercise exercise = getExerciseById(sessionTry.exercise
+sessionTry.correct = false;%0A    Session session = getSessionById(sessionTry.session
 );%0A%0A
@@ -3253,16 +3253,22 @@
 =%0A      
+   
+   
 getSessi
@@ -3334,24 +3334,69 @@
 exercise);%0A%0A
+    // Has max amount of tries been reached?%0A
     if(previ
@@ -3477,24 +3477,414 @@
 d();%0A    %7D%0A%0A
+    // Is answer syntactically correct?%0A    if(sessionTry.answer.charAt((sessionTry.answer.length() -1)) != ';') %7B%0A      createSessionTry(sessionTry);%0A      throw new SessionTrySyntaxError();%0A    %7D%0A%0A    if(!isValidSyntax(sessionTry.answer)) %7B%0A      createSessionTry(sessionTry);%0A      throw new SessionTrySyntaxError();%0A    %7D%0A%0A    Exercise exercise = getExerciseById(sessionTry.exercise);%0A%0A
     try(Conn

check session try limit before doing any request validation