"""TestCaseInterpreterAgent — AI-powered conversion of human test steps
to executable browser actions.

Takes parsed TestCase objects (from Excel/CSV) and produces FlowDefinition
objects that the FlowRunner can execute directly.
"""

from __future__ import annotations

import json
import logging
from pathlib import Path
from typing import Any

from agno.agent import Agent

from antiqa.config.models import resolve_model
from antiqa.config.schema import AntiQAConfig
from antiqa.crawler.graph import NavigationGraph
from antiqa.flows.parser import FlowAssertion, FlowDefinition, FlowStep
from antiqa.jira.test_case_parser import TestCase

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

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


class TestCaseInterpreterAgent:
    """Convert human-written test steps into executable FlowDefinitions."""

    def __init__(
        self,
        config: AntiQAConfig,
        graph: NavigationGraph | None = None,
    ) -> None:
        self.config = config
        self.model = resolve_model(config.llm)
        self.graph = graph

        self._system_prompt = _PROMPT_PATH.read_text()

    def interpret(self, test_case: TestCase) -> FlowDefinition:
        """Convert a single TestCase into a FlowDefinition."""
        user_prompt = self._build_prompt(test_case)

        try:
            actions = self._ai_interpret(user_prompt)
        except json.JSONDecodeError:
            log.debug("AI returned non-JSON for %s, using heuristic fallback", test_case.id)
            actions = self._fallback_interpret(test_case)
        except Exception:
            log.info("AI unavailable for %s, using heuristic fallback", test_case.id)
            actions = self._fallback_interpret(test_case)

        steps = [self._action_to_step(a) for a in actions]

        # Build assertions from "verify" / "check" steps in the original
        assertions = self._extract_assertions(test_case, actions)

        return FlowDefinition(
            name=f"tc_{test_case.id.lower().replace('-', '_')}",
            description=f"Auto-interpreted: {test_case.title}",
            steps=steps,
            assertions=assertions,
            params={},
        )

    def interpret_many(self, test_cases: list[TestCase]) -> list[FlowDefinition]:
        """Convert multiple test cases into flow definitions."""
        flows: list[FlowDefinition] = []
        for tc in test_cases:
            try:
                flow = self.interpret(tc)
                flows.append(flow)
                log.info("Interpreted %s: %s (%d steps)", tc.id, tc.title, len(flow.steps))
            except Exception as exc:
                log.error("Failed to interpret %s: %s", tc.id, exc)
        return flows

    def _ai_interpret(self, user_prompt: str) -> list[dict[str, Any]]:
        """Call the LLM, suppressing noisy Agno logs on auth failures."""
        from agno.utils.log import logger as agno_logger

        prev_handlers = agno_logger.handlers[:]
        prev_propagate = agno_logger.propagate
        agno_logger.handlers = [logging.NullHandler()]
        agno_logger.propagate = False
        try:
            agent = Agent(
                model=self.model,
                instructions=[self._system_prompt],
                markdown=False,
            )
            response = agent.run(user_prompt)
            content = self._clean_response(response.content or "[]")
            return json.loads(content)
        finally:
            agno_logger.handlers = prev_handlers
            agno_logger.propagate = prev_propagate

    # ── Prompt construction ────────────────────────────

    def _build_prompt(self, test_case: TestCase) -> str:
        parts = [
            f"## Test Case: {test_case.id} — {test_case.title}\n",
        ]

        if test_case.preconditions:
            parts.append(f"**Preconditions:** {test_case.preconditions}\n")

        parts.append("**Steps:**\n")
        for step in test_case.steps:
            line = f"{step.step_number}. {step.action}"
            if step.test_data:
                line += f"  [data: {step.test_data}]"
            if step.expected_result:
                line += f"  → Expected: {step.expected_result}"
            parts.append(line)

        parts.append(f"\n**App URL:** {self.config.app_url}")

        if self.graph:
            parts.append(f"\n**Navigation Graph:**\n{self.graph.summary_for_ai()}")

        return "\n".join(parts)

    # ── Response processing ────────────────────────────

    @staticmethod
    def _clean_response(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()

    @staticmethod
    def _action_to_step(action: dict[str, Any]) -> FlowStep:
        return FlowStep(
            action=action.get("action", "navigate"),
            selector=action.get("selector"),
            value=action.get("value"),
            url=action.get("url"),
            key=action.get("key"),
            name=action.get("name"),
            timeout_ms=action.get("timeout_ms"),
            full_page=action.get("full_page", False),
            direction=action.get("direction"),
            amount=action.get("amount"),
            attribute=action.get("attribute"),
            wait_until=action.get("wait_until"),
            description=action.get("description", ""),
        )

    @staticmethod
    def _extract_assertions(test_case: TestCase, actions: list[dict]) -> list[FlowAssertion]:
        """Look for verify/check steps and create post-flow assertions."""
        assertions: list[FlowAssertion] = []
        for step in test_case.steps:
            lower = step.expected_result.lower()
            if "redirect" in lower or "dashboard" in lower:
                assertions.append(FlowAssertion(type="url_contains", value="/dashboard"))
            elif "error" in lower and "message" in lower:
                assertions.append(FlowAssertion(type="element_visible", selector=".error, .alert, [role='alert']"))
        return assertions

    # ── Fallback (no AI) ───────────────────────────────

    def _fallback_interpret(self, test_case: TestCase) -> list[dict[str, Any]]:
        """Simple heuristic fallback when AI parsing fails."""
        actions: list[dict[str, Any]] = []
        for step in test_case.steps:
            action = self._heuristic_step(step.action, step.test_data, step.expected_result)
            actions.append(action)
        return actions

    def _heuristic_step(
        self, action_text: str, test_data: str | None, expected: str
    ) -> dict[str, Any]:
        lower = action_text.lower()

        if "navigate" in lower or "go to" in lower:
            url = self.config.app_url
            if "login" in lower:
                url += "/login"
            elif "dashboard" in lower:
                url += "/dashboard"
            elif "product" in lower and "add" in lower:
                url += "/add-product"
            return {"action": "navigate", "url": url, "description": action_text}

        if "enter" in lower or "fill" in lower or "type" in lower:
            selector = self._guess_selector(lower)
            return {"action": "fill", "selector": selector, "value": test_data or "", "description": action_text}

        if "click" in lower or "press" in lower or "submit" in lower:
            selector = self._guess_click_selector(lower)
            return {"action": "click", "selector": selector, "description": action_text}

        if "verify" in lower or "check" in lower or "ensure" in lower:
            return {"action": "wait_for", "selector": "body", "timeout_ms": 5000, "description": action_text}

        if "wait" in lower:
            return {"action": "wait_for", "selector": "body", "timeout_ms": 3000, "description": action_text}

        return {"action": "screenshot", "name": "step", "description": action_text}

    @staticmethod
    def _guess_selector(text: str) -> str:
        if "email" in text:
            return "input[name='email'], input[type='email'], #email"
        if "password" in text:
            return "input[name='password'], input[type='password'], #password"
        if "name" in text and "product" not in text:
            return "input[name='name'], #name"
        if "price" in text:
            return "input[name='price'], #price"
        if "stock" in text or "quantity" in text:
            return "input[name='stock'], #stock"
        return "input"

    @staticmethod
    def _guess_click_selector(text: str) -> str:
        if "login" in text:
            return "button[type='submit'], button:has-text('Login')"
        if "add" in text and "product" in text:
            return "button[type='submit'], button:has-text('Add')"
        if "submit" in text:
            return "button[type='submit']"
        return "button"
