"""
Tests fuer Template Rule Routes — Migration 147 (compliance_template_rules).

Deckt ab:
- Schema-Validation (Pydantic)
- Recommendation-Service (Condition-Evaluator + Override-Anwendung)
- Rule-Lifecycle (submit → approve → publish, reject)
- Pflichtfelder (source_citation, change_summary)
"""

from datetime import datetime, timezone
from unittest.mock import MagicMock
from uuid import uuid4

import pytest

from compliance.schemas.template_rule import (
    OverrideCreate,
    RecommendationRequest,
    RuleCondition,
    RuleClause,
    RuleVersionCreate,
    SubmitForReviewRequest,
)
from compliance.services.recommendation_service import (
    RecommendationService,
    _eval_clause,
    _eval_condition,
)


# ============================================================================
# Fixtures
# ============================================================================

def make_rule(
    rule_id=None,
    rule_key="test_rule",
    document_type="privacy_policy",
    title="Test Regel",
):
    r = MagicMock()
    r.id = rule_id or uuid4()
    r.rule_key = rule_key
    r.document_type = document_type
    r.title = title
    r.current_version_id = None
    r.created_at = datetime.now(timezone.utc)
    r.updated_at = None
    return r


def make_version(
    rule_id=None,
    classification="required",
    conditions=None,
    is_live=1,
    status="published",
    source_citation="Test Quelle",
):
    v = MagicMock()
    v.id = uuid4()
    v.rule_id = rule_id or uuid4()
    v.version_number = 1
    v.status = status
    v.is_live = is_live
    v.classification = classification
    v.conditions = conditions or {"kind": "all", "clauses": []}
    v.source_citation = source_citation
    v.rationale = None
    v.change_summary = None
    v.created_by = None
    v.submitted_by = None
    v.submitted_at = None
    v.approved_by = None
    v.approved_at = None
    v.published_by = None
    v.published_at = None
    v.rejected_by = None
    v.rejected_at = None
    v.rejection_reason = None
    v.created_at = datetime.now(timezone.utc)
    v.updated_at = None
    return v


def make_override(rule_id, override_classification="optional", reason="Tenant-Anpassung"):
    o = MagicMock()
    o.id = uuid4()
    o.tenant_id = "test-tenant"
    o.rule_id = rule_id
    o.override_classification = override_classification
    o.reason = reason
    o.created_by = "anwalt@test"
    o.created_at = datetime.now(timezone.utc)
    o.updated_at = None
    return o


# ============================================================================
# Schema-Tests
# ============================================================================

class TestSchemas:
    def test_rule_condition_default_kind_all(self):
        c = RuleCondition()
        assert c.kind == "all"
        assert c.clauses == []

    def test_rule_clause_minimal(self):
        c = RuleClause(field="employees", op="gte", value=50)
        assert c.field == "employees"
        assert c.op == "gte"
        assert c.value == 50

    def test_version_create_requires_classification(self):
        with pytest.raises(Exception):
            RuleVersionCreate(  # type: ignore[call-arg]
                rule_id=str(uuid4()),
                conditions=RuleCondition(),
                source_citation="x",
            )

    def test_submit_for_review_includes_change_summary(self):
        req = SubmitForReviewRequest(change_summary="Korrekturen vorgenommen")
        assert req.change_summary == "Korrekturen vorgenommen"

    def test_override_create(self):
        oc = OverrideCreate(
            rule_id=str(uuid4()),
            override_classification="optional",
            reason="Bei meinen Mandanten nicht relevant",
        )
        assert oc.override_classification == "optional"

    def test_recommendation_request_minimal(self):
        req = RecommendationRequest(profile={"employees": 100})
        assert req.profile == {"employees": 100}


# ============================================================================
# Clause-Evaluator
# ============================================================================

