"""Tests for Controls routes (routes.py → /compliance/controls).

Control status enum values: "pass", "partial", "fail", "n/a", "planned"
Control domain enum values: "gov", "priv", "iam", "crypto", "sdlc", "ops", "ai", "cra", "aud"
"""

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

from compliance.api.routes import router as compliance_router
from classroom_engine.database import get_db

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

app = FastAPI()
app.include_router(compliance_router)

mock_db = MagicMock()


def override_get_db():
    yield mock_db


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

CONTROL_UUID = "cccccccc-4444-5555-6666-cccccccccccc"
NOW = datetime(2024, 3, 1, 12, 0, 0)


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

def make_control(overrides=None):
    c = MagicMock()
    c.id = CONTROL_UUID
    c.control_id = "GOV-001"
    # domain and control_type are Enum → need .value
    c.domain = MagicMock()
    c.domain.value = "gov"
    c.control_type = MagicMock()
    c.control_type.value = "preventive"
    c.title = "Datenschutzbeauftragter bestellt"
    c.description = "DSB nach Art. 37 DSGVO"
    c.pass_criteria = "DSB bestellt und dokumentiert"
    c.implementation_guidance = "Ernennung per Urkunde"
    c.code_reference = None
    c.documentation_url = None
    c.is_automated = False
    c.automation_tool = None
    c.automation_config = None
    c.owner = "GF"
    c.review_frequency_days = 365
    # status is Enum → need .value
    c.status = MagicMock()
    c.status.value = "planned"
    c.status_notes = None
    c.status_justification = None
    c.last_reviewed_at = None
    c.next_review_at = None
    c.created_at = NOW
    c.updated_at = NOW
    c.evidence_count = 0
    c.requirement_count = 1
    if overrides:
        for k, v in overrides.items():
            setattr(c, k, v)
    return c


# ---------------------------------------------------------------------------
# Tests: GET /compliance/controls
# ---------------------------------------------------------------------------

class TestListControls:
    """Tests for GET /compliance/controls."""

    def test_list_empty(self):
        with patch("compliance.api.routes.ControlRepository") as MockRepo, \
             patch("compliance.api.routes.EvidenceRepository") as MockEvRepo:
            MockRepo.return_value.get_all.return_value = []
            MockEvRepo.return_value.get_by_control.return_value = []
            response = client.get("/compliance/controls")
        assert response.status_code == 200
        data = response.json()
        assert data["controls"] == []
        assert data["total"] == 0

    def test_list_with_control(self):
        with patch("compliance.api.routes.ControlRepository") as MockRepo, \
             patch("compliance.api.routes.EvidenceRepository") as MockEvRepo:
            MockRepo.return_value.get_all.return_value = [make_control()]
            MockEvRepo.return_value.get_by_control.return_value = []
            response = client.get("/compliance/controls")
        assert response.status_code == 200
        data = response.json()
        assert data["total"] == 1
        c = data["controls"][0]
        assert c["control_id"] == "GOV-001"
        assert c["domain"] == "gov"
        assert c["is_automated"] is False
        assert c["status"] == "planned"

    def test_list_filter_domain(self):
        with patch("compliance.api.routes.ControlRepository") as MockRepo, \
             patch("compliance.api.routes.EvidenceRepository") as MockEvRepo:
            ctrl = make_control()
            ctrl.domain.value = "priv"
            MockRepo.return_value.get_all.return_value = [ctrl]
            MockEvRepo.return_value.get_by_control.return_value = []
            response = client.get("/compliance/controls", params={"domain": "priv"})
        assert response.status_code == 200

    def test_list_filter_status(self):
        with patch("compliance.api.routes.ControlRepository") as MockRepo, \
             patch("compliance.api.routes.EvidenceRepository") as MockEvRepo:
            MockRepo.return_value.get_all.return_value = []
            MockEvRepo.return_value.get_by_control.return_value = []
            response = client.get("/compliance/controls", params={"status": "pass"})
        assert response.status_code == 200

    def test_list_filter_is_automated(self):
        with patch("compliance.api.routes.ControlRepository") as MockRepo, \
             patch("compliance.api.routes.EvidenceRepository") as MockEvRepo:
            ctrl = make_control({"is_automated": True})
            MockRepo.return_value.get_all.return_value = [ctrl]
            MockEvRepo.return_value.get_by_control.return_value = []
            response = client.get("/compliance/controls", params={"is_automated": "true"})
        assert response.status_code == 200

    def test_list_filter_search(self):
        with patch("compliance.api.routes.ControlRepository") as MockRepo, \
             patch("compliance.api.routes.EvidenceRepository") as MockEvRepo:
            MockRepo.return_value.get_all.return_value = []
            MockEvRepo.return_value.get_by_control.return_value = []
            response = client.get("/compliance/controls", params={"search": "Datenschutz"})
        assert response.status_code == 200

    def test_list_multiple(self):
        c2 = make_control()
        c2.id = "dddddddd-1111-2222-3333-dddddddddddd"
        c2.control_id = "PRIV-001"
        c2.domain.value = "priv"
        with patch("compliance.api.routes.ControlRepository") as MockRepo, \
             patch("compliance.api.routes.EvidenceRepository") as MockEvRepo:
            MockRepo.return_value.get_all.return_value = [make_control(), c2]
            MockEvRepo.return_value.get_by_control.return_value = []
            response = client.get("/compliance/controls")
        assert response.status_code == 200
        assert response.json()["total"] == 2


