"""Unit tests for policy loading and merging functionality."""

# FILE: tests/unit/coverage/test_policy_load_and_merge.py

import tempfile
from pathlib import Path
from unittest.mock import patch

import pytest
import yaml

from libs.policy import PolicyLoader
from libs.schemas import CoveragePolicy, PolicyError

# pylint: disable=wrong-import-position,import-error,too-few-public-methods,global-statement
# pylint: disable=raise-missing-from,unused-argument,too-many-arguments,too-many-positional-arguments
# pylint: disable=too-many-locals,import-outside-toplevel
# mypy: disable-error-code=union-attr
# mypy: disable-error-code=no-untyped-def


class TestPolicyLoader:
    """Test policy loading and merging functionality"""

    @pytest.fixture
    def temp_config_dir(self):
        """Create temporary config directory with test files"""
        with tempfile.TemporaryDirectory() as temp_dir:
            config_dir = Path(temp_dir)

            # Create baseline policy
            baseline_policy = {
                "version": "1.0",
                "jurisdiction": "UK",
                "tax_year": "2024-25",
                "tax_year_boundary": {"start": "2024-04-06", "end": "2025-04-05"},
                "defaults": {"confidence_thresholds": {"ocr": 0.82, "extract": 0.85}},
                "document_kinds": ["P60", "P11D"],
                "triggers": {
                    "SA102": {"any_of": ["exists(IncomeItem[type='Employment'])"]}
                },
                "schedules": {
                    "SA102": {
                        "evidence": [
                            {"id": "P60", "role": "REQUIRED", "boxes": ["SA102_b1"]}
                        ]
                    }
                },
                "status_classifier": {
                    "present_verified": {"min_ocr": 0.82},
                    "present_unverified": {"min_ocr": 0.60},
                    "conflicting": {"conflict_rules": []},
                    "missing": {"default": True},
                },
                "conflict_resolution": {"precedence": ["P60"]},
                "question_templates": {
                    "default": {
                        "text": "Need {evidence}",
                        "why": "Required for {schedule}",
                    }
                },
                "privacy": {"vector_pii_free": True, "redact_patterns": []},
            }

            with open(config_dir / "coverage.yaml", "w") as f:
                yaml.dump(baseline_policy, f)

            # Create jurisdiction overlay
            jurisdiction_overlay = {
                "defaults": {
                    "confidence_thresholds": {"ocr": 0.85}  # Override threshold
                },
                "document_kinds": ["P60", "P11D", "P45"],  # Add P45
            }

            with open(config_dir / "coverage.UK.2024-25.yaml", "w") as f:
                yaml.dump(jurisdiction_overlay, f)

            # Create tenant overlay
            (config_dir / "overrides").mkdir()
            tenant_overlay = {
                "defaults": {"date_tolerance_days": 60}  # Override tolerance
            }

            with open(config_dir / "overrides" / "tenant123.yaml", "w") as f:
                yaml.dump(tenant_overlay, f)

            yield config_dir

    @pytest.fixture
    def policy_loader(self, temp_config_dir):
        """Create policy loader with temp config"""
        return PolicyLoader(str(temp_config_dir))

    def test_load_baseline_policy(self, policy_loader, temp_config_dir):
        """Test loading baseline policy without overlays"""
        policy = policy_loader.load_policy(
            baseline_path=str(temp_config_dir / "coverage.yaml"),
            jurisdiction="US",  # No overlay exists
            tax_year="2023-24",  # No overlay exists
            tenant_id=None,
        )

        assert isinstance(policy, CoveragePolicy)
        assert policy.version == "1.0"
        assert policy.jurisdiction == "UK"
        assert policy.defaults.confidence_thresholds["ocr"] == 0.82
        assert len(policy.document_kinds) == 2

    def test_load_policy_with_jurisdiction_overlay(self, policy_loader):
        """Test loading policy with jurisdiction overlay applied"""
        policy = policy_loader.load_policy(jurisdiction="UK", tax_year="2024-25")

        # Should have jurisdiction overlay applied
        assert policy.defaults.confidence_thresholds["ocr"] == 0.85  # Overridden
        assert len(policy.document_kinds) == 3  # P45 added
        assert "P45" in policy.document_kinds

    def test_load_policy_with_tenant_overlay(self, policy_loader):
        """Test loading policy with tenant overlay applied"""
        policy = policy_loader.load_policy(
            jurisdiction="UK", tax_year="2024-25", tenant_id="tenant123"
        )

        # Should have both jurisdiction and tenant overlays
        assert policy.defaults.confidence_thresholds["ocr"] == 0.85  # From jurisdiction
        assert policy.defaults.date_tolerance_days == 60  # From tenant
        assert len(policy.document_kinds) == 3  # From jurisdiction

    def test_merge_overlays(self, policy_loader):
        """Test overlay merging logic"""
        base = {"a": 1, "b": {"x": 10, "y": 20}, "c": [1, 2, 3]}

        overlay1 = {
            "b": {"x": 15, "z": 30},  # Merge into b, override x, add z
            "d": 4,  # Add new key
        }

        overlay2 = {
            "b": {"y": 25},  # Override y in b
            "c": [4, 5, 6],  # Replace entire list
        }

        result = policy_loader.merge_overlays(base, overlay1, overlay2)

        assert result["a"] == 1
        assert result["b"]["x"] == 15  # From overlay1
        assert result["b"]["y"] == 25  # From overlay2
        assert result["b"]["z"] == 30  # From overlay1
        assert result["c"] == [4, 5, 6]  # From overlay2
        assert result["d"] == 4  # From overlay1

    def test_compile_predicates(self, policy_loader):
        """Test predicate compilation"""
        policy = policy_loader.load_policy()
        compiled = policy_loader.compile_predicates(policy)

        assert compiled.policy == policy
        assert len(compiled.compiled_predicates) > 0
        assert "exists(IncomeItem[type='Employment'])" in compiled.compiled_predicates
        assert compiled.hash is not None
        assert len(compiled.source_files) > 0

    def test_predicate_execution(self, policy_loader):
        """Test that compiled predicates are callable"""
        policy = policy_loader.load_policy()
        compiled = policy_loader.compile_predicates(policy)

        predicate = compiled.compiled_predicates[
            "exists(IncomeItem[type='Employment'])"
        ]

        # Should be callable and return boolean
        result = predicate("T-001", "2024-25")
        assert isinstance(result, bool)

    def test_invalid_yaml_file(self, temp_config_dir):
        """Test handling of invalid YAML file"""
        # Create invalid YAML
        with open(temp_config_dir / "invalid.yaml", "w") as f:
            f.write("invalid: yaml: content: [")

        loader = PolicyLoader(str(temp_config_dir))

        with pytest.raises(PolicyError, match="Invalid YAML"):
            loader._load_yaml_file(str(temp_config_dir / "invalid.yaml"))

    def test_missing_file(self, temp_config_dir):
        """Test handling of missing file"""
        loader = PolicyLoader(str(temp_config_dir))

        with pytest.raises(PolicyError, match="Policy file not found"):
            loader._load_yaml_file(str(temp_config_dir / "missing.yaml"))

    def test_schema_validation_success(self, policy_loader, temp_config_dir):
        """Test successful schema validation"""
        policy_dict = policy_loader._load_yaml_file(
            str(temp_config_dir / "coverage.yaml")
        )

        # Should not raise exception
        policy_loader._validate_policy(policy_dict)

    def test_schema_validation_failure(self, policy_loader):
        """Test schema validation failure"""
        invalid_policy = {
            "version": "1.0",
            # Missing required fields
        }

        with pytest.raises(Exception):  # ValidationError from jsonschema
            policy_loader._validate_policy(invalid_policy)

    def test_business_rules_validation(self, policy_loader, temp_config_dir):
        """Test business rules validation"""
        policy_dict = policy_loader._load_yaml_file(
            str(temp_config_dir / "coverage.yaml")
        )

        result = policy_loader.validate_policy(policy_dict)
        assert result.ok is True
        assert len(result.errors) == 0

    def test_business_rules_validation_failure(self, policy_loader):
        """Test business rules validation with errors"""
        invalid_policy = {
            "version": "1.0",
            "jurisdiction": "UK",
            "tax_year": "2024-25",
            "tax_year_boundary": {"start": "2024-04-06", "end": "2025-04-05"},
            "defaults": {"confidence_thresholds": {"ocr": 0.82}},
            "document_kinds": ["P60"],
            "triggers": {"SA102": {"any_of": ["test"]}},
            "schedules": {
                "SA102": {
                    "evidence": [
                        {
                            "id": "P11D",  # Not in document_kinds
                            "role": "REQUIRED",
                            "boxes": ["SA102_b1"],
                        }
                    ]
                }
            },
            "status_classifier": {
                "present_verified": {"min_ocr": 0.82},
                "present_unverified": {"min_ocr": 0.60},
                "conflicting": {"conflict_rules": []},
                "missing": {"default": True},
            },
            "conflict_resolution": {"precedence": ["P60"]},
            "question_templates": {"default": {"text": "test", "why": "test"}},
        }

        result = policy_loader.validate_policy(invalid_policy)
        assert result.ok is False
        assert len(result.errors) > 0
        assert any("P11D" in error for error in result.errors)

    def test_apply_feature_flags_placeholder(self, policy_loader):
        """Test feature flags application (placeholder)"""
        policy_dict = {"test": "value"}
        result = policy_loader.apply_feature_flags(policy_dict)

        # Currently just returns unchanged
        assert result == policy_dict

    @patch("libs.policy.utils.get_policy_loader")
    def test_convenience_functions(self, mock_get_loader, policy_loader):
        """Test convenience functions"""
        # Create a valid mock policy for testing
        from unittest.mock import MagicMock

        from libs.schemas import (
            ConflictRules,
            CoveragePolicy,
            Defaults,
            Privacy,
            QuestionTemplates,
            StatusClassifier,
            StatusClassifierConfig,
            TaxYearBoundary,
        )

        mock_policy = CoveragePolicy(
            version="1.0",
            jurisdiction="UK",
            tax_year="2024-25",
            tax_year_boundary=TaxYearBoundary(start="2024-04-06", end="2025-04-05"),
            defaults=Defaults(
                confidence_thresholds={"ocr": 0.82, "extract": 0.85},
                date_tolerance_days=30,
            ),
            document_kinds=["P60"],
            status_classifier=StatusClassifierConfig(
                present_verified=StatusClassifier(min_ocr=0.82, min_extract=0.85),
                present_unverified=StatusClassifier(min_ocr=0.60, min_extract=0.70),
                conflicting=StatusClassifier(),
                missing=StatusClassifier(),
            ),
            triggers={},
            conflict_resolution=ConflictRules(precedence=["P60"]),
            question_templates=QuestionTemplates(
                default={"text": "test", "why": "test"}
            ),
            privacy=Privacy(vector_pii_free=True, redact_patterns=[]),
        )

        # Mock the policy loader to return our test policy
        from datetime import datetime

        from libs.schemas import CompiledCoveragePolicy

        mock_compiled_policy = CompiledCoveragePolicy(
            policy=mock_policy,
            compiled_predicates={},
            compiled_at=datetime.now(),
            hash="test-hash",
            source_files=["test.yaml"],
        )

        mock_loader = MagicMock()
        mock_loader.load_policy.return_value = mock_policy
        mock_loader.merge_overlays.side_effect = lambda base, *overlays: {
            **base,
            **{k: v for overlay in overlays for k, v in overlay.items()},
        }
        mock_loader.compile_predicates.return_value = mock_compiled_policy
        mock_get_loader.return_value = mock_loader

        from libs.policy import compile_predicates, load_policy, merge_overlays

        # Test load_policy - use the mock policy directly since we're testing the convenience function
        policy = load_policy()
        assert isinstance(policy, CoveragePolicy)
        assert policy.version == "1.0"

        # Test merge_overlays
        base = {"a": 1}
        overlay = {"b": 2}
        merged = merge_overlays(base, overlay)
        assert merged == {"a": 1, "b": 2}

        # Test compile_predicates
        compiled = compile_predicates(policy)
        assert compiled.policy == policy
