"""Tests for Evidence Check routes (evidence_check_routes.py)."""

import json
import pytest
from unittest.mock import MagicMock, patch, AsyncMock
from datetime import datetime
from fastapi import FastAPI
from fastapi.testclient import TestClient

from compliance.api.evidence_check_routes import router, VALID_CHECK_TYPES
from classroom_engine.database import get_db
from compliance.api.tenant_utils import get_tenant_id

# ---------------------------------------------------------------------------
# App setup with mocked DB dependency
# ---------------------------------------------------------------------------

app = FastAPI()
app.include_router(router)

DEFAULT_TENANT_ID = "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e"
CHECK_ID = "ffffffff-0001-0001-0001-000000000001"
RESULT_ID = "eeeeeeee-0001-0001-0001-000000000001"
MAPPING_ID = "dddddddd-0001-0001-0001-000000000001"
EVIDENCE_ID = "cccccccc-0001-0001-0001-000000000001"
NOW = datetime(2026, 3, 14, 12, 0, 0)


def override_get_tenant_id():
    return DEFAULT_TENANT_ID


app.dependency_overrides[get_tenant_id] = override_get_tenant_id


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _make_check_row(overrides=None):
    """Create a mock DB row for a check."""
    data = {
        "id": CHECK_ID,
        "tenant_id": DEFAULT_TENANT_ID,
        "project_id": None,
        "check_code": "TLS-SCAN-001",
        "title": "TLS-Scan Hauptwebseite",
        "description": "Prueft TLS",
        "check_type": "tls_scan",
        "target_url": "https://example.com",
        "target_config": {},
        "linked_control_ids": [],
        "frequency": "monthly",
        "last_run_at": None,
        "next_run_at": None,
        "is_active": True,
        "created_at": NOW,
        "updated_at": NOW,
    }
    if overrides:
        data.update(overrides)
    row = MagicMock()
    row._mapping = data
    row.__getitem__ = lambda self, i: list(data.values())[i]
    return row


def _make_result_row(overrides=None):
    """Create a mock DB row for a result."""
    data = {
        "id": RESULT_ID,
        "check_id": CHECK_ID,
        "tenant_id": DEFAULT_TENANT_ID,
        "run_status": "passed",
        "result_data": {"tls_version": "TLSv1.3"},
        "summary": "TLS TLSv1.3",
        "findings_count": 0,
        "critical_findings": 0,
        "evidence_id": None,
        "duration_ms": 150,
        "run_at": NOW,
    }
    if overrides:
        data.update(overrides)
    row = MagicMock()
    row._mapping = data
    row.__getitem__ = lambda self, i: list(data.values())[i]
    return row


def _make_mapping_row(overrides=None):
    data = {
        "id": MAPPING_ID,
        "tenant_id": DEFAULT_TENANT_ID,
        "evidence_id": EVIDENCE_ID,
        "control_code": "TOM-001",
        "mapping_type": "supports",
        "verified_at": None,
        "verified_by": None,
        "notes": "Test mapping",
        "created_at": NOW,
    }
    if overrides:
        data.update(overrides)
    row = MagicMock()
    row._mapping = data
    row.__getitem__ = lambda self, i: list(data.values())[i]
    return row


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------

class TestListChecks:
    def test_list_checks(self):
        mock_db = MagicMock()
        # COUNT query
        count_row = MagicMock()
        count_row.__getitem__ = lambda self, i: 2
        # Data rows
        rows = [_make_check_row(), _make_check_row({"check_code": "TLS-SCAN-002"})]
        mock_db.execute.side_effect = [
            MagicMock(fetchone=MagicMock(return_value=count_row)),
            MagicMock(fetchall=MagicMock(return_value=rows)),
        ]

        app.dependency_overrides[get_db] = lambda: (yield mock_db).__next__() or mock_db

        def override_db():
            yield mock_db

        app.dependency_overrides[get_db] = override_db
        client = TestClient(app)

        resp = client.get("/evidence-checks")
        assert resp.status_code == 200
        data = resp.json()
        assert "checks" in data
        assert len(data["checks"]) == 2


class TestCreateCheck:
    def test_create_check(self):
        mock_db = MagicMock()
        mock_db.execute.return_value.fetchone.return_value = _make_check_row()

        def override_db():
            yield mock_db

        app.dependency_overrides[get_db] = override_db
        client = TestClient(app)

        resp = client.post("/evidence-checks", json={
            "check_code": "TLS-SCAN-001",
            "title": "TLS-Scan Hauptwebseite",
            "check_type": "tls_scan",
            "frequency": "monthly",
        })
        assert resp.status_code == 201
        data = resp.json()
        assert data["check_code"] == "TLS-SCAN-001"

    def test_create_check_invalid_type(self):
        mock_db = MagicMock()

        def override_db():
            yield mock_db

        app.dependency_overrides[get_db] = override_db
        client = TestClient(app)

        resp = client.post("/evidence-checks", json={
            "check_code": "INVALID-001",
            "title": "Invalid Check",
            "check_type": "invalid_type",
        })
        assert resp.status_code == 400
        assert "Ungueltiger check_type" in resp.json()["detail"]


class TestGetSingleCheck:
    def test_get_single_check(self):
        mock_db = MagicMock()
        check_row = _make_check_row()
        result_rows = [_make_result_row()]
        mock_db.execute.side_effect = [
            MagicMock(fetchone=MagicMock(return_value=check_row)),
            MagicMock(fetchall=MagicMock(return_value=result_rows)),
        ]

        def override_db():
            yield mock_db

        app.dependency_overrides[get_db] = override_db
        client = TestClient(app)

        resp = client.get(f"/evidence-checks/{CHECK_ID}")
        assert resp.status_code == 200
        data = resp.json()
        assert data["check_code"] == "TLS-SCAN-001"
        assert "recent_results" in data
        assert len(data["recent_results"]) == 1


