"""Tests for the coding standards checker.

Tests verify that each rule correctly detects violations in known-bad
code and passes known-good code.
"""

import ast
import textwrap
import tempfile
import os

import pytest

from checks.run_checks import (
    AdapterASTChecker,
    run_ast_checks,
    run_grep_check,
    load_rules,
    find_python_files,
    format_github_annotation,
)


def _write_temp_file(code: str) -> str:
    """Write code to a temp file and return its path."""
    fd, path = tempfile.mkstemp(suffix=".py")
    with os.fdopen(fd, "w") as f:
        f.write(textwrap.dedent(code))
    return path


# ── ENTR-001: No print statements ───────────────────────────────────────


class TestNoPrintStatements:

    def test_detects_print(self):
        code = _write_temp_file("""
            def process():
                print("hello")
        """)
        violations = run_ast_checks(code, {"ENTR-001"})
        os.unlink(code)
        assert any(v["rule"] == "ENTR-001" for v in violations)

    def test_passes_logger(self):
        code = _write_temp_file("""
            import logging
            logger = logging.getLogger(__name__)
            def process():
                logger.info("hello")
        """)
        violations = run_ast_checks(code, {"ENTR-001"})
        os.unlink(code)
        assert not any(v["rule"] == "ENTR-001" for v in violations)


# ── ENTR-002: No HTTPException ──────────────────────────────────────────


class TestNoHTTPException:

    def test_detects_http_exception(self):
        code = _write_temp_file("""
            from fastapi import HTTPException
            def process():
                raise HTTPException(status_code=400, detail="bad")
        """)
        violations = run_ast_checks(code, {"ENTR-002"})
        os.unlink(code)
        assert any(v["rule"] == "ENTR-002" for v in violations)

    def test_passes_adapter_response(self):
        code = _write_temp_file("""
            def process():
                return AdapterResponse(status="failed", message="bad input")
        """)
        violations = run_ast_checks(code, {"ENTR-002"})
        os.unlink(code)
        assert not any(v["rule"] == "ENTR-002" for v in violations)


# ── ENTR-003: No broad process try/except ────────────────────────────────


class TestNoBroadProcessTryExcept:

    def test_detects_wrapped_process(self):
        code = _write_temp_file("""
            class MyHandler(BaseHandler):
                async def process(self, payload):
                    try:
                        result = do_something()
                        return result
                    except Exception as e:
                        return AdapterResponse(status="failed")
        """)
        violations = run_ast_checks(code, {"ENTR-003"})
        os.unlink(code)
        assert any(v["rule"] == "ENTR-003" for v in violations)

    def test_passes_targeted_try_except(self):
        code = _write_temp_file("""
            class MyHandler(BaseHandler):
                async def process(self, payload):
                    data = payload.data
                    try:
                        result = call_api(data)
                    except ConnectionError:
                        return AdapterResponse(status="failed")
                    return AdapterResponse(status="success")
        """)
        violations = run_ast_checks(code, {"ENTR-003"})
        os.unlink(code)
        assert not any(v["rule"] == "ENTR-003" for v in violations)

    def test_ignores_non_handler_class(self):
        code = _write_temp_file("""
            class MyService:
                def process(self, data):
                    try:
                        return do_work(data)
                    except Exception:
                        return None
        """)
        violations = run_ast_checks(code, {"ENTR-003"})
        os.unlink(code)
        assert not any(v["rule"] == "ENTR-003" for v in violations)


# ── ENTR-004: No custom flight recorder ──────────────────────────────────


class TestNoFlightRecorder:

    def test_detects_flight_recorder(self):
        rule = {
            "id": "ENTR-004",
            "type": "grep",
            "pattern": "FlightRecorder|flight_recorder|execution_log",
            "description": "No custom flight recorder",
        }
        code = _write_temp_file("""
            flight_recorder = []
            flight_recorder.append("step 1")
        """)
        violations = run_grep_check(rule, [code])
        os.unlink(code)
        assert any(v["rule"] == "ENTR-004" for v in violations)

    def test_passes_clean_code(self):
        rule = {
            "id": "ENTR-004",
            "type": "grep",
            "pattern": "FlightRecorder|flight_recorder|execution_log",
            "description": "No custom flight recorder",
        }
        code = _write_temp_file("""
            logger.info("Processing document")
        """)
        violations = run_grep_check(rule, [code])
        os.unlink(code)
        assert not any(v["rule"] == "ENTR-004" for v in violations)


# ── ENTR-006: Handler logic in process ───────────────────────────────────


class TestHandlerLogicInProcess:

    def test_detects_handle_document(self):
        code = _write_temp_file("""
            class MyHandler(BaseHandler):
                async def process(self, payload):
                    return await self.handle_document(payload)

                async def handle_document(self, payload):
                    return AdapterResponse(status="success")
        """)
        violations = run_ast_checks(code, {"ENTR-006"})
        os.unlink(code)
        assert any(v["rule"] == "ENTR-006" for v in violations)

    def test_passes_utility_helpers(self):
        code = _write_temp_file("""
            class MyHandler(BaseHandler):
                async def process(self, payload):
                    data = self._transform_data(payload.data)
                    return AdapterResponse(status="success")

                def _transform_data(self, data):
                    return data
        """)
        violations = run_ast_checks(code, {"ENTR-006"})
        os.unlink(code)
        assert not any(v["rule"] == "ENTR-006" for v in violations)


# ── ENTR-009: No request state on self ───────────────────────────────────


class TestNoRequestStateOnSelf:

    def test_detects_request_state(self):
        rule = {
            "id": "ENTR-009",
            "type": "grep",
            "pattern": r"self\.current_|self\.request_|self\.payload_",
            "description": "No request state on self",
        }
        code = _write_temp_file("""
            self.current_document = payload.data
        """)
        violations = run_grep_check(rule, [code])
        os.unlink(code)
        assert any(v["rule"] == "ENTR-009" for v in violations)


# ── Rule loading ─────────────────────────────────────────────────────────


class TestRuleLoading:

    def test_load_rules(self):
        rules_path = os.path.join(os.path.dirname(__file__), "..", "checks", "rules.yaml")
        if not os.path.exists(rules_path):
            pytest.skip("rules.yaml not found")
        rules = load_rules(rules_path)
        assert len(rules) >= 10
        assert all("id" in r for r in rules)
        assert all("type" in r for r in rules)

    def test_all_rules_have_fix(self):
        rules_path = os.path.join(os.path.dirname(__file__), "..", "checks", "rules.yaml")
        if not os.path.exists(rules_path):
            pytest.skip("rules.yaml not found")
        rules = load_rules(rules_path)
        for rule in rules:
            assert "fix" in rule, f"Rule {rule['id']} missing 'fix' field"


# ── GitHub annotation formatting ─────────────────────────────────────────


class TestAnnotationFormatting:

    def test_error_annotation(self):
        violation = {"rule": "ENTR-001", "file": "app/handler.py", "line": 42, "message": "print() found"}
        rules_map = {"ENTR-001": {"severity": "error", "fix": "Use logger"}}
        result = format_github_annotation(violation, rules_map)
        assert "::error" in result
        assert "file=app/handler.py" in result
        assert "line=42" in result

    def test_warning_annotation(self):
        violation = {"rule": "ENTR-004", "file": "app/handler.py", "line": 10, "message": "flight recorder"}
        rules_map = {"ENTR-004": {"severity": "warning", "fix": "Remove it"}}
        result = format_github_annotation(violation, rules_map)
        assert "::warning" in result
