package me.blackphreak.CommandHandling.Handlers;

import me.blackphreak.CommandHandling.AbstractEditCommandHandler;
import me.blackphreak.Database;
import me.blackphreak.Debug.Debug;
import me.blackphreak.Grade.Grade;
import me.blackphreak.Lib;

import java.sql.SQLException;
import java.util.HashMap;

import static me.blackphreak.Lib.promptQuestion;
import static me.blackphreak.Lib.tryParseUInt;

public class EditGradesCommand extends AbstractEditCommandHandler {
	private HashMap<Integer, Grade> gradeMap;
	private HashMap<Integer, String> subjMap = new HashMap<>();
	
	public EditGradesCommand(String desc) {
		super(desc);
		
		try {
			// query db to get all subjects
			var result = Database.getInstance()
					.query("SELECT SubjectID, Name FROM Subject;");
			while (result.next()) {
				subjMap.put(
						result.getInt("SubjectID"),
					result.getString("Name")
				);
			}
		}
		catch (SQLException e) {
			Debug.err("Failed to fetch all subjects. [Database Error]");
			e.printStackTrace();
			Database.getInstance().close();
			System.exit(1);  // exit with status code: 1
		}
	}
	
	@Override
	public boolean handle() {
		boolean nextFlag;
		
		do
		{
			nextFlag = selectStudentByID(false);
			
			// if lastCmd is set.
			if (lastCmd != null) {
				// in this case, lastCmd should be either "exit" nor "end".
				return lastCmd.equalsIgnoreCase("exit");
			}
		} while (!nextFlag);
		
		return selectActionMenu();
	}
	
	private boolean selectActionMenu() {
		do {
			int i = 1;
			System.out.println(String.format(
					"  %2s. %s",
					(i++) + "",
					"Edit Subjects"
			));
			
			System.out.println(String.format(
					"  %2s. %s",
					(i++) + "",
					"Edit Grades"
			));
			
			// exit option
			System.out.println(String.format(
					"  %2s. %15s",
					"99",
					"Back to main menu"
			));
			
			var inp = promptQuestion("Please select an action: ");
			var selected = tryParseUInt(inp, "Invalid action number");
			if (selected == -1)
				continue;
			
			if (selected == 99)
				return true;
			
			// get grade map from db
			try {
				gradeMap = Lib.dbToGradeMap(selectedStudentID.get(0));
			} catch (SQLException e) {
				Debug.err("Failed to parse to Subjects Map. [Database Error]");
				e.printStackTrace();
				return false;
			}
			
			switch (selected) {
				case 1:
					return editSubjectMenu();
				case 2:
					return editGradeMenu();
				default:
					System.out.println("Invalid action!");
			}
		} while (true);
	}
	
	private boolean editSubjectMenu() {
		boolean endFlag;
		do {
			printCurrentSubjects();
			
			endFlag = selectAndEditSubject();
		} while (!endFlag);
		
		return true;
	}
	
	private void printCurrentSubjects() {
		System.out.println("| Registered | SubjectID | Subject Name");
		System.out.println("|------------|-----------|----------------->>");
		
		subjMap.forEach((id, name) ->
				System.out.println(String.format(
						"| %5s%5s | %-9s | %s",
						gradeMap.containsKey(id) ? "✓" : "✕",
						" ", // padding
						id + "",
						name
				))
		);
		
		System.out.println("|------------|------------|----------------->>");
	}
	
	private boolean selectAndEditSubject() {
		do {
			var inp = promptQuestion("Please provide SubjectID before edit: ");
			var sid = tryParseUInt(inp, "Invalid subjectID. [Parse Failed]");
			
			if (sid == -1)
				continue;
			
			if (!subjMap.containsKey(sid)) {
				System.out.println("Invalid subjectID. [Subject does not exist]");
				continue;
			}
			
			if (gradeMap.containsKey(sid)) {
				System.out.println("This subject is already registered for this student.");
				
				// update subject. (del-reg)
				var confirmation = promptQuestion("Are you sure to deregister subject["+sid+"] for student["+selectedStudentID.get(0)+"]? [confirm / No]");
				if (confirmation.equals("confirm"))
				{
					Database.getInstance().update(String.format(
							"DELETE FROM Grade WHERE StudentID = \"%s\" AND SubjectID = \"%d\"",
							selectedStudentID.get(0),
							sid
					));
					
					System.out.println("Deregistered "+sid+" for student["+selectedStudentID.get(0)+"].");
				}
				else
					System.out.println("No changes have been made.");
			}
			else
			{
				System.out.println("Registering new subject for student["+selectedStudentID.get(0)+"]");
				
				var obj = new Grade();
				
				Database.getInstance().update(String.format(
						"INSERT INTO Grade (StudentID, SubjectID, Grade, Sem, InProgress) VALUES (\"%s\", \"%d\", \"%s\", \"%d\", \"%d\");",
						selectedStudentID.get(0),
						sid,
						obj.getGrade(),
						obj.getSem(),
						obj.isInProgress() ? 1 : 0
				));
				
				System.out.println("Registered new subject["+sid+"] for student["+selectedStudentID.get(0)+"]");
			}
			
			var contin = promptQuestion("Would you like to continue edit Subject for the same student? [Y/n]");
			if (contin.equalsIgnoreCase("n"))
				return true;
		} while (true);
	}
	