class TestUpdateCheck:
    def test_update_check(self):
        mock_db = MagicMock()
        updated_row = _make_check_row({"title": "Updated Title"})
        mock_db.execute.return_value.fetchone.return_value = updated_row

        def override_db():
            yield mock_db

        app.dependency_overrides[get_db] = override_db
        client = TestClient(app)

        resp = client.put(f"/evidence-checks/{CHECK_ID}", json={
            "title": "Updated Title",
        })
        assert resp.status_code == 200
        assert resp.json()["title"] == "Updated Title"


class TestDeleteCheck:
    def test_delete_check(self):
        mock_db = MagicMock()
        mock_db.execute.return_value.rowcount = 1

        def override_db():
            yield mock_db

        app.dependency_overrides[get_db] = override_db
        client = TestClient(app)

        resp = client.delete(f"/evidence-checks/{CHECK_ID}")
        assert resp.status_code == 204


class TestRunCheckTLS:
    def test_run_check_tls(self):
        mock_db = MagicMock()
        check_row = _make_check_row()
        result_insert_row = _make_result_row({"run_status": "running"})
        result_update_row = _make_result_row({"run_status": "passed"})

        mock_db.execute.side_effect = [
            # Load check
            MagicMock(fetchone=MagicMock(return_value=check_row)),
            # Insert running result
            MagicMock(fetchone=MagicMock(return_value=result_insert_row)),
            # Update result
            MagicMock(fetchone=MagicMock(return_value=result_update_row)),
            # Update check timestamps
            MagicMock(),
        ]
        mock_db.commit = MagicMock()

        tls_result = {
            "run_status": "passed",
            "result_data": {"tls_version": "TLSv1.3", "findings": []},
            "summary": "TLS TLSv1.3, Zertifikat gueltig",
            "findings_count": 0,
            "critical_findings": 0,
            "duration_ms": 100,
        }

        def override_db():
            yield mock_db

        app.dependency_overrides[get_db] = override_db
        client = TestClient(app)

        with patch("compliance.api.evidence_check_routes._run_tls_scan", new_callable=AsyncMock, return_value=tls_result):
            resp = client.post(f"/evidence-checks/{CHECK_ID}/run")

        assert resp.status_code == 200
        data = resp.json()
        assert data["run_status"] == "passed"


class TestRunCheckHeader:
    def test_run_check_header(self):
        mock_db = MagicMock()
        check_row = _make_check_row({"check_type": "header_check"})
        result_insert_row = _make_result_row({"run_status": "running"})
        result_update_row = _make_result_row({"run_status": "warning"})

        mock_db.execute.side_effect = [
            MagicMock(fetchone=MagicMock(return_value=check_row)),
            MagicMock(fetchone=MagicMock(return_value=result_insert_row)),
            MagicMock(fetchone=MagicMock(return_value=result_update_row)),
            MagicMock(),
        ]
        mock_db.commit = MagicMock()

        header_result = {
            "run_status": "warning",
            "result_data": {"missing_headers": ["Permissions-Policy"], "findings": []},
            "summary": "5/6 Security-Header vorhanden",
            "findings_count": 1,
            "critical_findings": 0,
            "duration_ms": 200,
        }

        def override_db():
            yield mock_db

        app.dependency_overrides[get_db] = override_db
        client = TestClient(app)

        with patch("compliance.api.evidence_check_routes._run_header_check", new_callable=AsyncMock, return_value=header_result):
            resp = client.post(f"/evidence-checks/{CHECK_ID}/run")

        assert resp.status_code == 200
        data = resp.json()
        assert data["run_status"] == "warning"


class TestSeedChecks:
    def test_seed_checks(self):
        mock_db = MagicMock()
        # Each seed INSERT returns rowcount=1
        mock_result = MagicMock()
        mock_result.rowcount = 1
        mock_db.execute.return_value = mock_result

        def override_db():
            yield mock_db

        app.dependency_overrides[get_db] = override_db
        client = TestClient(app)

        resp = client.post("/evidence-checks/seed")
        assert resp.status_code == 200
        data = resp.json()
        assert data["total_definitions"] == 15
        assert data["seeded"] == 15


class TestMappingsCRUD:
    def test_mappings_crud(self):
        mock_db = MagicMock()

        def override_db():
            yield mock_db

        app.dependency_overrides[get_db] = override_db
        client = TestClient(app)

        # Create mapping
        mapping_row = _make_mapping_row()
        mock_db.execute.return_value.fetchone.return_value = mapping_row

        resp = client.post("/evidence-checks/mappings", json={
            "evidence_id": EVIDENCE_ID,
            "control_code": "TOM-001",
            "mapping_type": "supports",
            "notes": "Test mapping",
        })
        assert resp.status_code == 201
        data = resp.json()
        assert data["control_code"] == "TOM-001"

        # List mappings
        mock_db.execute.return_value.fetchall.return_value = [mapping_row]
        resp = client.get("/evidence-checks/mappings")
        assert resp.status_code == 200
        assert "mappings" in resp.json()

        # Delete mapping
        mock_db.execute.return_value.rowcount = 1
        resp = client.delete(f"/evidence-checks/mappings/{MAPPING_ID}")
        assert resp.status_code == 204
