"""AssertionInterpreterAgent — convert natural language assertions to concrete checks.

Users write:
    assertions:
      - "the success toast message should appear"
      - "price should be greater than 0"

This agent converts them into structured assertions the engine can execute.
"""

from __future__ import annotations

import json
import logging
from typing import Any

from agno.agent import Agent

from antiqa.config.models import resolve_model
from antiqa.config.schema import AntiQAConfig

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

NL_ASSERTION_PROMPT = """\
You are a Playwright test assertion expert. Convert natural language assertions into structured checks.

Available assertion types:
- url_contains: check if the current URL contains a string. Fields: value
- url_equals: check if the URL exactly matches. Fields: value
- element_visible: check if an element is visible. Fields: selector
- element_absent: check if an element is NOT present. Fields: selector
- text_equals: check if an element's text exactly matches. Fields: selector, value
- text_contains: check if an element's text contains a substring. Fields: selector, value

Rules:
1. Choose the most appropriate assertion type.
2. Use robust CSS selectors (prefer ID, name, role, text content over class names).
3. If the assertion mentions "should appear" or "should be visible", use element_visible.
4. If it mentions "should not appear" or "should be gone", use element_absent.
5. If it mentions URL or redirect, use url_contains.
6. If it mentions specific text content, use text_contains or text_equals.
7. For complex assertions that don't map directly, use element_visible with the best selector.

Return ONLY valid JSON — an array of assertion objects:
```json
[
  {"type": "element_visible", "selector": "#success-toast"},
  {"type": "text_contains", "selector": ".price", "value": "$"}
]
```
"""


class AssertionInterpreterAgent:
    """Convert natural language assertions to structured assertion defs."""

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

    def interpret(self, nl_assertion: str) -> list[dict[str, str]]:
        """Convert a single NL assertion into one or more structured assertions."""
        user_prompt = f"Convert this assertion to a structured check:\n\n\"{nl_assertion}\""

        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=[NL_ASSERTION_PROMPT],
                markdown=False,
            )
            response = agent.run(user_prompt)
            content = _clean(response.content or "[]")
            data = json.loads(content)
            if isinstance(data, dict):
                data = [data]
            return data
        except Exception as exc:
            log.debug("NL assertion interpretation failed: %s", exc)
            return self._fallback(nl_assertion)
        finally:
            agno_logger.handlers = prev_handlers
            agno_logger.propagate = prev_propagate

    def interpret_many(self, assertions: list[str]) -> list[dict[str, str]]:
        """Convert multiple NL assertions."""
        results: list[dict[str, str]] = []
        for nl in assertions:
            results.extend(self.interpret(nl))
        return results

    def _fallback(self, text: str) -> list[dict[str, str]]:
        """Heuristic fallback for common assertion patterns."""
        lower = text.lower()

        if "visible" in lower or "appear" in lower or "show" in lower:
            selector = self._guess_selector(lower)
            return [{"type": "element_visible", "selector": selector}]

        if "not" in lower and ("visible" in lower or "appear" in lower or "present" in lower):
            selector = self._guess_selector(lower)
            return [{"type": "element_absent", "selector": selector}]

        if "url" in lower or "redirect" in lower or "navigate" in lower:
            value = self._extract_url_hint(lower)
            return [{"type": "url_contains", "value": value}]

        if "text" in lower or "contain" in lower or "say" in lower:
            return [{"type": "element_visible", "selector": "body"}]

        return [{"type": "element_visible", "selector": "body"}]

    def _guess_selector(self, text: str) -> str:
        if "success" in text or "toast" in text:
            return ".success, .toast, [role='alert'], #success"
        if "error" in text or "alert" in text:
            return ".error, .alert, [role='alert']"
        if "modal" in text or "dialog" in text:
            return "[role='dialog'], .modal"
        if "button" in text:
            return "button"
        return "body"

    def _extract_url_hint(self, text: str) -> str:
        if "dashboard" in text:
            return "/dashboard"
        if "login" in text:
            return "/login"
        if "home" in text:
            return "/"
        return "/"


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()
