"""Tests for policy template types (Migration 054) — 29 policy templates."""

import pytest
from unittest.mock import MagicMock
from fastapi.testclient import TestClient
from fastapi import FastAPI
from datetime import datetime

from compliance.api.legal_template_routes import (
    VALID_DOCUMENT_TYPES,
    VALID_STATUSES,
    router,
)
from compliance.api.db_utils import row_to_dict as _row_to_dict
from classroom_engine.database import get_db
from compliance.api.tenant_utils import get_tenant_id

DEFAULT_TENANT_ID = "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e"

# =============================================================================
# Test App Setup
# =============================================================================

app = FastAPI()
app.include_router(router)

mock_db = MagicMock()


def override_get_db():
    yield mock_db


def override_tenant():
    return DEFAULT_TENANT_ID


app.dependency_overrides[get_db] = override_get_db
app.dependency_overrides[get_tenant_id] = override_tenant

client = TestClient(app)

# =============================================================================
# Policy type constants (grouped by category)
# =============================================================================

IT_SECURITY_POLICIES = [
    "information_security_policy",
    "access_control_policy",
    "password_policy",
    "encryption_policy",
    "logging_policy",
    "backup_policy",
    "incident_response_policy",
    "change_management_policy",
    "patch_management_policy",
    "asset_management_policy",
    "cloud_security_policy",
    "devsecops_policy",
    "secrets_management_policy",
    "vulnerability_management_policy",
]

DATA_POLICIES = [
    "data_protection_policy",
    "data_classification_policy",
    "data_retention_policy",
    "data_transfer_policy",
    "privacy_incident_policy",
]

PERSONNEL_POLICIES = [
    "employee_security_policy",
    "security_awareness_policy",
    "remote_work_policy",
    "offboarding_policy",
]

VENDOR_POLICIES = [
    "vendor_risk_management_policy",
    "third_party_security_policy",
    "supplier_security_policy",
]

BCM_POLICIES = [
    "business_continuity_policy",
    "disaster_recovery_policy",
    "crisis_management_policy",
]

ALL_POLICY_TYPES = (
    IT_SECURITY_POLICIES
    + DATA_POLICIES
    + PERSONNEL_POLICIES
    + VENDOR_POLICIES
    + BCM_POLICIES
)


# =============================================================================
# Helpers
# =============================================================================


def make_policy_row(doc_type, title="Test Policy", content="# Test", **overrides):
    data = {
        "id": "policy-001",
        "tenant_id": DEFAULT_TENANT_ID,
        "document_type": doc_type,
        "title": title,
        "description": f"Test {doc_type}",
        "content": content,
        "placeholders": ["{{COMPANY_NAME}}", "{{SECURITY_OFFICER}}", "{{VERSION}}", "{{DATE}}"],
        "language": "de",
        "jurisdiction": "DE",
        "status": "published",
        "license_id": "mit",
        "license_name": "MIT License",
        "source_name": "BreakPilot Compliance",
        "attribution_required": False,
        "is_complete_document": True,
        "version": "1.0.0",
        "source_url": None,
        "source_repo": None,
        "source_file_path": None,
        "source_retrieved_at": None,
        "attribution_text": None,
        "inspiration_sources": [],
        "created_at": datetime(2026, 3, 14),
        "updated_at": datetime(2026, 3, 14),
    }
    data.update(overrides)
    row = MagicMock()
    row._mapping = data
    return row


# =============================================================================
# TestPolicyTypeValidation
# =============================================================================