class TestClauseEvaluation:
    def test_eq_match(self):
        assert _eval_clause({"field": "x", "op": "eq", "value": "yes"}, {"x": "yes"})

    def test_eq_mismatch(self):
        assert not _eval_clause({"field": "x", "op": "eq", "value": "yes"}, {"x": "no"})

    def test_neq(self):
        assert _eval_clause({"field": "x", "op": "neq", "value": "no"}, {"x": "yes"})

    def test_in_match(self):
        clause = {"field": "x", "op": "in", "value": ["a", "b"]}
        assert _eval_clause(clause, {"x": "a"})
        assert not _eval_clause(clause, {"x": "c"})

    def test_not_in_match(self):
        clause = {"field": "x", "op": "not_in", "value": ["a", "b"]}
        assert _eval_clause(clause, {"x": "c"})
        assert not _eval_clause(clause, {"x": "a"})

    def test_gte_with_string_buckets(self):
        # "50_249" → 50 (untere Schwelle)
        assert _eval_clause(
            {"field": "employees", "op": "gte", "value": 50}, {"employees": "50_249"}
        )
        assert not _eval_clause(
            {"field": "employees", "op": "gte", "value": 100}, {"employees": "50_249"}
        )

    def test_exists(self):
        clause = {"field": "x", "op": "exists"}
        assert _eval_clause(clause, {"x": "value"})
        assert not _eval_clause(clause, {"x": ""})
        assert not _eval_clause(clause, {})

    def test_truthy(self):
        clause = {"field": "x", "op": "truthy"}
        assert _eval_clause(clause, {"x": True})
        assert _eval_clause(clause, {"x": "yes"})
        assert not _eval_clause(clause, {"x": False})
        assert not _eval_clause(clause, {"x": None})


class TestConditionEvaluation:
    def test_all_kind_requires_every_clause(self):
        cond = {"kind": "all", "clauses": [
            {"field": "a", "op": "eq", "value": 1},
            {"field": "b", "op": "eq", "value": 2},
        ]}
        assert _eval_condition(cond, {"a": 1, "b": 2})
        assert not _eval_condition(cond, {"a": 1, "b": 3})

    def test_any_kind_passes_with_one(self):
        cond = {"kind": "any", "clauses": [
            {"field": "a", "op": "eq", "value": 1},
            {"field": "b", "op": "eq", "value": 2},
        ]}
        assert _eval_condition(cond, {"a": 1, "b": 99})
        assert _eval_condition(cond, {"a": 99, "b": 2})
        assert not _eval_condition(cond, {"a": 99, "b": 99})

    def test_empty_clauses_always_true(self):
        cond = {"kind": "all", "clauses": []}
        assert _eval_condition(cond, {})


# ============================================================================
# Recommendation-Service (Tabellengetrieben)
# ============================================================================

@pytest.fixture
def mock_db():
    return MagicMock()


def _setup_db_with_rules(mock_db, rules_and_versions, overrides=None):
    """Setze die DB-Queries so, dass list_versions / list_rules / list_overrides
    die übergebenen Listen liefern."""
    rules = [r for r, _ in rules_and_versions]
    versions = [v for _, v in rules_and_versions]
    overrides = overrides or []

    def query_side_effect(model):
        from compliance.db.template_rule_models import (
            TemplateRuleDB, TemplateRuleVersionDB, TenantRuleOverrideDB,
        )
        q = MagicMock()
        q.filter.return_value = q
        if model == TemplateRuleVersionDB:
            q.all.return_value = versions
        elif model == TemplateRuleDB:
            q.all.return_value = rules
        elif model == TenantRuleOverrideDB:
            q.all.return_value = overrides
        return q

    mock_db.query.side_effect = query_side_effect


