"""
Regression Tests — verify pipeline updates don't break existing controls.

Requires: DATABASE_URL environment variable for DB tests.
Tests without DB run always (structural checks).
"""

import os
import sys
import pytest

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))


# ============================================================================
# Structural tests (no DB needed)
# ============================================================================

class TestOntologyStability:
    """Verify ontology constants haven't accidentally changed."""

    def test_action_types_count(self):
        from services.control_ontology import ACTION_TYPES
        assert len(ACTION_TYPES) >= 26, f"ACTION_TYPES shrank to {len(ACTION_TYPES)}"

    def test_phase_order_count(self):
        from services.control_ontology import PHASE_ORDER
        assert len(PHASE_ORDER) >= 15, f"PHASE_ORDER shrank to {len(PHASE_ORDER)}"

    def test_key_action_types_exist(self):
        from services.control_ontology import ACTION_TYPES
        required = ["define", "implement", "monitor", "test", "prevent", "exclude", "train"]
        for action in required:
            assert action in ACTION_TYPES, f"Missing action_type: {action}"

    def test_classify_action_deterministic(self):
        """Same input must always produce same output."""
        from services.control_ontology import classify_action
        for _ in range(10):
            assert classify_action("implementieren") == "implement"
            assert classify_action("überwachen") == "monitor"
            assert classify_action("verhindern") == "prevent"


class TestDependencyEngineStability:
    """Verify dependency engine core functions haven't changed behavior."""

    def test_evaluate_condition_empty(self):
        from services.dependency_engine import evaluate_condition
        assert evaluate_condition({}, {}) is True

    def test_evaluate_condition_simple(self):
        from services.dependency_engine import evaluate_condition
        cond = {"field": "source.status", "op": "==", "value": "pass"}
        assert evaluate_condition(cond, {"source": {"status": "pass"}}) is True
        assert evaluate_condition(cond, {"source": {"status": "fail"}}) is False

    def test_apply_effect_not_applicable(self):
        from services.dependency_engine import apply_effect
        assert apply_effect({"set_status": "not_applicable"}, "fail") == "not_applicable"

    def test_default_priorities_unchanged(self):
        from services.dependency_engine import DEFAULT_PRIORITIES
        assert DEFAULT_PRIORITIES["supersedes"] == 10
        assert DEFAULT_PRIORITIES["scope_exclusion"] == 20
        assert DEFAULT_PRIORITIES["prerequisite"] == 50
        assert DEFAULT_PRIORITIES["compensating_control"] == 80


class TestDocumentComplianceStability:
    """Verify document compliance rules haven't changed."""

    def test_basic_website_requires_impressum(self):
        from services.document_scope_resolver import resolve_required_documents
        result = resolve_required_documents({"has_website": True})
        docs = result.get("required_documents", [])
        doc_types = [d["document_type"] if isinstance(d, dict) else d.document_type for d in docs]
        assert "impressum" in doc_types
        assert "privacy_policy" in doc_types


# ============================================================================
# DB tests (require DATABASE_URL)
# ============================================================================

