__author__ = "Roland Trouville"
__copyright__ = "Copyright 2015+, Consortium MonPaGe"
__license__ = "Creative Commons 4.0 By-Nc-Sa"
__maintainer__ = "Roland Trouville"
__email__ = "contact.monpage@gmail.com"
__status__ = "Production"

import datetime
import logging
import os
import re
from typing import TypeVar, Optional

from tools.csv_manager import CSVManager
from tools.db_manager import DBManager
from tools.display_tools import DisplayTools
from tools.general_tools import GeneralTools
from tools.options import Options
from tools.ui_tools import UITools

QtWidget = TypeVar("QtWidget")
QMainWindow = TypeVar("QMainWindow")

logger = logging.getLogger(__name__)


class ParticipantManager(object):
	"""
	Managing module participants, the access to their cotation_result databases, data csvs and folder structure
	"""

	data_csv_filename: str
	gender: str
	birthyear: Optional[int]
	cotation_db_filename: str
	session_mask = re.compile(r"\d{4}_\d{2}_\d{2}")
	current_participant: str
	folder_path: str
	full_session_path: str
	filename_prefix: str

	@staticmethod
	def set_current_participant(participant_code: str):
		"""
		Sets the current participant by their code and prepares related data paths.

		Converts the participant code to uppercase, creates the participant folder if it
		doesn't exist, and manages the renaming of old data and database files to new
		naming conventions.

		Attempts to load participant metadata such as birth year and gender from the
		participant's data CSV file. Logs an error if the CSV file is missing or cannot be
		read.

		Args:
			participant_code (str): The code identifying the participant.
		"""
		participant_code = participant_code.upper()
		ParticipantManager.current_participant = participant_code
		folder_path = "./data/participant/" + participant_code + "/"
		if not os.path.isdir(folder_path):
			os.makedirs(folder_path)
		ParticipantManager.folder_path = folder_path
		data_csv_name = "data_" + participant_code + ".csv"
		if os.path.exists(folder_path + "data.csv"):
			# old version of the data CSV, we rename
			os.rename(folder_path + "data.csv", folder_path + data_csv_name)
		ParticipantManager.data_csv_filename = folder_path + data_csv_name
		if os.path.exists(ParticipantManager.data_csv_filename):
			try:
				data = CSVManager.read_file(ParticipantManager.data_csv_filename, "\t")
				ParticipantManager.birthyear = int(data[1][0])
				ParticipantManager.gender = str(data[2][0])
			except Exception as e:
				UITools.error_log(e, "Erreur dans set_current_participant")
				return
		else:
			logger.error(
				f"Participant manager - no data csv found {ParticipantManager.data_csv_filename}"
			)

		cotation_db_name = "cotation_result_" + participant_code + ".db"
		if os.path.exists(folder_path + "cotation_result.db"):
			# old version of the cotation_db, we rename
			os.rename(
				folder_path + "cotation_result.db", folder_path + cotation_db_name
			)
		ParticipantManager.cotation_db_filename = folder_path + cotation_db_name

	@staticmethod
	def get_participant_age(year: int = 1900) -> int:
		"""
		Calculates the participant's age for a given year.

		Args:
			year (int): The reference year to calculate the age against (default is 1900).

		Returns:
			int: The participant's age for the given year, or -1 if birth year is unknown.
		"""
		if ParticipantManager.birthyear is None:
			return -1
		return year - ParticipantManager.birthyear

	@staticmethod
	def check_cotation_result_db_file(parent: QMainWindow = None) -> bool:
		"""
		Checks if the participant's cotation result database file exists and handles
		renaming if needed.

		If the standard cotation result DB file exists, returns True.
		Otherwise, it looks for .db files in the participant's folder:
		- If no .db files are found, returns False.
		- If multiple .db files are found, shows an error dialog and returns False.
		- If exactly one .db file is found, prompts the user to rename it to the
			standard filename. Renames if confirmed and returns True; otherwise, returns False.

		Args:
			parent (QMainWindow, optional): Parent window for dialog display.

		Returns:
			bool: True if the cotation DB file is valid or renamed successfully, False otherwise.
		"""
		if os.path.exists(ParticipantManager.cotation_db_filename):
			return True
		files = GeneralTools.find_files(ParticipantManager.folder_path, ".db", False)

		try:
			file = next(files)
		except StopIteration:
			# len(files) == 0
			return False

		try:
			# len(files) > 1
			next(files)
			DisplayTools.open_dialog(
				parent,
				"Problème avec le fichier de cotation de "
				+ ParticipantManager.current_participant,
				"Pas de fichier de cotation standard et plusieurs fichiers .db trouvé dans le repertoire. Supprimez les "
				"fichiers en trop pour pouvoir continuer",
			)
			return False
		except StopIteration:
			pass

		choice = DisplayTools.open_confirm_dialog(
			parent,
			"Renommer le fichier ?",
			"Un fichier de resultat, "
			+ file
			+ " a été trouvé pour le participant "
			+ ParticipantManager.current_participant
			+ ". Voulez vous le renommer en "
			+ ParticipantManager.cotation_db_filename
			+ " ?",
		)
		if choice:
			os.rename(file, ParticipantManager.cotation_db_filename)
			return True
		return False

	@staticmethod
	def save_meta_data(birthyear: int = 1900, gender: str = ""):
		"""
		Saves participant metadata (birthyear and gender) to the participant's data CSV file.

		This method resets the existing data CSV file and writes the participant code,
		birthyear, and gender into it. Also updates the ParticipantManager's
		birthyear and gender attributes.

		Args:
			birthyear (int): The participant's birth year. Default is 1900.
			gender (str): The participant's gender as a string.
		"""
		CSVManager.reset_file(ParticipantManager.data_csv_filename)
		CSVManager.add_lines(
			ParticipantManager.data_csv_filename,
			"\t",
			[[ParticipantManager.current_participant]],
		)
		CSVManager.add_lines(
			ParticipantManager.data_csv_filename, "\t", [[str(birthyear)]]
		)
		CSVManager.add_lines(ParticipantManager.data_csv_filename, "\t", [[gender]])
		ParticipantManager.birthyear = birthyear
		ParticipantManager.gender = gender

	@staticmethod
	def start_session(module_name: str = None):
		"""
		Starts a new session directory for the current participant.

		Creates a dated folder for the session inside the participant's folder.
		If a module name is provided, creates a subfolder for that module.
		If the subfolder already exists and research mode is enabled, it creates
		numbered session folders (e.g., module_name_sess2, _sess3, etc.) up to 9.
		Sets internal paths and filename prefix for use during the session.

		Args:
			module_name (str, optional): The name of the module for which to start the session.
		"""
		ParticipantManager.base_session_path = None
		ParticipantManager.full_session_path = None
		ParticipantManager.filename_prefix = None

		now = datetime.datetime.now()
		session_date = (
			("%02d" % now.year) + "_" + ("%02d" % now.month) + "_" + ("%02d" % now.day)
		)
		session_folder = ParticipantManager.folder_path + session_date + "/"
		if not os.path.isdir(session_folder):
			os.makedirs(session_folder)
		ParticipantManager.base_session_path = session_folder

		if module_name is not None:
			session_path = ParticipantManager.base_session_path + module_name + "/"
			if not os.path.isdir(session_path):
				os.makedirs(session_path)
				ParticipantManager.full_session_path = session_path
			else:
				if Options.is_enabled(Options.Option.RESEARCH):
					for i in range(2, 10):
						session_path = (
							ParticipantManager.base_session_path
							+ module_name
							+ "_sess"
							+ str(i)
							+ "/"
						)
						if not os.path.isdir(session_path):
							os.makedirs(session_path)
							ParticipantManager.full_session_path = session_path
							break
				else:  # Si mode Screening, on ne fait pas de sessions
					ParticipantManager.full_session_path = session_path

			ParticipantManager.filename_prefix = (
				ParticipantManager.current_participant
				+ "_"
				+ session_date
				+ "_"
				+ module_name
				+ "_"
			)

	@staticmethod
	def get_all_available_sessions():
		"""
		Retrieves a list of all available session directories for the current participant.

		This method scans the participant's folder and returns all subdirectory names
		that match the session naming pattern defined by `ParticipantManager.session_mask`.

		Returns:
			list[str]: A list of session directory names matching the session pattern.
		"""
		ret = []
		if os.path.isdir(ParticipantManager.folder_path):
			for dirname in os.listdir(ParticipantManager.folder_path):
				if os.path.isdir(ParticipantManager.folder_path + "/" + dirname):
					if ParticipantManager.session_mask.match(dirname):
						ret.append(dirname)
		return ret
