"""Built-in LLM-backed check implementations."""

from pydantic import BaseModel

from noisegate.checks import CheckPlugin, handles
from noisegate.llm import get_default_model, run_structured_llm_call
from noisegate.models import Check, CheckResult, Program, Report


class _CheckOutput(BaseModel):
    passed: bool
    reason: str


def _triage_user_message(report: Report, program: Program) -> str:
    return f"""\
## Program Policy

{program.to_policy_text()}

---

## Vulnerability Report

{report.raw_text}
"""


class BuiltinChecks(CheckPlugin):
    def __init__(self, model: str | None = None) -> None:
        self._model = model
        super().__init__()

    def _get_model(self) -> str:
        return self._model or get_default_model()

    def _run_check(
        self, check: Check, system_prompt: str, report: Report, program: Program,
    ) -> CheckResult:
        output, prompt_tokens, completion_tokens = run_structured_llm_call(
            model=self._get_model(),
            output_type=_CheckOutput,
            system_prompt=system_prompt,
            user_message=_triage_user_message(report, program),
        )
        return CheckResult(
            check_id=check.id,
            passed=output.passed,
            reason=output.reason,
            prompt_tokens=prompt_tokens,
            completion_tokens=completion_tokens,
        )

    @handles("asset_in_scope")
    def _check_asset_in_scope(
        self, check: Check, report: Report, program: Program,
    ) -> CheckResult:
        return self._run_check(check, """\
You are a bug bounty triage analyst. Determine whether the asset targeted in the vulnerability \
report falls within the program's in-scope assets.

Return:
- passed: true if the targeted asset matches or is plausibly covered by an in-scope asset
- reason: one or two sentences citing the specific asset and the relevant scope rule

Apply generous matching: wildcard domains cover subdomains, organization GitHub URLs cover \
their repositories, and assets owned by or closely affiliated with the program's organization \
should be treated as plausibly in scope even if not explicitly listed. Only fail if the asset \
is explicitly listed as out-of-scope or is clearly unrelated to the program's organization \
and assets. Do not fail simply because an asset is not literally enumerated in the scope list.\
""", report, program)

    @handles("finding_ineligible")
    def _check_finding_ineligible(
        self, check: Check, report: Report, program: Program,
    ) -> CheckResult:
        return self._run_check(check, """\
You are a bug bounty triage analyst. Determine whether the class of vulnerability described in \
the report matches any of the program's ineligible finding types.

This check is strictly about the vulnerability class/category (e.g. self-XSS, clickjacking, \
missing headers). Do NOT evaluate other eligibility concerns here — asset scope, software \
version, report format, or missing sections are handled by separate checks.

Return:
- passed: true if the vulnerability class does NOT match any ineligible type
- reason: one or two sentences citing the finding type and the relevant policy rule

Fail only if the vulnerability class clearly and directly matches an ineligible finding type \
listed by the program.\
""", report, program)

    @handles("required_sections_present")
    def _check_required_sections_present(
        self, check: Check, report: Report, program: Program,
    ) -> CheckResult:
        return self._run_check(check, """\
You are a bug bounty triage analyst. Check whether the report includes the core structural \
sections required by the program's policy.

Focus on hard structural requirements that are essential for triage: description of the \
vulnerability, steps to reproduce, and impact/severity. These are the sections without which \
a triager cannot evaluate the report.

Treat auxiliary requirements (screenshots, videos, environment details, CVSS scores, \
disclosure statements, account info, IP addresses, recommended fixes) as nice-to-have — \
their absence alone should not cause a failure.

Return:
- passed: true if the essential structural sections are present (or the program has no requirements)
- reason: one or two sentences identifying which core sections are present or absent

Fail only if a core section (description, reproduction steps, or impact) is clearly missing.\
""", report, program)

    @handles("reproduction_quality")
    def _check_reproduction_quality(
        self, check: Check, report: Report, program: Program,
    ) -> CheckResult:
        return self._run_check(check, """\
You are a bug bounty triage analyst. Assess whether the report's reproduction steps are \
sufficiently clear and complete.

Evaluate only the quality of the reproduction steps themselves. Do NOT consider whether the \
asset is in scope or the finding type is eligible — those are handled by separate checks.

Return:
- passed: true if reproduction steps are present and specific enough to reproduce the issue
- reason: one or two sentences evaluating the quality or absence of reproduction steps

Fail if steps are missing, too vague to follow, or lack a proof of concept where one is \
reasonably expected for the vulnerability type.\
""", report, program)

    @handles("impact_quality")
    def _check_impact_quality(
        self, check: Check, report: Report, program: Program,
    ) -> CheckResult:
        return self._run_check(check, """\
You are a bug bounty triage analyst. Assess whether the report clearly explains the security \
impact of the vulnerability.

Evaluate only the quality of the impact explanation. Do NOT consider whether the asset is in \
scope or the finding type is eligible — those are handled by separate checks.

Return:
- passed: true if the impact section identifies a concrete, realistic security consequence
- reason: one or two sentences evaluating the quality of the impact explanation

Fail if impact is absent, speculative, exaggerated, or does not describe a genuine security risk.\
""", report, program)
