"""
Tests for Contract Builder service.

Tests the functionality of building consolidated contracts from separate artifacts.
"""

import pytest

from pycharter.contract_builder import (
    ContractArtifacts,
    build_contract,
    build_contract_from_store,
)
from pycharter.metadata_store import InMemoryMetadataStore


class TestContractArtifacts:
    """Tests for ContractArtifacts dataclass."""

    def test_contract_artifacts_creation(self):
        """Test creating ContractArtifacts with all fields."""
        artifacts = ContractArtifacts(
            schema={"type": "object", "version": "1.0.0"},
            coercion_rules={"version": "1.0.0", "rules": {"age": "coerce_to_integer"}},
            validation_rules={
                "version": "1.0.0",
                "rules": {"age": {"is_positive": {}}},
            },
            metadata={"version": "1.0.0", "description": "Test"},
            ownership={"owner": "data-team", "team": "engineering"},
            governance_rules={"retention": {"days": 365}},
        )

        assert artifacts.schema["version"] == "1.0.0"
        assert artifacts.coercion_rules["version"] == "1.0.0"
        assert artifacts.validation_rules["version"] == "1.0.0"
        assert artifacts.metadata["version"] == "1.0.0"
        assert artifacts.ownership["owner"] == "data-team"
        assert artifacts.governance_rules["retention"]["days"] == 365

    def test_contract_artifacts_minimal(self):
        """Test creating ContractArtifacts with only required schema."""
        artifacts = ContractArtifacts(schema={"type": "object", "version": "1.0.0"})

        assert artifacts.schema["version"] == "1.0.0"
        assert artifacts.coercion_rules is None
        assert artifacts.validation_rules is None
        assert artifacts.metadata is None
        assert artifacts.ownership is None
        assert artifacts.governance_rules is None


