"""pydantic-ai agents — the only module that touches the LLM API."""

from pydantic import BaseModel
from pydantic_ai import Agent

from noisegate.exceptions import LLMError
from noisegate.models import (
    Check,
    CheckResult,
    ChecksResult,
    Policy,
    Program,
    Report,
)
from noisegate.policy import derive_verdict

FALLBACK_MODEL = "anthropic:claude-sonnet-4-6"


def get_default_model() -> str:
    """Return the configured default model, falling back to the built-in default."""
    from noisegate.config import get_default_model as _cfg_model
    return _cfg_model() or FALLBACK_MODEL


DEFAULT_MODEL = FALLBACK_MODEL  # kept for CLI option default display

_PROVIDER_KEY_MAP = {
    "anthropic": "ANTHROPIC_API_KEY",
    "openai": "OPENAI_API_KEY",
    "google-gla": "GOOGLE_API_KEY",
    "google-vertex": "GOOGLE_API_KEY",
    "mistral": "MISTRAL_API_KEY",
    "ollama": None,
}


def _make_llm_error(e: Exception, model: str) -> LLMError:
    """Return an actionable LLMError, upgrading bare auth errors to friendly messages."""
    raw = str(e)
    low = raw.lower()
    if "api_key" in low or "api key" in low or "environment variable" in low or "apikey" in low:
        provider = model.split(":")[0] if ":" in model else "anthropic"
        env_var = _PROVIDER_KEY_MAP.get(provider, f"{provider.upper()}_API_KEY")
        if env_var:
            hint = (
                f"No API key found for provider '{provider}'.\n\n"
                f"  Run [bold]noisegate llm[/bold] to configure an LLM provider,\n"
                f"  or set the environment variable directly: export {env_var}=<your-key>\n"
            )
        else:
            hint = f"LLM error with provider '{provider}': {raw}"
        return LLMError(hint)
    return LLMError(raw)


_CHECKS_SYSTEM_PROMPT = """\
You are NoiseGate, a bug bounty triage analyst. Evaluate a vulnerability report against \
a program policy by running each specified check.

For every check ID provided, return a CheckResult with:
- check_id: the check's ID, unchanged
- passed: true if the check passes, false if it fails
- reason: one or two sentences citing specific evidence from the report or policy

Check definitions:
- asset_in_scope: Is the reported target within the program's in-scope assets? \
Fail if the asset is explicitly out-of-scope or cannot be matched to any in-scope asset.
- finding_ineligible: Does this finding match any of the program's ineligible/non-qualifying \
finding types? Fail if it does.
- required_sections_present: Are all sections required by the program present in the report? \
Fail if any required section is clearly absent.
- reproduction_quality: Are reproduction steps present and clear enough to reproduce the issue? \
Fail if steps are missing, too vague, or lack a proof of concept where one is reasonably expected.
- impact_quality: Does the report clearly explain the security impact? \
Fail if impact is absent, speculative, or not a real security concern.

Return a result for every check ID listed — no omissions. \
Also provide a `summary`: 1–2 sentences describing the overall assessment.\
"""


class _ChecksOutput(BaseModel):
    results: list[CheckResult]
    summary: str


def run_checks(
    checks: list[Check],
    report: Report,
    program: Program,
    model: str = DEFAULT_MODEL,
) -> ChecksResult:
    """Evaluate a list of checks against a report in a single LLM call."""
    check_list = "\n".join(f"- {c.id}" for c in checks)
    user_message = f"""\
## Program Policy

{program.to_policy_text()}

---

## Vulnerability Report

{report.raw_text}

---

## Checks to Evaluate

{check_list}

Run each check and return your assessment.
"""
    try:
        agent: Agent[None, _ChecksOutput] = Agent(
            model,
            output_type=_ChecksOutput,
            system_prompt=_CHECKS_SYSTEM_PROMPT,
        )
        result = agent.run_sync(user_message)
    except Exception as e:
        raise _make_llm_error(e, model) from e

    usage = result.usage()
    return ChecksResult(
        checks=result.output.results,
        summary=result.output.summary,
        verdict=derive_verdict(result.output.results, Policy(checks=checks)),
        model_used=model,
        prompt_tokens=usage.request_tokens or 0,
        completion_tokens=usage.response_tokens or 0,
    )
