"""TicketAnalyzerAgent — AI-powered test plan generator from Jira tickets.

When no test case files are attached to a ticket, this agent analyses the
ticket description and selects the most relevant predefined YAML flows,
ordering them logically.
"""

from __future__ import annotations

import json
import logging
from pathlib import Path

from agno.agent import Agent
from pydantic import BaseModel, Field

from antiqa.config.models import resolve_model
from antiqa.config.schema import AntiQAConfig
from antiqa.flows.parser import FlowDefinition, FlowParser

log = logging.getLogger("antiqa.agents")

_PROMPT_PATH = Path(__file__).parent / "prompts" / "analyze_ticket.md"


class TestPlan(BaseModel):
    """AI-generated test plan mapping ticket to flows."""
    flows: list[str] = Field(default_factory=list)
    reasoning: str = ""
    risk_areas: list[str] = Field(default_factory=list)


class TicketAnalyzerAgent:
    """Analyse a Jira ticket and produce a TestPlan of flows to run."""

    def __init__(self, config: AntiQAConfig) -> None:
        self.config = config
        self.model = resolve_model(config.llm)
        self._system_prompt = _PROMPT_PATH.read_text()

    def analyze(
        self,
        ticket_summary: str,
        ticket_description: str,
        available_flows: list[FlowDefinition] | None = None,
    ) -> TestPlan:
        """Return a TestPlan selecting which flows to run for this ticket."""
        if available_flows is None:
            parser = FlowParser(self.config.flows_dir)
            available_flows = parser.list_flows()

        user_prompt = self._build_prompt(ticket_summary, ticket_description, available_flows)

        agent = Agent(
            model=self.model,
            instructions=[self._system_prompt],
            markdown=False,
        )

        response = agent.run(user_prompt)
        content = self._clean(response.content or "{}")

        try:
            data = json.loads(content)
            plan = TestPlan(
                flows=data.get("flows", []),
                reasoning=data.get("reasoning", ""),
                risk_areas=data.get("risk_areas", []),
            )
        except (json.JSONDecodeError, Exception) as exc:
            log.warning("AI analysis failed, falling back to all flows: %s", exc)
            plan = TestPlan(
                flows=[f.name for f in available_flows],
                reasoning="Fallback: running all available flows because AI analysis was inconclusive.",
                risk_areas=[],
            )

        # Validate flow names exist
        valid_names = {f.name for f in available_flows}
        plan.flows = [f for f in plan.flows if f in valid_names]

        if not plan.flows and available_flows:
            plan.flows = [f.name for f in available_flows]
            plan.reasoning += " (no matching flows found — running all)"

        log.info("Test plan: %d flows selected — %s", len(plan.flows), ", ".join(plan.flows))
        return plan

    def _build_prompt(
        self,
        summary: str,
        description: str,
        flows: list[FlowDefinition],
    ) -> str:
        parts = [
            f"## Jira Ticket\n",
            f"**Summary:** {summary}\n",
            f"**Description:**\n{description}\n",
            f"\n## Available Test Flows\n",
        ]
        for flow in flows:
            step_summary = ", ".join(
                f"{s.action}({s.selector or s.url or ''})" for s in flow.steps[:5]
            )
            parts.append(f"- **{flow.name}**: {flow.description or 'No description'}")
            parts.append(f"  Steps: {step_summary}")
            if flow.assertions:
                parts.append(f"  Assertions: {len(flow.assertions)}")

        return "\n".join(parts)

    @staticmethod
    def _clean(content: str) -> str:
        content = content.strip()
        if content.startswith("```"):
            first_newline = content.index("\n") if "\n" in content else 3
            content = content[first_newline + 1:]
        if content.endswith("```"):
            content = content[:-3]
        return content.strip()