class TestBuildContract:
    """Tests for build_contract function."""

    def test_build_contract_basic(self):
        """Test building a basic contract with schema only."""
        artifacts = ContractArtifacts(
            schema={
                "type": "object",
                "version": "1.0.0",
                "properties": {"name": {"type": "string"}},
            }
        )

        contract = build_contract(artifacts)

        assert "schema" in contract
        assert contract["schema"]["version"] == "1.0.0"
        assert "versions" in contract
        assert contract["versions"]["schema"] == "1.0.0"
        # Rules keys are always present (may be empty)
        assert "coercion_rules" in contract
        assert "validation_rules" in contract

    def test_build_contract_with_coercion_rules(self):
        """Test building contract with coercion rules.

        Note: Schema is RAW (coercion rules are NOT merged into it).
        Rules are stored separately in contract["coercion_rules"].
        """
        artifacts = ContractArtifacts(
            schema={
                "type": "object",
                "version": "1.0.0",
                "properties": {
                    "age": {"type": "integer"},
                },
            },
            coercion_rules={
                "version": "1.0.0",
                "rules": {"age": "coerce_to_integer"},
            },
        )

        contract = build_contract(artifacts)

        # Rules are stored separately, NOT merged into schema
        assert contract["coercion_rules"]["age"] == "coerce_to_integer"
        assert contract["versions"]["coercion_rules"] == "1.0.0"
        # Schema is raw - no coercion field
        assert "coercion" not in contract["schema"]["properties"]["age"]

    def test_build_contract_with_validation_rules(self):
        """Test building contract with validation rules.

        Note: Schema is RAW (validation rules are NOT merged into it).
        Rules are stored separately in contract["validation_rules"].
        """
        artifacts = ContractArtifacts(
            schema={
                "type": "object",
                "version": "1.0.0",
                "properties": {
                    "age": {"type": "integer"},
                },
            },
            validation_rules={
                "version": "1.0.0",
                "rules": {
                    "age": {
                        "is_positive": {"threshold": 0},
                    },
                },
            },
        )

        contract = build_contract(artifacts)

        # Rules are stored separately, NOT merged into schema
        assert "age" in contract["validation_rules"]
        assert contract["validation_rules"]["age"]["is_positive"]["threshold"] == 0
        assert contract["versions"]["validation_rules"] == "1.0.0"
        # Schema is raw - no validations field
        assert "validations" not in contract["schema"]["properties"]["age"]

    def test_build_contract_with_all_components(self):
        """Test building contract with all components."""
        artifacts = ContractArtifacts(
            schema={
                "type": "object",
                "version": "1.0.0",
                "properties": {"name": {"type": "string"}},
            },
            coercion_rules={"version": "1.0.0", "rules": {}},
            validation_rules={"version": "1.0.0", "rules": {}},
            metadata={"version": "1.0.0", "description": "Test contract"},
            ownership={"owner": "data-team", "team": "engineering"},
            governance_rules={"data_retention": {"days": 365}},
        )

        contract = build_contract(artifacts)

        assert "schema" in contract
        assert "metadata" in contract
        assert "ownership" in contract
        assert "governance_rules" in contract
        assert "versions" in contract
        assert contract["versions"]["schema"] == "1.0.0"
        assert contract["versions"]["metadata"] == "1.0.0"
        assert contract["metadata"]["description"] == "Test contract"
        assert contract["ownership"]["owner"] == "data-team"
        assert contract["governance_rules"]["data_retention"]["days"] == 365

    def test_build_contract_without_metadata(self):
        """Test building contract without metadata component."""
        artifacts = ContractArtifacts(
            schema={"type": "object", "version": "1.0.0"},
            metadata={"version": "1.0.0", "description": "Test"},
        )

        contract = build_contract(artifacts, include_metadata=False)

        assert "metadata" not in contract
        assert "versions" in contract  # Versions still tracked

    def test_build_contract_without_ownership(self):
        """Test building contract without ownership."""
        artifacts = ContractArtifacts(
            schema={"type": "object", "version": "1.0.0"},
            ownership={"owner": "team"},
        )

        contract = build_contract(artifacts, include_ownership=False)

        assert "ownership" not in contract

    def test_build_contract_without_governance(self):
        """Test building contract without governance rules."""
        artifacts = ContractArtifacts(
            schema={"type": "object", "version": "1.0.0"},
            governance_rules={"retention": 365},
        )

        contract = build_contract(artifacts, include_governance=False)

        assert "governance_rules" not in contract

    def test_build_contract_missing_schema(self):
        """Test that ValueError is raised when schema is missing."""
        artifacts = ContractArtifacts(schema={})

        with pytest.raises(ValueError, match="Schema is required"):
            build_contract(artifacts)

    def test_build_contract_missing_version(self):
        """Test that ValueError is raised when schema lacks version."""
        artifacts = ContractArtifacts(
            schema={"type": "object", "properties": {"name": {"type": "string"}}}
        )

        with pytest.raises(ValueError, match="Schema must have a 'version' field"):
            build_contract(artifacts)

    def test_build_contract_version_tracking(self):
        """Test that all component versions are tracked."""
        artifacts = ContractArtifacts(
            schema={"type": "object", "version": "2.0.0"},
            coercion_rules={"version": "1.5.0", "rules": {}},
            validation_rules={"version": "1.2.0", "rules": {}},
            metadata={"version": "1.0.0"},
        )

        contract = build_contract(artifacts)

        assert contract["versions"]["schema"] == "2.0.0"
        assert contract["versions"]["coercion_rules"] == "1.5.0"
        assert contract["versions"]["validation_rules"] == "1.2.0"
        assert contract["versions"]["metadata"] == "1.0.0"

    def test_build_contract_metadata_version_removed(self):
        """Test that version is removed from metadata in contract."""
        artifacts = ContractArtifacts(
            schema={"type": "object", "version": "1.0.0"},
            metadata={"version": "1.0.0", "description": "Test"},
        )

        contract = build_contract(artifacts)

        assert "version" not in contract["metadata"]
        assert contract["metadata"]["description"] == "Test"
        assert contract["versions"]["metadata"] == "1.0.0"

    def test_build_contract_coercion_rules_direct_format(self):
        """Test building contract with direct coercion rules format.

        Note: Schema is RAW (coercion rules are NOT merged into it).
        """
        artifacts = ContractArtifacts(
            schema={
                "type": "object",
                "version": "1.0.0",
                "properties": {"age": {"type": "integer"}},
            },
            coercion_rules={"age": "coerce_to_integer"},  # Direct format, no wrapper
        )

        contract = build_contract(artifacts)

        # Rules are stored separately, NOT merged into schema
        assert contract["coercion_rules"]["age"] == "coerce_to_integer"
        # Schema is raw - no coercion field
        assert "coercion" not in contract["schema"]["properties"]["age"]

    def test_build_contract_schema_is_raw(self):
        """Test that built contract schema does NOT contain merged rules.

        This is the key behavior change: build_contract returns RAW schema.
        The Validator class handles merging internally during validation.
        """
        artifacts = ContractArtifacts(
            schema={
                "type": "object",
                "version": "1.0.0",
                "properties": {
                    "name": {"type": "string"},
                    "age": {"type": "integer"},
                },
            },
            coercion_rules={
                "version": "1.0.0",
                "rules": {"age": "coerce_to_integer"},
            },
            validation_rules={
                "version": "1.0.0",
                "rules": {
                    "age": {"is_positive": {"threshold": 0}},
                    "name": {"min_length": {"threshold": 1}},
                },
            },
        )

        contract = build_contract(artifacts)

        # Schema should be RAW - no coercion or validations merged
        for prop_name, prop_def in contract["schema"]["properties"].items():
            assert (
                "coercion" not in prop_def
            ), f"Schema property {prop_name} should not have 'coercion'"
            assert (
                "validations" not in prop_def
            ), f"Schema property {prop_name} should not have 'validations'"

        # Rules should be in separate top-level keys
        assert "coercion_rules" in contract
        assert "validation_rules" in contract
        assert contract["coercion_rules"]["age"] == "coerce_to_integer"
        assert "is_positive" in contract["validation_rules"]["age"]

    def test_build_contract_always_has_rules_keys(self):
        """Test that coercion_rules and validation_rules are always present (may be empty)."""
        artifacts = ContractArtifacts(
            schema={"type": "object", "version": "1.0.0"},
            # No rules provided
        )

        contract = build_contract(artifacts)

        # Rules keys should always be present, even if empty
        assert "coercion_rules" in contract
        assert "validation_rules" in contract
        assert contract["coercion_rules"] == {}
        assert contract["validation_rules"] == {}


