import asyncio
import json
from os import wait
from typing import Optional, List, Dict, Any
from datetime import date
from time import sleep
from crewai.flow.flow import start, listen, and_
from flask import current_app
from pydantic import BaseModel, Field, EmailStr
from sqlalchemy.exc import SQLAlchemyError

from common.extensions import db
from common.models.user import Tenant
from common.models.interaction import Specialist
from eveai_chat_workers.outputs.globals.basic_types.list_item import ListItem
from eveai_chat_workers.specialists.crewai_base_specialist import CrewAIBaseSpecialistExecutor
from eveai_chat_workers.specialists.specialist_typing import SpecialistResult, SpecialistArguments
from eveai_chat_workers.outputs.traicie.competencies.competencies_v1_1 import Competencies
from eveai_chat_workers.specialists.crewai_base_classes import EveAICrewAICrew, EveAICrewAIFlow, EveAIFlowState
from common.services.interaction.specialist_services import SpecialistServices
from common.extensions import cache_manager


class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
    """
    type: TRAICIE_SELECTION_SPECIALIST
    type_version: 1.1
    Traicie Selection Specialist Executor class
    """

    def __init__(self, tenant_id, specialist_id, session_id, task_id, **kwargs):
        self.role_definition_crew = None

        super().__init__(tenant_id, specialist_id, session_id, task_id)

        # Load the Tenant & set language
        self.tenant = Tenant.query.get_or_404(tenant_id)

    @property
    def type(self) -> str:
        return "TRAICIE_SELECTION_SPECIALIST"

    @property
    def type_version(self) -> str:
        return "1.1"

    def _config_task_agents(self):
        self._add_task_agent("traicie_get_competencies_task", "traicie_hr_bp_agent")

    def _config_pydantic_outputs(self):
        self._add_pydantic_output("traicie_get_competencies_task", Competencies, "competencies")

    def _instantiate_specialist(self):
        verbose = self.tuning

        role_definition_agents = [self.traicie_hr_bp_agent]
        role_definition_tasks = [self.traicie_get_competencies_task]
        self.role_definition_crew = EveAICrewAICrew(
            self,
            "Role Definition Crew",
            agents=role_definition_agents,
            tasks=role_definition_tasks,
            verbose=verbose,
        )

        self.flow = RoleDefinitionFlow(
            self,
            self.role_definition_crew
        )

    def execute(self, arguments: SpecialistArguments, formatted_context, citations) -> SpecialistResult:
        self.log_tuning("Traicie Selection Specialist execution started", {})

        current_app.logger.debug(f"Arguments: {arguments.model_dump()}")
        current_app.logger.debug(f"Formatted Context: {formatted_context}")
        current_app.logger.debug(f"Formatted History: {self._formatted_history}")
        current_app.logger.debug(f"Cached Chat Session: {self._cached_session}")

        # flow_inputs = {
        #     "vacancy_text": arguments.vacancy_text,
        #     "role_name": arguments.role_name,
        #     'role_reference': arguments.role_reference,
        # }
        #
        # flow_results = self.flow.kickoff(inputs=flow_inputs)
        #
        # flow_state = self.flow.state
        #
        # results = RoleDefinitionSpecialistResult.create_for_type(self.type, self.type_version)
        # if flow_state.competencies:
        #     results.competencies = flow_state.competencies

        # self.create_selection_specialist(arguments, flow_state.competencies)
        for i in range(3):
            sleep(1)
            self.ept.send_update(self.task_id, "Traicie Selection Specialist Processing", {"name": f"Processing Iteration {i}"})

        if not self._cached_session.interactions:
            specialist_phase = "initial"
        else:
            specialist_phase = self._cached_session.interactions[-1].specialist_results.get('phase', 'initial')

        results = None

        match specialist_phase:
            case "initial":
                ko_form = form_definition = {
                    "type": "KO_CRITERIA_FORM",
                    "version": "1.0.0",
                    "name": "KO Criteria Form",
                    "icon": "verified",
                    "fields": {
                        "weekend_werk": {
                            "name": "Weekend Werk",
                            "description": "Werken in het weekend",
                            "context": "Ben je bereid om in het weekend te werken?",
                            "type": "options",
                            "required": True,
                            "allowed_values": ["Ja, geen probleem", "Nee, liever niet"]
                        },
                        "fysisch_werk": {
                            "name": "Fysische Activiteit",
                            "description": "Fysisch werken",
                            "context": "In onze winkels is het belangrijk dat je 8u kan rechtstaan in een iets koeler omgeving. Is dit voor jou haalbaar?",
                            "type": "options",
                            "required": True,
                            "allowed_values": ["Ja, prima haalbaar", "Neen, mogelijks een probleem"]
                        },
                        "nabijheid_werk": {
                            "name": "Nabijheid Werk",
                            "description": "Afstand Woon-Werk",
                            "context": "We hebben gemerkt dat tevreden collega’s in de buurt van de winkel wonen. Hoe ver wil jij je verplaatsen?",
                            "type": "options",
                            "required": True,
                            "allowed_values": ["Meer dan 15 km", "Minder dan 15 km"]
                        },
                    }
                }
                results = SpecialistResult.create_for_type(self.type, self.type_version,
                                                           answer=f"We starten met een aantal KO Criteria vragen",
                                                           form_request=ko_form,
                                                           phase="ko_questions")
            case "ko_questions":
                contact_form = cache_manager.specialist_forms_config_cache.get_config("PERSONAL_CONTACT_FORM", "1.0")
                results = SpecialistResult.create_for_type(self.type, self.type_version,
                                                           answer=f"We hebben de antwoorden op de KO criteria verwerkt. Je bent een geschikte kandidaat. Kan je je contactegevens doorgeven?",
                                                           form_request=contact_form,
                                                           phase="personal_contact_data")
            case "personal_contact_data":
                results = SpecialistResult.create_for_type(self.type, self.type_version,
                                                           answer=f"We hebben de contactgegevens verwerkt. We nemen zo snel mogelijk contact met je op.",
                                                           phase="candidate_selected")

        self.log_tuning(f"Traicie Selection Specialist execution ended", {"Results": results.model_dump() if results else "No info"})

        return results

    def create_selection_specialist(self, arguments: SpecialistArguments, competencies: List[ListItem]):
        """This method creates a new TRAICIE_SELECTION_SPECIALIST specialist with the given competencies."""
        current_app.logger.info(f"Creating selection with arguments: {arguments.model_dump()}")
        selection_comptencies = []
        for competency in competencies:
            selection_competency = {
                "title": competency.title,
                "description": competency.description,
                "assess": True,
                "is_knockout": False,
            }
            selection_comptencies.append(selection_competency)

        selection_config = {
            "name": arguments.specialist_name,
            "competencies": selection_comptencies,
            "tone_of_voice": "Professional & Neutral",
            "language_level": "Standard",
            "role_reference": arguments.role_reference,
        }
        name = arguments.role_name
        if len(name) > 50:
            name = name[:47] + "..."

        new_specialist = Specialist(
            name=name,
            description=f"Specialist for {arguments.role_name} role",
            type="TRAICIE_SELECTION_SPECIALIST",
            type_version="1.0",
            tuning=False,
            configuration=selection_config,
        )
        try:
            db.session.add(new_specialist)
            db.session.commit()
        except SQLAlchemyError as e:
            db.session.rollback()
            current_app.logger.error(f"Error creating selection specialist: {str(e)}")
            raise e

        SpecialistServices.initialize_specialist(new_specialist.id, self.type, self.type_version)


