"""
    Module to manage multiple 'Agency' models, write them to a file, load them from a file or parse them from a json object.

    Author: Felix Michelis
    Date: 12/05/2023
        dd/mm/yyyy
"""
from pydantic import ValidationError
from typing import Optional, List
import csv
from collections import Counter
import json


from ..utility import get_error_manager
from ..objects.agency import Agency
from ..models.errors import ErrorTypes, AgencyError




class Agencies():
    """A class to manage multiple 'Agency' models and write them to a file, load them from a file or parse them from a json object."""

    def __init__(self, agencies: List[Agency] = None, file: str = None):
        """
            A class to manage multiple 'Agency' models and write them to a file, load them from a file or parse them from a json object.

            :param agencies [List[Agency]] -- A list of 'Agency' models, can be passed later with the respective function on function call
            :param file [str] -- The file to write to or load from, can be passed later with the respective function on function call
        """
        self.error_manager = get_error_manager()
        self.agencies: List[Agency] = agencies
        self.file: str = file
    

    def to_file(self, agencies: Optional[List[Agency]] = None, file: Optional[str] = None) -> bool:
        """
            Writes a list of 'Agency' models to a file in csv format.

            :param agencies [Optional[List[Agency]]] -- A list containing 'Agency' models, if not given it will take 'self.agencies' if set
            :param file [Optional[str]] -- The file to write to, if not given it will take 'self.file' if set

            :return bool -- True on success, else False or raises Error if not silent error enabled
        """

        if not agencies:
            if self.agencies == None:
                raise TypeError("Got no agencies to write to file!")
            agencies = self.agencies

        if not file:
            if self.file == None:
                raise TypeError("Got no file to write to!")
            file = self.file


        headers: list = [variable for variable in agencies[0].dict(exclude_unset=False).keys()] # create a list of the headers for the csv file
        rows: list = []
        
        agencies_ids = [] # this list stores the ids of all the agencies that will be written to the file so we can verify that no agencies have the same id
        agencies_timezones = [] # stores the timezone of all the agencies as they have to be the same
        for obj in agencies:
            obj_as_dict = obj.dict(exclude_unset=False)
            agencies_ids.append(obj_as_dict["agency_id"]) # add the id of the agency to our ids list for verification later on
            agencies_timezones.append(obj_as_dict["agency_timezone"])
            if len(agencies_timezones) > 0:
                if obj_as_dict["agency_timezone"] != agencies_timezones[0]:
                    if self.error_manager.is_silent_errors():
                        self.error_manager.add_error(AgencyError(
                            error_type=ErrorTypes.AgencyErrorTimezonesNotSame,
                            message="If multiple agencies are present, they must have the same timezone!",
                            value = [obj_as_dict]
                        ))
                    else:
                        raise ValueError(f"If multiple agencies are present, they must have the same timezone! \n {obj_as_dict}")
            rows.append(obj_as_dict) # add the agency as json to our rows that will be written to the csv file

        if len(agencies_ids) > 1:
            duplicates = [k for k, v in Counter(agencies_ids).items() if v>1] # returns a list with all duplicate ids; if the id '123' was for example at least 2 times in the ids list the output would be: ['123']
            if len(duplicates) > 0:
                if self.error_manager.is_silent_errors():
                    self.error_manager.add_error(AgencyError(
                        caller = "AgencyManager",
                        error_type=ErrorTypes.AgencyErrorDuplicateID,
                        message="Multiple agencies cannot have the same id!",
                        values = duplicates
                    ))
                    return False
                else:
                    raise ValueError(f"Multiple agencies cannot have the same id! Got the following duplicate ids: {duplicates}")
                

        
                
        
            

        with open(file, "w", newline='') as _file:
            csvwriter = csv.DictWriter(_file, fieldnames=headers)
            csvwriter.writeheader()
            csvwriter.writerows(rows)


        return True



    def parse(self, agencies_list: List[dict]) -> List[Agency]:
        """
            Converts a list of dictionaries which each represent an 'Agency' to a list of 'Agency' models.
        """
        agencies = []

        for agency in agencies_list:
            if self.is_valid_agency(agency):
                agencies.append(Agency.parse_obj(agency))
            else:
                if self.error_manager.is_silent_errors():
                    self.error_manager.add_error(AgencyError(
                        caller = "AgencyManager",
                        error_type = ErrorTypes.AgencyErrorInvalidAgency,
                        message="Invalid agency from dict!",
                        values = [agency]
                    ))
                    return False
                else:
                    raise ValueError(f"Invalid agency: {agency}")

        return agencies
    


    def load_file(self, file: Optional[str] = None) -> List[Agency]:
        """
            Reads a file in csv format and outputs a list of 'Agency' models contained in that file.

            :param file [Optional[str]] -- The file to read from, if not given it will take 'self.file' if its set

            :return List[Agency] -- A list conatining 'Agency' models
        """

        if not file:
            if self.file == None:
                raise TypeError("Got no file to load from!")
            file = self.file
        

        with open(file, "r") as _file:
            csvreader = list(csv.DictReader(_file)) # gives a list of dictionaries where each dict is a json representation of an agency

        agencies = self.parse(csvreader)

        agencies_ids = []
        agencies_timezones = []

        for agency in agencies:
            if agency.agency_id in agencies_ids:
                if self.error_manager.is_silent_errors():
                    self.error_manager.add_error(AgencyError(
                        caller = "AgencyManager",
                        error_type = ErrorTypes.AgencyErrorDuplicateID,
                        message="Cannot have multiple agencies with the same id!",
                        values = [agency.agency_id]
                    ))
                    return False
                else:
                    raise ValueError("Cannot have multiple agencies with the same id!")
            
            if len(agencies_timezones) > 0:
                if agency.agency_timezone != agencies_timezones[0]:
                    if self.error_manager.is_silent_errors():
                        self.error_manager.add_error(AgencyError(
                            error_type=ErrorTypes.AgencyErrorTimezonesNotSame,
                            message="If multiple agencies are present, they must have the same timezone!",
                            value = [agency]
                        ))
                    else:
                        raise ValueError(f"If multiple agencies are present, they must have the same timezone! \n {agency}")
                    
            agencies_ids.append(agency.agency_id)

        return agencies

        
    def is_valid_agency(self, agency: dict) -> bool:
        """
            Checks if a dict is a valid 'Agency' model.

            :param agency [dict] -- The dict to validate
            
            :return bool -- 'True' if it contains a valid model, otherwise ('False', dict[argument_causing_error: error_message])
        """

        try:
            Agency.parse_obj(agency)

        except ValidationError as e:
            errors = {}

            for error in json.loads(e.json()):
                errors[error["loc"][0]] = error["msg"]

            return (False, errors)
        
        return True