class TestBuildContractFromStore:
    """Tests for build_contract_from_store function."""

    def test_build_contract_from_store(self):
        """Test building contract from metadata store."""
        store = InMemoryMetadataStore()
        store.connect()

        contract_name = "test_schema"
        contract_version = "1.0.0"
        schema = {
            "type": "object",
            "version": "1.0.0",
            "properties": {"name": {"type": "string"}},
        }
        store.store_schema(contract_name, contract_version, schema)

        coercion_rules = {"version": "1.0.0", "rules": {"name": "coerce_to_string"}}
        store.store_coercion_rules(contract_name, contract_version, coercion_rules)

        validation_rules = {
            "version": "1.0.0",
            "rules": {"name": {"min_length": {"threshold": 1}}},
        }
        store.store_validation_rules(contract_name, contract_version, validation_rules)

        metadata = {"version": "1.0.0", "description": "Test schema"}
        store.store_metadata(contract_name, contract_version, metadata)

        metadata = {
            "title": "Test Schema",
            "business_owners": ["data-team@example.com"],
        }
        store.store_metadata(contract_name, contract_version, metadata)

        contract = build_contract_from_store(store, contract_name, contract_version)

        assert "schema" in contract
        assert "versions" in contract
        assert contract["versions"]["schema"] == "1.0.0"
        assert "metadata" in contract
        # ownership is top-level only when metadata contains "ownership"; we stored business_owners
        assert "coercion_rules" in contract and "validation_rules" in contract

    def test_build_contract_from_store_schema_not_found(self):
        """Test that ValueError is raised when schema is not found."""
        store = InMemoryMetadataStore()
        store.connect()

        with pytest.raises(ValueError, match="Contract not found"):
            build_contract_from_store(store, "nonexistent_schema", "1.0")

    def test_build_contract_from_store_without_metadata(self):
        """Test building contract without metadata component."""
        store = InMemoryMetadataStore()
        store.connect()

        schema = {"type": "object", "version": "1.0.0"}
        store.store_schema("test", "1.0.0", schema)

        contract = build_contract_from_store(
            store, "test", "1.0.0", include_metadata=False
        )

        assert "metadata" not in contract

    def test_build_contract_from_store_with_version(self):
        """Test building contract with specific version."""
        store = InMemoryMetadataStore()
        store.connect()

        schema_v1 = {"type": "object", "version": "1.0.0"}
        store.store_schema("test", "1.0.0", schema_v1)

        schema_v2 = {"type": "object", "version": "2.0.0"}
        store.store_schema("test", "2.0.0", schema_v2)

        contract = build_contract_from_store(store, "test", "1.0.0")

        assert contract["schema"]["version"] == "1.0.0"

    def test_build_contract_from_store_schema_missing_version(self):
        """Test that ValueError is raised when stored schema lacks version."""
        store = InMemoryMetadataStore()
        store.connect()

        # Store schema with version parameter but without version in schema dict
        # Note: InMemoryMetadataStore may add version, so this test verifies
        # that build_contract_from_store checks for version in the schema dict
        schema = {"type": "object", "properties": {"name": {"type": "string"}}}
        store.store_schema("test", "1.0.0", schema)

        contract = build_contract_from_store(store, "test", "1.0.0")
        # If schema has version, contract should be built successfully
        assert "schema" in contract
        # The actual error case (missing version) would need to be tested
        # by directly calling build_contract with a schema without version