class SelectionSpecialistInput(BaseModel):
    region: str = Field(..., alias="region")
    working_schedule: Optional[str] = Field(..., alias="working_schedule")
    start_date: Optional[date] = Field(None, alias="vacancy_text")
    language: Optional[str] = Field(None, alias="language")
    interaction_mode: Optional[str] = Field(None, alias="interaction_mode")
    question: Optional[str] = Field(None, alias="question")
    field_values: Optional[Dict[str, Any]] = Field(None, alias="field_values")


class SelectionSpecialistKOCriteriumScore(BaseModel):
    criterium: Optional[str] = Field(None, alias="criterium")
    answer: Optional[str] = Field(None, alias="answer")
    score: Optional[int] = Field(None, alias="score")


class SelectionSpecialistCompetencyScore(BaseModel):
    competency: Optional[str] = Field(None, alias="competency")
    answer: Optional[str] = Field(None, alias="answer")
    score: Optional[int] = Field(None, alias="score")


class PersonalContactData(BaseModel):
    name: str = Field(..., description="Your name", alias="name")
    email: EmailStr = Field(..., description="Your Name", alias="email")
    phone: str = Field(..., description="Your Phone Number", alias="phone")
    address: Optional[str] = Field(None, description="Your Address", alias="address")
    zip: Optional[str] = Field(None, description="Postal Code", alias="zip")
    city: Optional[str] = Field(None, description="City", alias="city")
    country: Optional[str] = Field(None, description="Country", alias="country")
    consent: bool = Field(..., description="Consent", alias="consent")


