"""Tests for Requirements routes (routes.py → /compliance/requirements)."""

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)

REQ_UUID = "rrrrrrrr-1111-2222-3333-rrrrrrrrrrrr"
REG_UUID = "gggggggg-1111-2222-3333-gggggggggggg"
NOW = datetime(2024, 3, 1, 12, 0, 0)


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

def make_requirement(overrides=None):
    r = MagicMock()
    r.id = REQ_UUID
    r.regulation_id = REG_UUID
    r.regulation_code = "GDPR"
    # regulation is accessed as r.regulation.code in list route
    r.regulation = MagicMock()
    r.regulation.code = "GDPR"
    r.article = "Art. 32"
    r.paragraph = "Abs. 1"
    r.title = "Sicherheit der Verarbeitung"
    r.description = "Angemessene technische Massnahmen"
    r.requirement_text = "Implementierung geeigneter TOMs"
    r.breakpilot_interpretation = "Risikobasierter Ansatz"
    r.is_applicable = True
    r.applicability_reason = "Kernanforderung"
    r.priority = 1
    r.implementation_status = "implemented"
    r.implementation_details = None
    r.code_references = None
    r.documentation_links = None
    r.evidence_description = None
    r.evidence_artifacts = None
    r.auditor_notes = None
    r.audit_status = "pending"
    r.last_audit_date = None
    r.last_auditor = None
    r.source_page = None
    r.source_section = None
    r.created_at = NOW
    r.updated_at = NOW
    if overrides:
        for k, v in overrides.items():
            setattr(r, k, v)
    return r


def make_regulation(overrides=None):
    reg = MagicMock()
    reg.id = REG_UUID
    reg.code = "GDPR"
    reg.name = "DSGVO"
    if overrides:
        for k, v in overrides.items():
            setattr(reg, k, v)
    return reg


# ---------------------------------------------------------------------------
# Tests: GET /compliance/requirements (paginated)
# ---------------------------------------------------------------------------

class TestListRequirements:
    """Tests for GET /compliance/requirements."""

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

    def test_list_with_requirement(self):
        req = make_requirement()
        with patch("compliance.api.routes.RequirementRepository") as MockRepo:
            MockRepo.return_value.get_paginated.return_value = ([req], 1)
            response = client.get("/compliance/requirements")
        assert response.status_code == 200
        data = response.json()
        assert data["pagination"]["total"] == 1
        r = data["data"][0]
        assert r["article"] == "Art. 32"
        assert r["title"] == "Sicherheit der Verarbeitung"
        assert r["is_applicable"] is True
        assert r["regulation_code"] == "GDPR"

    def test_list_pagination_params(self):
        with patch("compliance.api.routes.RequirementRepository") as MockRepo:
            MockRepo.return_value.get_paginated.return_value = ([], 0)
            response = client.get("/compliance/requirements", params={"page": 2, "page_size": 25})
        assert response.status_code == 200
        data = response.json()
        assert data["pagination"]["page"] == 2

    def test_list_filter_is_applicable(self):
        req = make_requirement({"is_applicable": True})
        with patch("compliance.api.routes.RequirementRepository") as MockRepo:
            MockRepo.return_value.get_paginated.return_value = ([req], 1)
            response = client.get("/compliance/requirements", params={"is_applicable": "true"})
        assert response.status_code == 200
        assert response.json()["pagination"]["total"] == 1

    def test_list_filter_search(self):
        with patch("compliance.api.routes.RequirementRepository") as MockRepo:
            MockRepo.return_value.get_paginated.return_value = ([], 0)
            response = client.get("/compliance/requirements", params={"search": "TOM"})
        assert response.status_code == 200

    def test_list_multiple_requirements(self):
        r2 = make_requirement()
        r2.id = "rrrrrrrr-2222-2222-2222-rrrrrrrrrrrr"
        r2.article = "Art. 5"
        with patch("compliance.api.routes.RequirementRepository") as MockRepo:
            MockRepo.return_value.get_paginated.return_value = ([make_requirement(), r2], 2)
            response = client.get("/compliance/requirements")
        assert response.status_code == 200
        assert response.json()["pagination"]["total"] == 2


# ---------------------------------------------------------------------------
# Tests: GET /compliance/requirements/{id}
# Note: This route uses db.query() directly, not RequirementRepository.
# ---------------------------------------------------------------------------

class TestGetRequirementById:
    """Tests for GET /compliance/requirements/{id}."""

    def test_get_existing(self):
        req = make_requirement()
        regulation = make_regulation()
        # db.query(RequirementDB).filter(...).first() → req
        mock_db.query.return_value.filter.return_value.first.side_effect = [req, regulation]
        response = client.get(f"/compliance/requirements/{REQ_UUID}")
        assert response.status_code == 200
        data = response.json()
        assert data["id"] == REQ_UUID
        assert data["article"] == "Art. 32"

    def test_get_not_found(self):
        # Reset side_effect then set return_value to None
        mock_db.query.return_value.filter.return_value.first.side_effect = None
        mock_db.query.return_value.filter.return_value.first.return_value = None
        response = client.get("/compliance/requirements/nonexistent-id")
        assert response.status_code == 404

    def test_get_returns_dict_structure(self):
        req = make_requirement()
        regulation = make_regulation()
        mock_db.query.return_value.filter.return_value.first.side_effect = [req, regulation]
        response = client.get(f"/compliance/requirements/{REQ_UUID}")
        assert response.status_code == 200
        data = response.json()
        # dict response has these keys
        assert "id" in data
        assert "article" in data
        assert "implementation_status" in data
        assert "audit_status" in data


