from __future__ import annotations
from pydantic import model_validator, Field  # type: ignore
from typing import (
    Dict,
    Literal,
    ClassVar,
    Any,
    AsyncGenerator,
    List,
    Optional
#    override
)  # NOTE: You must import Optional for late binding to work
import inspect
import re
import json
import asyncio
import time
import asyncio
import numpy as np  # type: ignore

from .base import Agent, agent_registry, LLMMessage
from models import Candidate, ChatMessage, ChatMessageError, ChatMessageMetaData, ApiMessageType, ChatMessageStatus, ChatMessageStreaming, ChatMessageUser, ChatOptions, ChatSenderType, ApiStatusType, SkillMatch, Tunables
import model_cast
from logger import logger
import defines

class SkillMatchAgent(Agent):
    agent_type: Literal["skill_match"] = "skill_match"  # type: ignore
    _agent_type: ClassVar[str] = agent_type  # Add this for registration

    def generate_skill_assessment_prompt(self, skill, rag_context):
        """
        Generate a system prompt to query the LLM for evidence of a specific skill

        Parameters:
        - skill (str): The specific skill to assess from job requirements
        - rag_contexty (str): Additional RAG content queried from candidate documents

        Returns:
        - str: A system prompt tailored to assess the specific skill
        """

        system_prompt = f"""You are an objective skill assessor. Your task is to determine if a candidate possesses 
a SPECIFIC skill based solely on their resume and supporting evidence.

## SKILL TO ASSESS:
"{skill}"

## INSTRUCTIONS:
1. Focus exclusively on assessing the candidate's proficiency with the skill: "{skill}".
2. Examine the resume and supporting documents for both explicit mentions and clearly demonstrated applications of the skill.
3. Do NOT infer the skill unless it is either:
   - Directly mentioned in association with experience, or
   - Clearly evidenced through relevant tools, technologies, responsibilities, or outcomes.
   - Referenced as being used or listed as part of a broader initiative.
4. Evaluate each piece of evidence and assign a confidence rating:
   - STRONG: Explicit use with clear context or repeated/prolonged experience
   - MODERATE: Inferred through tools, environments, or outcomes (e.g., Python used in a listed project/tool)
   - WEAK: Mentioned in a list or indirectly implied without context
   - NONE: No relevant evidence
5. Provide up to 10 evidence_details. Each should include:
   - source: where the evidence appears
   - quote: an exact snippet from the document(s)
   - context: a short rationale explaining how this supports the skill claim
6. When no supporting evidence exists, output a "NONE" rating with an empty evidence details list.
7. IMPORTANT: Even if the skill is only listed in a technologies or languages section, consider this valid evidence.

## OUTPUT FORMAT:
```json
{{
  "skill_assessment": {{
    "skill": "{skill}",
    "evidence_found": true/false,
    "evidence_strength": "STRONG/MODERATE/WEAK/NONE",
    "assessment": "short (one to two sentence) assessment of the candidate's proficiency with {skill}",
    "description": "short (two to three sentence) description of what the {skill} is, independent of whether the candidate has that skill or not",
    "evidence_details": [
      {{
        "source": "resume section/position/project",
        "quote": "exact text from resume showing evidence",
        "context": "brief explanation of how this demonstrates the skill"
      }}
    ]
  }}
}}
```

IMPORTANT: Be factual and precise. If you cannot find strong evidence for this specific skill, it's better to indicate "evidence_strength": WEAK than to stretch for connections. 
Focus only on "{skill}" and not similar skills unless they directly demonstrate the required skill. 
Remember that a skill listed in a "Languages" or "Technologies" section should be considered valid evidence.

Adhere strictly to the JSON output format requested. Do not include any additional text or commentary outside the JSON structure.
"""

        prompt = f"""Analyze the candidate information below for evidence of "{skill}".

RESPOND WITH ONLY VALID JSON USING THE EXACT FORMAT SPECIFIED.

<candidate_info>
{rag_context}
</candidate_info>

JSON RESPONSE:"""

        return system_prompt, prompt

    async def analyze_job_requirements(
        self, llm: Any, model: str, session_id: str, requirement: str
    ) -> AsyncGenerator[ChatMessage, None]:
        """Analyze job requirements from job description."""
        system_prompt, prompt = self.create_job_analysis_prompt(requirement)

        generated_message = None
        async for generated_message in self.llm_one_shot(llm, model, session_id=session_id, prompt=prompt, system_prompt=system_prompt):
            if generated_message.status == ApiStatusType.ERROR:
                generated_message.content = "Error analyzing job requirements."
                yield generated_message
                return
            if generated_message.status != ApiStatusType.DONE:
                yield generated_message

        if not generated_message:
            error_message = ChatMessageError(
                session_id=session_id,
                content = "Job requirements analysis failed to generate a response.")
            yield error_message
            return

        chat_message = ChatMessage(
            session_id=session_id,
            status=ApiStatusType.DONE,
            content=generated_message.content,
            metadata=generated_message.metadata,
        )
        yield chat_message
        return

    async def generate(
        self, llm: Any, model: str, 
        session_id: str, prompt: str, 
        tunables: Optional[Tunables] = None, 
        temperature=0.7
    ) -> AsyncGenerator[ChatMessage | ChatMessageStatus | ChatMessageError | ChatMessageStreaming, None]:
        # Stage 1A: Analyze job requirements
        rag_message = None
        async for rag_message in self.generate_rag_results(session_id=session_id, prompt=prompt):
            if rag_message.status == ApiStatusType.ERROR:
                yield rag_message
                return
            if rag_message.status != ApiStatusType.DONE:
                yield rag_message

        if rag_message is None:
            error_message = ChatMessageError(
                session_id=session_id,
                content="RAG search did not return a valid response."
            )
            logger.error(f"⚠️ {error_message.content}")
            yield error_message
            return

        logger.info(f"🔍 RAG content retrieved: {len(rag_message.content)} bytes")
        rag_context = self.get_rag_context(rag_message)
        system_prompt, prompt = self.generate_skill_assessment_prompt(skill=prompt, rag_context=rag_context)

        skill_assessment = None
        async for skill_assessment in self.llm_one_shot(llm=llm, model=model, session_id=session_id, prompt=prompt, system_prompt=system_prompt, temperature=0.7):
            if skill_assessment.status == ApiStatusType.ERROR:
                logger.error(f"⚠️ {skill_assessment.content}")
                yield skill_assessment
                return
            if skill_assessment.status != ApiStatusType.DONE:
                yield skill_assessment

        if skill_assessment is None:
            error_message = ChatMessageError(
                session_id=session_id,
                content="Skill assessment failed to generate a response."
            )
            logger.error(f"⚠️ {error_message.content}")
            yield error_message
            return

        json_str = self.extract_json_from_text(skill_assessment.content)
        skill_assessment_data = ""
        try:
            skill_assessment_data = json.loads(json_str).get("skill_assessment", {})
        except Exception as e:
            error_message = ChatMessageError(
                session_id=session_id,
                content=f"Failed to parse Skill assessment JSON: {str(e)}\n\n{skill_assessment.content}"
            )
            logger.error(f"⚠️ {error_message.content}")
            yield error_message
            return
        
        skill_assessment_message = ChatMessage(
            session_id=session_id,
            status=ApiStatusType.DONE,
            content=json.dumps(skill_assessment_data),
            metadata=skill_assessment.metadata
        )
        yield skill_assessment_message
        logger.info(f"✅ Skill assessment completed successfully.")
        return

# Register the base agent
agent_registry.register(SkillMatchAgent._agent_type, SkillMatchAgent)