class TestPolicyTypeValidation:
    """Verify all 29 policy types are accepted by VALID_DOCUMENT_TYPES."""

    def test_all_29_policy_types_present(self):
        """All 29 policy types from Migration 054 are in VALID_DOCUMENT_TYPES."""
        for doc_type in ALL_POLICY_TYPES:
            assert doc_type in VALID_DOCUMENT_TYPES, (
                f"Policy type '{doc_type}' missing from VALID_DOCUMENT_TYPES"
            )

    def test_policy_count(self):
        """There are exactly 29 policy template types."""
        assert len(ALL_POLICY_TYPES) == 29

    def test_it_security_policy_count(self):
        """IT Security category has 14 policy types."""
        assert len(IT_SECURITY_POLICIES) == 14

    def test_data_policy_count(self):
        """Data category has 5 policy types."""
        assert len(DATA_POLICIES) == 5

    def test_personnel_policy_count(self):
        """Personnel category has 4 policy types."""
        assert len(PERSONNEL_POLICIES) == 4

    def test_vendor_policy_count(self):
        """Vendor/Supply Chain category has 3 policy types."""
        assert len(VENDOR_POLICIES) == 3

    def test_bcm_policy_count(self):
        """BCM category has 3 policy types."""
        assert len(BCM_POLICIES) == 3

    def test_total_valid_types_count(self):
        """VALID_DOCUMENT_TYPES has 58 entries total (16 original + 7 security + 29 policies + 1 CRA + 1 DSFA + 4 module docs)."""
        assert len(VALID_DOCUMENT_TYPES) == 58

    def test_no_duplicate_policy_types(self):
        """No duplicate entries in the policy type lists."""
        assert len(ALL_POLICY_TYPES) == len(set(ALL_POLICY_TYPES))

    def test_policies_distinct_from_security_concepts(self):
        """Policy types are distinct from security concept types (Migration 051)."""
        security_concepts = [
            "it_security_concept", "data_protection_concept",
            "backup_recovery_concept", "logging_concept",
            "incident_response_plan", "access_control_concept",
            "risk_management_concept",
        ]
        for policy_type in ALL_POLICY_TYPES:
            assert policy_type not in security_concepts, (
                f"Policy type '{policy_type}' clashes with security concept"
            )


# =============================================================================
# TestPolicyTemplateCreation
# =============================================================================


class TestPolicyTemplateCreation:
    """Test creating policy templates via API."""

    def setup_method(self):
        mock_db.reset_mock()

    def test_create_information_security_policy(self):
        """POST /legal-templates accepts information_security_policy."""
        row = make_policy_row("information_security_policy", "Informationssicherheits-Richtlinie")
        mock_db.execute.return_value.fetchone.return_value = row
        mock_db.commit = MagicMock()

        resp = client.post("/legal-templates", json={
            "document_type": "information_security_policy",
            "title": "Informationssicherheits-Richtlinie",
            "content": "# Informationssicherheits-Richtlinie\n\n## 1. Zweck",
        })
        assert resp.status_code == 201

    def test_create_data_protection_policy(self):
        """POST /legal-templates accepts data_protection_policy."""
        row = make_policy_row("data_protection_policy", "Datenschutz-Richtlinie")
        mock_db.execute.return_value.fetchone.return_value = row
        mock_db.commit = MagicMock()

        resp = client.post("/legal-templates", json={
            "document_type": "data_protection_policy",
            "title": "Datenschutz-Richtlinie",
            "content": "# Datenschutz-Richtlinie",
        })
        assert resp.status_code == 201

    def test_create_business_continuity_policy(self):
        """POST /legal-templates accepts business_continuity_policy."""
        row = make_policy_row("business_continuity_policy", "Business-Continuity-Richtlinie")
        mock_db.execute.return_value.fetchone.return_value = row
        mock_db.commit = MagicMock()

        resp = client.post("/legal-templates", json={
            "document_type": "business_continuity_policy",
            "title": "Business-Continuity-Richtlinie",
            "content": "# Business-Continuity-Richtlinie",
        })
        assert resp.status_code == 201

    def test_create_vendor_risk_management_policy(self):
        """POST /legal-templates accepts vendor_risk_management_policy."""
        row = make_policy_row("vendor_risk_management_policy", "Lieferanten-Risikomanagement")
        mock_db.execute.return_value.fetchone.return_value = row
        mock_db.commit = MagicMock()

        resp = client.post("/legal-templates", json={
            "document_type": "vendor_risk_management_policy",
            "title": "Lieferanten-Risikomanagement-Richtlinie",
            "content": "# Lieferanten-Risikomanagement",
        })
        assert resp.status_code == 201

    def test_create_employee_security_policy(self):
        """POST /legal-templates accepts employee_security_policy."""
        row = make_policy_row("employee_security_policy", "Mitarbeiter-Sicherheitsrichtlinie")
        mock_db.execute.return_value.fetchone.return_value = row
        mock_db.commit = MagicMock()

        resp = client.post("/legal-templates", json={
            "document_type": "employee_security_policy",
            "title": "Mitarbeiter-Sicherheitsrichtlinie",
            "content": "# Mitarbeiter-Sicherheitsrichtlinie",
        })
        assert resp.status_code == 201

    @pytest.mark.parametrize("doc_type", ALL_POLICY_TYPES)
    def test_all_policy_types_accepted_by_api(self, doc_type):
        """POST /legal-templates accepts every policy type (parametrized)."""
        row = make_policy_row(doc_type)
        mock_db.execute.return_value.fetchone.return_value = row
        mock_db.commit = MagicMock()

        resp = client.post("/legal-templates", json={
            "document_type": doc_type,
            "title": f"Test {doc_type}",
            "content": f"# {doc_type}",
        })
        assert resp.status_code == 201, (
            f"Expected 201 for {doc_type}, got {resp.status_code}: {resp.text}"
        )