class SelectionSpecialistResult(SpecialistResult):
    ko_criteria_scores: Optional[List[SelectionSpecialistKOCriteriumScore]] = Field(
        None, alias="ko_criteria_scores"
    )
    competency_scores: Optional[List[SelectionSpecialistCompetencyScore]] = Field(
        None, alias="competency_scores"
    )
    personal_contact_data: Optional[PersonalContactData] = Field(
        None, alias="personal_contact_data"
    )


class SelectionSpecialistFlowState(EveAIFlowState):
    """Flow state for Traicie Role Definition specialist that automatically updates from task outputs"""
    input: Optional[SelectionSpecialistInput] = None
    ko_criteria_scores: Optional[List[SelectionSpecialistKOCriteriumScore]] = Field(
        None, alias="ko_criteria_scores"
    )
    competency_scores: Optional[List[SelectionSpecialistCompetencyScore]] = Field(
        None, alias="competency_scores"
    )
    personal_contact_data: Optional[PersonalContactData] = Field(
        None, alias="personal_contact_data"
    )
    phase: Optional[str] = Field(None, alias="phase")
    interaction_mode: Optional[str] = Field(None, alias="mode")


class RoleDefinitionFlow(EveAICrewAIFlow[SelectionSpecialistFlowState]):
    def __init__(self,
                 specialist_executor: CrewAIBaseSpecialistExecutor,
                 role_definition_crew: EveAICrewAICrew,
                 **kwargs):
        super().__init__(specialist_executor, "Traicie Role Definition Specialist Flow", **kwargs)
        self.specialist_executor = specialist_executor
        self.role_definition_crew = role_definition_crew
        self.exception_raised = False

    @start()
    def process_inputs(self):
        return ""

    @listen(process_inputs)
    async def execute_role_definition   (self):
        inputs = self.state.input.model_dump()
        try:
            current_app.logger.debug("In execute_role_definition")
            crew_output = await self.role_definition_crew.kickoff_async(inputs=inputs)
            # Unfortunately, crew_output will only contain the output of the latest task.
            # As we will only take into account the flow state, we need to ensure both competencies and criteria
            # are copies to the flow state.
            update = {}
            for task in self.role_definition_crew.tasks:
                current_app.logger.debug(f"Task {task.name} output:\n{task.output}")
                if task.name == "traicie_get_competencies_task":
                    # update["competencies"] = task.output.pydantic.competencies
                    self.state.competencies = task.output.pydantic.competencies
            # crew_output.pydantic = crew_output.pydantic.model_copy(update=update)
            current_app.logger.debug(f"State after execute_role_definition: {self.state}")
            current_app.logger.debug(f"State dump after execute_role_definition: {self.state.model_dump()}")
            return crew_output
        except Exception as e:
            current_app.logger.error(f"CREW execute_role_definition Kickoff Error: {str(e)}")
            self.exception_raised = True
            raise e

    async def kickoff_async(self, inputs=None):
        current_app.logger.debug(f"Async kickoff {self.name}")
        current_app.logger.debug(f"Inputs: {inputs}")
        self.state.input = RoleDefinitionSpecialistInput.model_validate(inputs)
        current_app.logger.debug(f"State: {self.state}")
        result = await super().kickoff_async(inputs)
        return self.state