class TestListControlsPaginated:
    """Tests for GET /compliance/controls/paginated."""

    def test_paginated_empty(self):
        with patch("compliance.api.routes.ControlRepository") as MockRepo:
            MockRepo.return_value.get_paginated.return_value = ([], 0)
            response = client.get("/compliance/controls/paginated")
        assert response.status_code == 200
        data = response.json()
        assert data["data"] == []
        assert data["pagination"]["total"] == 0

    def test_paginated_with_data(self):
        ctrl = make_control()
        with patch("compliance.api.routes.ControlRepository") as MockRepo:
            MockRepo.return_value.get_paginated.return_value = ([ctrl], 1)
            response = client.get("/compliance/controls/paginated", params={"page": 1, "page_size": 25})
        assert response.status_code == 200
        data = response.json()
        assert data["pagination"]["total"] == 1
        assert data["data"][0]["control_id"] == "GOV-001"

    def test_paginated_filters(self):
        with patch("compliance.api.routes.ControlRepository") as MockRepo:
            MockRepo.return_value.get_paginated.return_value = ([], 0)
            response = client.get(
                "/compliance/controls/paginated",
                params={"domain": "gov", "status": "pass"},
            )
        assert response.status_code == 200


class TestGetControlById:
    """Tests for GET /compliance/controls/{control_id}."""

    def test_get_existing_by_control_id(self):
        ctrl = make_control()
        with patch("compliance.api.routes.ControlRepository") as MockRepo, \
             patch("compliance.api.routes.EvidenceRepository") as MockEvRepo:
            MockRepo.return_value.get_by_control_id.return_value = ctrl
            MockEvRepo.return_value.get_by_control.return_value = []
            response = client.get("/compliance/controls/GOV-001")
        assert response.status_code == 200
        data = response.json()
        assert data["id"] == CONTROL_UUID
        assert data["control_id"] == "GOV-001"
        assert data["domain"] == "gov"
        assert data["status"] == "planned"

    def test_get_not_found(self):
        with patch("compliance.api.routes.ControlRepository") as MockRepo:
            MockRepo.return_value.get_by_control_id.return_value = None
            response = client.get("/compliance/controls/DOES-NOT-EXIST")
        assert response.status_code == 404

    def test_get_evidence_count_included(self):
        """Evidence count is included in response."""
        ctrl = make_control()
        fake_evidence = [MagicMock(), MagicMock()]
        with patch("compliance.api.routes.ControlRepository") as MockRepo, \
             patch("compliance.api.routes.EvidenceRepository") as MockEvRepo:
            MockRepo.return_value.get_by_control_id.return_value = ctrl
            MockEvRepo.return_value.get_by_control.return_value = fake_evidence
            response = client.get("/compliance/controls/GOV-001")
        assert response.status_code == 200
        assert response.json()["evidence_count"] == 2