# =============================================================================
# TestPolicyTemplateFilter
# =============================================================================


class TestPolicyTemplateFilter:
    """Verify filtering templates by policy document types."""

    def setup_method(self):
        mock_db.reset_mock()

    @pytest.mark.parametrize("doc_type", [
        "information_security_policy",
        "data_protection_policy",
        "employee_security_policy",
        "vendor_risk_management_policy",
        "business_continuity_policy",
    ])
    def test_filter_by_policy_type(self, doc_type):
        """GET /legal-templates?document_type={policy} returns 200."""
        count_mock = MagicMock()
        count_mock.__getitem__ = lambda self, i: 1
        first_call = MagicMock()
        first_call.fetchone.return_value = count_mock
        second_call = MagicMock()
        second_call.fetchall.return_value = [make_policy_row(doc_type)]
        mock_db.execute.side_effect = [first_call, second_call]

        resp = client.get(f"/legal-templates?document_type={doc_type}")
        assert resp.status_code == 200
        data = resp.json()
        assert "templates" in data


# =============================================================================
# TestPolicyTemplatePlaceholders
# =============================================================================


class TestPolicyTemplatePlaceholders:
    """Verify placeholder structure for policy templates."""

    def test_information_security_policy_placeholders(self):
        """Information security policy has standard placeholders."""
        row = make_policy_row(
            "information_security_policy",
            placeholders=[
                "{{COMPANY_NAME}}", "{{SECURITY_OFFICER}}",
                "{{VERSION}}", "{{DATE}}",
                "{{SCOPE_DESCRIPTION}}", "{{GF_NAME}}",
            ],
        )
        result = _row_to_dict(row)
        assert "{{COMPANY_NAME}}" in result["placeholders"]
        assert "{{SECURITY_OFFICER}}" in result["placeholders"]
        assert "{{GF_NAME}}" in result["placeholders"]

    def test_data_protection_policy_placeholders(self):
        """Data protection policy has DSB and DPO placeholders."""
        row = make_policy_row(
            "data_protection_policy",
            placeholders=[
                "{{COMPANY_NAME}}", "{{DSB_NAME}}",
                "{{DSB_EMAIL}}", "{{VERSION}}", "{{DATE}}",
                "{{GF_NAME}}", "{{SCOPE_DESCRIPTION}}",
            ],
        )
        result = _row_to_dict(row)
        assert "{{DSB_NAME}}" in result["placeholders"]
        assert "{{DSB_EMAIL}}" in result["placeholders"]

    def test_password_policy_placeholders(self):
        """Password policy has complexity-related placeholders."""
        row = make_policy_row(
            "password_policy",
            placeholders=[
                "{{COMPANY_NAME}}", "{{SECURITY_OFFICER}}",
                "{{VERSION}}", "{{DATE}}",
                "{{MIN_PASSWORD_LENGTH}}", "{{MAX_AGE_DAYS}}",
                "{{HISTORY_COUNT}}", "{{GF_NAME}}",
            ],
        )
        result = _row_to_dict(row)
        assert "{{MIN_PASSWORD_LENGTH}}" in result["placeholders"]
        assert "{{MAX_AGE_DAYS}}" in result["placeholders"]

    def test_backup_policy_placeholders(self):
        """Backup policy has retention-related placeholders."""
        row = make_policy_row(
            "backup_policy",
            placeholders=[
                "{{COMPANY_NAME}}", "{{SECURITY_OFFICER}}",
                "{{VERSION}}", "{{DATE}}",
                "{{RPO_HOURS}}", "{{RTO_HOURS}}",
                "{{BACKUP_RETENTION_DAYS}}", "{{GF_NAME}}",
            ],
        )
        result = _row_to_dict(row)
        assert "{{RPO_HOURS}}" in result["placeholders"]
        assert "{{RTO_HOURS}}" in result["placeholders"]