# ---------------------------------------------------------------------------
# Tests: GET /compliance/regulations/{code}/requirements
# ---------------------------------------------------------------------------

class TestGetRequirementsByRegulation:
    """Tests for GET /compliance/regulations/{code}/requirements."""

    def test_get_by_regulation_code(self):
        req = make_requirement()
        regulation = make_regulation()
        with patch("compliance.api.routes.RegulationRepository") as MockRegRepo, \
             patch("compliance.api.routes.RequirementRepository") as MockReqRepo:
            MockRegRepo.return_value.get_by_code.return_value = regulation
            MockReqRepo.return_value.get_by_regulation.return_value = [req]
            response = client.get("/compliance/regulations/GDPR/requirements")
        assert response.status_code == 200
        data = response.json()
        assert data["total"] == 1
        assert data["requirements"][0]["article"] == "Art. 32"

    def test_get_by_regulation_not_found(self):
        with patch("compliance.api.routes.RegulationRepository") as MockRegRepo:
            MockRegRepo.return_value.get_by_code.return_value = None
            response = client.get("/compliance/regulations/UNKNOWN/requirements")
        assert response.status_code == 404

    def test_get_by_regulation_empty_requirements(self):
        regulation = make_regulation()
        with patch("compliance.api.routes.RegulationRepository") as MockRegRepo, \
             patch("compliance.api.routes.RequirementRepository") as MockReqRepo:
            MockRegRepo.return_value.get_by_code.return_value = regulation
            MockReqRepo.return_value.get_by_regulation.return_value = []
            response = client.get("/compliance/regulations/GDPR/requirements")
        assert response.status_code == 200
        assert response.json()["total"] == 0


# ---------------------------------------------------------------------------
# Tests: POST /compliance/requirements
# ---------------------------------------------------------------------------

class TestCreateRequirement:
    """Tests for POST /compliance/requirements."""

    def test_create_success(self):
        req = make_requirement()
        regulation = make_regulation()
        with patch("compliance.api.routes.RequirementRepository") as MockRepo, \
             patch("compliance.api.routes.RegulationRepository") as MockRegRepo:
            MockRegRepo.return_value.get_by_id.return_value = regulation
            MockRepo.return_value.create.return_value = req
            response = client.post("/compliance/requirements", json={
                "regulation_id": REG_UUID,
                "article": "Art. 32",
                "title": "Sicherheit der Verarbeitung",
                "is_applicable": True,
                "priority": 1,
            })
        assert response.status_code == 200
        data = response.json()
        assert data["article"] == "Art. 32"
        assert data["regulation_id"] == REG_UUID

    def test_create_regulation_not_found(self):
        with patch("compliance.api.routes.RegulationRepository") as MockRegRepo:
            MockRegRepo.return_value.get_by_id.return_value = None
            response = client.post("/compliance/requirements", json={
                "regulation_id": "nonexistent-reg",
                "article": "Art. 99",
                "title": "Test",
                "is_applicable": True,
                "priority": 2,
            })
        assert response.status_code == 404

    def test_create_missing_required_fields(self):
        """Missing title → 422."""
        response = client.post("/compliance/requirements", json={
            "regulation_id": REG_UUID,
            "article": "Art. 32",
        })
        assert response.status_code == 422


# ---------------------------------------------------------------------------
# Tests: DELETE /compliance/requirements/{id}
# ---------------------------------------------------------------------------

class TestDeleteRequirement:
    """Tests for DELETE /compliance/requirements/{id}."""

    def test_delete_success(self):
        with patch("compliance.api.routes.RequirementRepository") as MockRepo:
            MockRepo.return_value.delete.return_value = True
            response = client.delete(f"/compliance/requirements/{REQ_UUID}")
        assert response.status_code == 200
        assert response.json()["success"] is True

    def test_delete_not_found(self):
        with patch("compliance.api.routes.RequirementRepository") as MockRepo:
            MockRepo.return_value.delete.return_value = False
            response = client.delete("/compliance/requirements/nonexistent")
        assert response.status_code == 404


# ---------------------------------------------------------------------------
# Tests: PUT /compliance/requirements/{id}
# Note: This route uses db.query() directly.
# ---------------------------------------------------------------------------

class TestUpdateRequirement:
    """Tests for PUT /compliance/requirements/{id}."""

    def test_update_success(self):
        req = make_requirement()
        req.implementation_status = "implemented"
        # Reset any previous side_effect before setting return_value
        mock_db.query.return_value.filter.return_value.first.side_effect = None
        mock_db.query.return_value.filter.return_value.first.return_value = req
        response = client.put(
            f"/compliance/requirements/{REQ_UUID}",
            json={"implementation_status": "implemented"},
        )
        assert response.status_code == 200
        data = response.json()
        assert data["success"] is True

    def test_update_not_found(self):
        mock_db.query.return_value.filter.return_value.first.side_effect = None
        mock_db.query.return_value.filter.return_value.first.return_value = None
        response = client.put(
            "/compliance/requirements/nonexistent",
            json={"implementation_status": "implemented"},
        )
        assert response.status_code == 404