class TestUpdateControl:
    """Tests for PUT /compliance/controls/{control_id}."""

    def test_update_title(self):
        updated = make_control()
        updated.title = "Neuer Titel"
        with patch("compliance.api.routes.ControlRepository") as MockRepo:
            MockRepo.return_value.get_by_control_id.return_value = make_control()
            MockRepo.return_value.update.return_value = updated
            response = client.put(
                "/compliance/controls/GOV-001",
                json={"title": "Neuer Titel"},
            )
        assert response.status_code == 200
        assert response.json()["title"] == "Neuer Titel"

    def test_update_not_found(self):
        with patch("compliance.api.routes.ControlRepository") as MockRepo:
            MockRepo.return_value.get_by_control_id.return_value = None
            response = client.put(
                "/compliance/controls/DOES-NOT-EXIST",
                json={"title": "Test"},
            )
        assert response.status_code == 404

    def test_update_status_with_valid_enum(self):
        """Status must be a valid ControlStatusEnum value (planned → in_progress is always allowed)."""
        updated = make_control()
        updated.status.value = "in_progress"
        with patch("compliance.api.routes.ControlRepository") as MockRepo:
            MockRepo.return_value.get_by_control_id.return_value = make_control()
            MockRepo.return_value.update.return_value = updated
            response = client.put(
                "/compliance/controls/GOV-001",
                json={"status": "in_progress"},
            )
        assert response.status_code == 200

    def test_update_status_invalid_enum(self):
        """Invalid status → 400."""
        with patch("compliance.api.routes.ControlRepository") as MockRepo:
            MockRepo.return_value.get_by_control_id.return_value = make_control()
            response = client.put(
                "/compliance/controls/GOV-001",
                json={"status": "invalid_status"},
            )
        assert response.status_code == 400


class TestReviewControl:
    """Tests for PUT /compliance/controls/{control_id}/review."""

    def test_review_success(self):
        reviewed = make_control()
        reviewed.status.value = "pass"
        reviewed.status_notes = "Geprueft OK"
        with patch("compliance.api.routes.ControlRepository") as MockRepo:
            MockRepo.return_value.get_by_control_id.return_value = make_control()
            MockRepo.return_value.mark_reviewed.return_value = reviewed
            response = client.put(
                "/compliance/controls/GOV-001/review",
                json={"status": "pass", "status_notes": "Geprueft OK"},
            )
        assert response.status_code == 200
        data = response.json()
        assert data["status"] == "pass"
        assert data["status_notes"] == "Geprueft OK"

    def test_review_not_found(self):
        with patch("compliance.api.routes.ControlRepository") as MockRepo:
            MockRepo.return_value.get_by_control_id.return_value = None
            response = client.put(
                "/compliance/controls/DOES-NOT-EXIST/review",
                json={"status": "pass"},
            )
        assert response.status_code == 404

    def test_review_missing_status(self):
        """Missing required status field → 422."""
        response = client.put(
            "/compliance/controls/GOV-001/review",
            json={},
        )
        assert response.status_code == 422

    def test_review_invalid_status(self):
        """Invalid status enum → 400."""
        with patch("compliance.api.routes.ControlRepository") as MockRepo:
            MockRepo.return_value.get_by_control_id.return_value = make_control()
            response = client.put(
                "/compliance/controls/GOV-001/review",
                json={"status": "invalid_status"},
            )
        assert response.status_code == 400


class TestGetControlsByDomain:
    """Tests for GET /compliance/controls/by-domain/{domain}."""

    def test_get_by_valid_domain(self):
        ctrl = make_control()
        with patch("compliance.api.routes.ControlRepository") as MockRepo:
            MockRepo.return_value.get_by_domain.return_value = [ctrl]
            response = client.get("/compliance/controls/by-domain/gov")
        assert response.status_code == 200
        data = response.json()
        assert data["total"] == 1
        assert data["controls"][0]["domain"] == "gov"

    def test_get_by_domain_empty_result(self):
        with patch("compliance.api.routes.ControlRepository") as MockRepo:
            MockRepo.return_value.get_by_domain.return_value = []
            response = client.get("/compliance/controls/by-domain/ai")
        assert response.status_code == 200
        assert response.json()["total"] == 0

    def test_get_by_invalid_domain(self):
        """Invalid domain enum → 400."""
        response = client.get("/compliance/controls/by-domain/invalid_domain")
        assert response.status_code == 400
