"""Tests for the checks registry (checks.py)."""

from unittest.mock import patch

import pytest

from noisegate.checks import (
    ASSET_IN_SCOPE,
    FINDING_INELIGIBLE,
    IMPACT_QUALITY,
    REPRODUCTION_QUALITY,
    REQUIRED_SECTIONS_PRESENT,
    BuiltinChecks,
    _BUILTIN_CHECK_IDS,
    create_plugin_manager,
)
from noisegate.models import Check, CheckResult, ChecksResult, Program, Report, Verdict


def _check(id: str) -> Check:
    return Check(id=id)


def _report(text: str = "Some report text.") -> Report:
    return Report(raw_text=text, source_path="test.md", char_count=len(text))


def _program() -> Program:
    return Program(name="Test", platform="HackerOne", source="https://example.com")


def _all_pass() -> ChecksResult:
    return ChecksResult(
        checks=[CheckResult(check_id=cid, passed=True, reason="ok") for cid in _BUILTIN_CHECK_IDS],
        summary="All checks passed.",
        verdict=Verdict.PASS,
        model_used="test",
        prompt_tokens=0,
        completion_tokens=0,
    )


# ---------------------------------------------------------------------------
# LLM dispatch
# ---------------------------------------------------------------------------

def test_builtin_check_calls_llm_and_returns_result():
    expected = CheckResult(check_id=ASSET_IN_SCOPE, passed=True, reason="In scope.")
    mock_output = ChecksResult(checks=[expected], summary="ok", verdict=Verdict.PASS, model_used="test", prompt_tokens=0, completion_tokens=0)
    with patch("noisegate.llm.run_checks", return_value=mock_output) as mock_run:
        plugin = BuiltinChecks()
        result = plugin.run_check(
            check=_check(ASSET_IN_SCOPE), report=_report(), program=_program()
        )
    assert result == expected
    mock_run.assert_called_once()


def test_llm_called_with_all_builtin_check_ids():
    """The batch call should include all 5 built-in check IDs."""
    with patch("noisegate.llm.run_checks", return_value=_all_pass()) as mock_run:
        plugin = BuiltinChecks()
        plugin.run_check(check=_check(ASSET_IN_SCOPE), report=_report(), program=_program())

    passed_check_ids = {c.id for c in mock_run.call_args.args[0]}
    assert passed_check_ids == _BUILTIN_CHECK_IDS


def test_unknown_check_id_returns_none_without_calling_llm():
    with patch("noisegate.llm.run_checks") as mock_run:
        plugin = BuiltinChecks()
        result = plugin.run_check(
            check=_check("my_org.custom_check"), report=_report(), program=_program()
        )
    assert result is None
    mock_run.assert_not_called()


# ---------------------------------------------------------------------------
# Caching — one LLM call for multiple checks on the same (report, program)
# ---------------------------------------------------------------------------

def test_multiple_checks_on_same_report_make_one_llm_call():
    with patch("noisegate.llm.run_checks", return_value=_all_pass()) as mock_run:
        plugin = BuiltinChecks()
        report, program = _report(), _program()
        for cid in _BUILTIN_CHECK_IDS:
            plugin.run_check(check=_check(cid), report=report, program=program)
    mock_run.assert_called_once()


def test_different_reports_make_separate_llm_calls():
    with patch("noisegate.llm.run_checks", return_value=_all_pass()) as mock_run:
        plugin = BuiltinChecks()
        program = _program()
        plugin.run_check(check=_check(ASSET_IN_SCOPE), report=_report("Report A"), program=program)
        plugin.run_check(check=_check(ASSET_IN_SCOPE), report=_report("Report B"), program=program)
    assert mock_run.call_count == 2


# ---------------------------------------------------------------------------
# Model forwarding
# ---------------------------------------------------------------------------

def test_custom_model_forwarded_to_llm():
    with patch("noisegate.llm.run_checks", return_value=_all_pass()) as mock_run:
        plugin = BuiltinChecks(model="openai:gpt-4o")
        plugin.run_check(check=_check(ASSET_IN_SCOPE), report=_report(), program=_program())
    # run_checks is called with positional args: (checks, report, program, model)
    assert mock_run.call_args.args[3] == "openai:gpt-4o"


# ---------------------------------------------------------------------------
# Plugin manager smoke test
# ---------------------------------------------------------------------------

def test_create_plugin_manager_hook_callable():
    with patch("noisegate.llm.run_checks", return_value=_all_pass()):
        pm, _ = create_plugin_manager()
        result = pm.hook.run_check(
            check=_check(REPRODUCTION_QUALITY),
            report=_report(),
            program=_program(),
        )
    assert result is not None
    assert result.passed