class TestRecommendationProfiles:
    """Tabellengetriebene Tests: Profil → erwartete required-Set."""

    def test_ai_startup_2mw_gets_ai_policy_but_not_whistleblower(self, mock_db):
        ai_rule = make_rule(rule_key="ai_required", document_type="ai_usage_policy")
        ai_version = make_version(
            rule_id=ai_rule.id,
            classification="required",
            conditions={"kind": "any", "clauses": [
                {"field": "proc_ai_usage", "op": "not_in", "value": ["none", "no"]},
            ]},
        )
        whistle_rule = make_rule(
            rule_key="whistleblower_required", document_type="whistleblower_policy",
        )
        whistle_version = make_version(
            rule_id=whistle_rule.id,
            classification="required",
            conditions={"kind": "all", "clauses": [
                {"field": "org_employee_count", "op": "in",
                 "value": ["50_249", "250_999", "1000_plus"]},
            ]},
        )
        _setup_db_with_rules(mock_db, [
            (ai_rule, ai_version),
            (whistle_rule, whistle_version),
        ])

        svc = RecommendationService(mock_db)
        result = svc.recommend(RecommendationRequest(profile={
            "proc_ai_usage": "extensive",
            "org_employee_count": "1_9",
        }))
        types = [item.document_type for item in result.required]
        assert "ai_usage_policy" in types
        assert "whistleblower_policy" not in types

    def test_corporate_5000_gets_whistleblower(self, mock_db):
        whistle_rule = make_rule(
            rule_key="whistleblower_required", document_type="whistleblower_policy",
        )
        whistle_version = make_version(
            rule_id=whistle_rule.id,
            classification="required",
            conditions={"kind": "all", "clauses": [
                {"field": "org_employee_count", "op": "in",
                 "value": ["50_249", "250_999", "1000_plus"]},
            ]},
        )
        _setup_db_with_rules(mock_db, [(whistle_rule, whistle_version)])
        svc = RecommendationService(mock_db)
        result = svc.recommend(RecommendationRequest(profile={
            "org_employee_count": "1000_plus",
        }))
        assert any(i.document_type == "whistleblower_policy" for i in result.required)

    def test_always_rules_apply_to_anyone(self, mock_db):
        impressum = make_rule(rule_key="impressum_always", document_type="impressum")
        impressum_v = make_version(
            rule_id=impressum.id,
            classification="required",
            conditions={"kind": "all", "clauses": []},  # always
        )
        _setup_db_with_rules(mock_db, [(impressum, impressum_v)])
        svc = RecommendationService(mock_db)
        result = svc.recommend(RecommendationRequest(profile={}))
        assert any(i.document_type == "impressum" for i in result.required)

    def test_third_country_triggers_tia(self, mock_db):
        tia = make_rule(rule_key="tia_required", document_type="transfer_impact_assessment")
        tia_v = make_version(
            rule_id=tia.id,
            classification="required",
            conditions={"kind": "all", "clauses": [
                {"field": "tech_third_country", "op": "not_in",
                 "value": ["no", "us_dpf_only", "adequate_only"]},
            ]},
        )
        _setup_db_with_rules(mock_db, [(tia, tia_v)])
        svc = RecommendationService(mock_db)
        # Drittland-Transfer ohne DPF/Adäquanz → TIA required
        result = svc.recommend(RecommendationRequest(profile={
            "tech_third_country": "yes_other",
        }))
        assert any(i.document_type == "transfer_impact_assessment" for i in result.required)
        # Mit DPF → keine TIA-Pflicht
        result = svc.recommend(RecommendationRequest(profile={
            "tech_third_country": "us_dpf_only",
        }))
        assert not any(i.document_type == "transfer_impact_assessment" for i in result.required)


class TestTenantOverrides:
    """Override-Anwendung im Recommendation-Service."""

    def test_override_changes_classification(self, mock_db):
        rule = make_rule(rule_key="r1", document_type="some_doc")
        version = make_version(
            rule_id=rule.id, classification="required",
            conditions={"kind": "all", "clauses": []},
        )
        override = make_override(rule.id, override_classification="optional")
        _setup_db_with_rules(mock_db, [(rule, version)], overrides=[override])

        svc = RecommendationService(mock_db)
        result = svc.recommend(
            RecommendationRequest(profile={}),
            tenant_id="test-tenant",
        )
        # Erscheint im optional-Bucket statt required
        assert not any(i.document_type == "some_doc" for i in result.required)
        assert any(i.document_type == "some_doc" for i in result.optional)
        # override_applied Flag gesetzt
        opt_item = [i for i in result.optional if i.document_type == "some_doc"][0]
        assert opt_item.override_applied
        assert opt_item.base_classification == "required"

    def test_null_override_disables_rule(self, mock_db):
        rule = make_rule(rule_key="r1", document_type="some_doc")
        version = make_version(
            rule_id=rule.id, classification="required",
            conditions={"kind": "all", "clauses": []},
        )
        override = make_override(rule.id, override_classification=None)
        _setup_db_with_rules(mock_db, [(rule, version)], overrides=[override])

        svc = RecommendationService(mock_db)
        result = svc.recommend(
            RecommendationRequest(profile={}),
            tenant_id="test-tenant",
        )
        # Regel komplett verschwunden
        all_types = [
            i.document_type for bucket in
            (result.required, result.recommended, result.optional)
            for i in bucket
        ]
        assert "some_doc" not in all_types