# =============================================================================
# TestPolicyTemplateStructure
# =============================================================================


class TestPolicyTemplateStructure:
    """Validate structural aspects of policy templates."""

    def test_policy_uses_mit_license(self):
        """Policy templates use MIT license."""
        row = make_policy_row("information_security_policy")
        result = _row_to_dict(row)
        assert result["license_id"] == "mit"
        assert result["license_name"] == "MIT License"
        assert result["attribution_required"] is False

    def test_policy_language_de(self):
        """Policy templates default to German language."""
        row = make_policy_row("access_control_policy")
        result = _row_to_dict(row)
        assert result["language"] == "de"
        assert result["jurisdiction"] == "DE"

    def test_policy_is_complete_document(self):
        """Policy templates are complete documents."""
        row = make_policy_row("encryption_policy")
        result = _row_to_dict(row)
        assert result["is_complete_document"] is True

    def test_policy_default_status_published(self):
        """Policy templates default to published status."""
        row = make_policy_row("logging_policy")
        result = _row_to_dict(row)
        assert result["status"] == "published"

    def test_policy_row_to_dict_datetime(self):
        """_row_to_dict converts datetime for policy rows."""
        row = make_policy_row("patch_management_policy")
        result = _row_to_dict(row)
        assert result["created_at"] == "2026-03-14T00:00:00"

    def test_policy_source_name(self):
        """Policy templates have BreakPilot Compliance as source."""
        row = make_policy_row("cloud_security_policy")
        result = _row_to_dict(row)
        assert result["source_name"] == "BreakPilot Compliance"


# =============================================================================
# TestPolicyTemplateRejection
# =============================================================================


class TestPolicyTemplateRejection:
    """Verify invalid policy types are rejected."""

    def setup_method(self):
        mock_db.reset_mock()

    def test_reject_fake_policy_type(self):
        """POST /legal-templates rejects non-existent policy type."""
        resp = client.post("/legal-templates", json={
            "document_type": "fake_security_policy",
            "title": "Fake Policy",
            "content": "# Fake",
        })
        assert resp.status_code == 400
        assert "Invalid document_type" in resp.json()["detail"]

    def test_reject_policy_with_typo(self):
        """POST /legal-templates rejects misspelled policy type."""
        resp = client.post("/legal-templates", json={
            "document_type": "informaton_security_policy",
            "title": "Typo Policy",
            "content": "# Typo",
        })
        assert resp.status_code == 400

    def test_reject_policy_with_invalid_status(self):
        """POST /legal-templates rejects invalid status for policy."""
        resp = client.post("/legal-templates", json={
            "document_type": "password_policy",
            "title": "Password Policy",
            "content": "# Password",
            "status": "active",
        })
        assert resp.status_code == 400