	// end of subject editing
	
	private boolean editGradeMenu() {
		boolean endFlag;
		do {
			printCurrentGrades();
			
			endFlag = selectAndEditGrade();
		} while (!endFlag);
		
		return true;
	}
	
	private void printCurrentGrades() {
		System.out.println("| SubjectID | Grade | Semester | InProgress | SubjectName");
		System.out.println("|-----------|-------|----------|------------|--------------->>");
		gradeMap.forEach((id, obj) -> System.out.println(String.format(
				"| %9s | %5s | %8s | %10s | %s",
				id + "",
				obj.getGrade(),
				obj.getSem(),
				obj.isInProgress(),
				subjMap.get(id)
		)));
		System.out.println("|-----------|-------|----------|------------|--------------->>");
	}
	
	private boolean selectAndEditGrade() {
		var subjID = -1;
		do {
			var inp = promptQuestion("Please provide SubjectID that you want to edit: ");
			subjID = tryParseUInt(inp, "Invalid SubjectID. [Parse Failed]");
			if (subjID == -1) {
				continue;
			}
			
			if (!gradeMap.containsKey(subjID))
				System.out.println("Invalid SubjectID. [Does not exist]");
		} while (subjID == -1);
		
		var selected = gradeMap.get(subjID);
		boolean done = false;
		
		// print menu
		do {
			System.out.println("Columns:");
			int i = 1;
			System.out.println(String.format(
					"  %2s. %s",
					i++ + "",
					"Grade"
			));
			System.out.println(String.format(
					"  %2s. %s",
					i++ + "",
					"Semester"
			));
			System.out.println(String.format(
					"  %2s. %s",
					i++ + "",
					"In Progress"
			));
			
			var inp = promptQuestion("Please select a column to edit: ");
			var col = tryParseUInt(inp, "Invalid column index. [Parse Failed]");
			if (col == -1)
				continue;
			
			switch (col) {
				case 1:
				{
					var newGrade = promptQuestion("    Please provide new grade: ");
					if (!newGrade.matches("^[A-F,0]$"))
					{
						System.out.println("Invalid grade. (Must within these: A, B, C, D, E, F / 0)");
						continue;
					}
					
					selected.setGrade(newGrade.equals("0") ? null : newGrade);
					done = true;
					break;
				}
				case 2:
				{
					var newSem = promptQuestion("    Please provide new semester: ");
					if (!newSem.matches("^[0-9]{1,2}$"))
					{
						System.out.println("Invalid semester. (Min: 0, Max: 99)");
						continue;
					}
					
					selected.setSem(tryParseUInt(newSem));
					done = true;
					break;
				}
				case 3:
				{
					var isInProgress = promptQuestion("    Please provide new progress status [Y: in progress / N: Finished]: ");
					if (!isInProgress.matches("^[YN]$"))
					{
						System.out.println("Invalid progress status. (Must be: Y / N)");
						continue;
					}
					
					selected.setInProgress(isInProgress.equals("Y"));
					done = true;
					break;
				}
				default:
					System.out.println("Invalid column index. [Default Caught]");
			}
			
		} while (!done);
		
		// update the database
		gradeMap.forEach((id, o) -> {
			if (!o.haveChanges)
				return;
			
			Database.getInstance().update(String.format(
				"UPDATE Grade SET Sem = \"%d\", Grade = \"%s\", InProgress = \"%d\" "
						+ "WHERE SubjectID = \"%d\" AND StudentID = \"%s\";",
				o.getSem(),
				o.getGrade(),
				o.isInProgress() ? 1 : 0,
				id,
				selectedStudentID.get(0)
			));
		});
		
		var inp = promptQuestion("Want to update another grade? [y/N]: ");
		return !inp.equalsIgnoreCase("y");
	}
}