@pytest.mark.skipif(
    not os.getenv("DATABASE_URL"),
    reason="DATABASE_URL not set"
)
class TestControlCountStability:
    """Draft count must stay within expected range."""

    def test_draft_count_minimum(self, db_session):
        from sqlalchemy import text
        count = db_session.execute(text(
            "SELECT COUNT(*) FROM compliance.canonical_controls "
            "WHERE release_state = 'draft' AND decomposition_method = 'pass0b'"
        )).scalar()
        assert count > 140000, f"Draft count too low: {count} (expected >140k)"

    def test_draft_count_maximum(self, db_session):
        from sqlalchemy import text
        count = db_session.execute(text(
            "SELECT COUNT(*) FROM compliance.canonical_controls "
            "WHERE release_state = 'draft' AND decomposition_method = 'pass0b'"
        )).scalar()
        assert count < 200000, f"Draft count too high: {count} (expected <200k)"

    def test_no_null_titles(self, db_session):
        from sqlalchemy import text
        null_count = db_session.execute(text(
            "SELECT COUNT(*) FROM compliance.canonical_controls "
            "WHERE release_state = 'draft' AND decomposition_method = 'pass0b' "
            "AND (title IS NULL OR title = '')"
        )).scalar()
        assert null_count == 0, f"{null_count} controls without title"

    def test_assertion_coverage(self, db_session):
        from sqlalchemy import text
        no_assertion = db_session.execute(text(
            "SELECT COUNT(*) FROM compliance.canonical_controls "
            "WHERE release_state = 'draft' AND decomposition_method = 'pass0b' "
            "AND (generation_metadata->>'assertion' IS NULL "
            "     OR generation_metadata->>'assertion' = '')"
        )).scalar()
        total = db_session.execute(text(
            "SELECT COUNT(*) FROM compliance.canonical_controls "
            "WHERE release_state = 'draft' AND decomposition_method = 'pass0b'"
        )).scalar()
        coverage = (total - no_assertion) / max(total, 1) * 100
        assert coverage > 99, f"Assertion coverage only {coverage:.1f}% (expected >99%)"


@pytest.mark.skipif(
    not os.getenv("DATABASE_URL"),
    reason="DATABASE_URL not set"
)
class TestDependencyGraphStability:
    """Dependency graph must be valid and within expected size."""

    def test_dependency_count_minimum(self, db_session):
        from sqlalchemy import text
        count = db_session.execute(text(
            "SELECT COUNT(*) FROM compliance.control_dependencies WHERE is_active = true"
        )).scalar()
        assert count > 10000, f"Too few dependencies: {count} (expected >10k)"

    def test_no_self_dependencies(self, db_session):
        from sqlalchemy import text
        self_deps = db_session.execute(text(
            "SELECT COUNT(*) FROM compliance.control_dependencies "
            "WHERE source_control_id = target_control_id AND is_active = true"
        )).scalar()
        assert self_deps == 0, f"{self_deps} self-referencing dependencies"

    def test_no_orphan_dependencies(self, db_session):
        from sqlalchemy import text
        orphans = db_session.execute(text("""
            SELECT COUNT(*) FROM compliance.control_dependencies d
            WHERE d.is_active = true
              AND NOT EXISTS (
                  SELECT 1 FROM compliance.canonical_controls c
                  WHERE c.id = d.source_control_id AND c.release_state = 'draft'
              )
        """)).scalar()
        # Some orphans OK (pointing to deprecated/duplicate controls)
        assert orphans < 1000, f"Too many orphan dependencies: {orphans}"


@pytest.mark.skipif(
    not os.getenv("DATABASE_URL"),
    reason="DATABASE_URL not set"
)
class TestQualityMetrics:
    """Quality metrics must stay within target ranges."""

    def test_duplicate_rate(self, db_session):
        from sqlalchemy import text
        total = db_session.execute(text(
            "SELECT COUNT(DISTINCT generation_metadata->>'merge_group_hint') "
            "FROM compliance.canonical_controls "
            "WHERE release_state = 'draft' AND decomposition_method = 'pass0b' "
            "AND generation_metadata->>'merge_group_hint' IS NOT NULL"
        )).scalar()
        dups = db_session.execute(text("""
            SELECT COUNT(*) FROM (
                SELECT generation_metadata->>'merge_group_hint', COUNT(*)
                FROM compliance.canonical_controls
                WHERE release_state = 'draft' AND decomposition_method = 'pass0b'
                AND generation_metadata->>'merge_group_hint' IS NOT NULL
                GROUP BY generation_metadata->>'merge_group_hint'
                HAVING COUNT(*) > 1
            ) sub
        """)).scalar()
        rate = dups / max(total, 1) * 100
        assert rate < 5, f"Duplicate merge_key rate {rate:.1f}% exceeds 5% threshold"