# =============================================================================
# TestPolicySeedScript
# =============================================================================


class TestPolicySeedScript:
    """Validate the seed_policy_templates.py script structure."""

    def test_seed_script_exists(self):
        """Seed script file exists."""
        import os
        path = os.path.join(
            os.path.dirname(__file__), "..", "scripts", "seed_policy_templates.py"
        )
        assert os.path.exists(path), "seed_policy_templates.py not found"

    def test_seed_script_importable(self):
        """Seed script can be parsed without errors."""
        import importlib.util
        import os
        path = os.path.join(
            os.path.dirname(__file__), "..", "scripts", "seed_policy_templates.py"
        )
        spec = importlib.util.spec_from_file_location("seed_policy_templates", path)
        mod = importlib.util.module_from_spec(spec)
        # Don't execute main() — just verify the module parses
        # We do this by checking TEMPLATES is defined
        try:
            spec.loader.exec_module(mod)
        except SystemExit:
            pass  # Script may call sys.exit
        except Exception:
            pass  # Network calls may fail in test env
        # Module should define TEMPLATES list
        assert hasattr(mod, "TEMPLATES"), "TEMPLATES list not found in seed script"
        assert len(mod.TEMPLATES) == 29, f"Expected 29 templates, got {len(mod.TEMPLATES)}"

    def test_seed_templates_have_required_fields(self):
        """Each seed template has document_type, title, description, content, placeholders."""
        import importlib.util
        import os
        path = os.path.join(
            os.path.dirname(__file__), "..", "scripts", "seed_policy_templates.py"
        )
        spec = importlib.util.spec_from_file_location("seed_policy_templates", path)
        mod = importlib.util.module_from_spec(spec)
        try:
            spec.loader.exec_module(mod)
        except Exception:
            pass

        required_fields = {"document_type", "title", "description", "content", "placeholders"}
        for tmpl in mod.TEMPLATES:
            for field in required_fields:
                assert field in tmpl, (
                    f"Template '{tmpl.get('document_type', '?')}' missing field '{field}'"
                )

    def test_seed_templates_use_valid_types(self):
        """All seed template document_types are in VALID_DOCUMENT_TYPES."""
        import importlib.util
        import os
        path = os.path.join(
            os.path.dirname(__file__), "..", "scripts", "seed_policy_templates.py"
        )
        spec = importlib.util.spec_from_file_location("seed_policy_templates", path)
        mod = importlib.util.module_from_spec(spec)
        try:
            spec.loader.exec_module(mod)
        except Exception:
            pass

        for tmpl in mod.TEMPLATES:
            assert tmpl["document_type"] in VALID_DOCUMENT_TYPES, (
                f"Seed type '{tmpl['document_type']}' not in VALID_DOCUMENT_TYPES"
            )

    def test_seed_templates_have_german_content(self):
        """All seed templates have German content (contain common German words)."""
        import importlib.util
        import os
        path = os.path.join(
            os.path.dirname(__file__), "..", "scripts", "seed_policy_templates.py"
        )
        spec = importlib.util.spec_from_file_location("seed_policy_templates", path)
        mod = importlib.util.module_from_spec(spec)
        try:
            spec.loader.exec_module(mod)
        except Exception:
            pass

        german_markers = ["Richtlinie", "Zweck", "Geltungsbereich", "Verantwortlich"]
        for tmpl in mod.TEMPLATES:
            content = tmpl["content"]
            has_german = any(marker in content for marker in german_markers)
            assert has_german, (
                f"Template '{tmpl['document_type']}' content appears not to be German"
            )
