from __future__ import annotations

import unittest

from govoplan_core.core.configuration_packages import (
    ConfigurationModuleRequirement,
    ConfigurationPackageEvidence,
    ConfigurationPackageManifest,
    ConfigurationPackageParent,
    ConfigurationPreflightContext,
    ConfigurationProviderExpectation,
    dry_run_configuration_package,
    validate_configuration_package_derivation,
)


def _evidence(*kinds: str) -> tuple[ConfigurationPackageEvidence, ...]:
    return tuple(
        ConfigurationPackageEvidence(
            kind=kind,  # type: ignore[arg-type]
            reference=f"evidence/{kind}.json",
            summary=f"Evidence for {kind}.",
            checksum=f"sha256:{'a' * 64}",
        )
        for kind in kinds
    )


class ConfigurationPackageArchitectureTests(unittest.TestCase):
    def test_legacy_package_defaults_to_product_and_round_trips(self) -> None:
        package = ConfigurationPackageManifest.from_mapping(
            {"package_id": "example", "name": "Example", "version": "1.0.0"}
        )

        self.assertEqual(package.package_class, "product")
        self.assertEqual(
            ConfigurationPackageManifest.from_mapping(package.to_dict()),
            package,
        )

    def test_reference_package_requires_operational_evidence(self) -> None:
        with self.assertRaisesRegex(ValueError, "accessibility"):
            ConfigurationPackageManifest(
                package_id="reference.example",
                name="Example reference",
                version="1.0.0",
                package_class="reference",
                evidence=_evidence("target_test", "recovery"),
            )

    def test_reference_evidence_requires_content_checksums(self) -> None:
        with self.assertRaisesRegex(ValueError, "without checksums"):
            ConfigurationPackageManifest(
                package_id="reference.unbound",
                name="Unbound reference",
                version="1.0.0",
                package_class="reference",
                evidence=tuple(
                    ConfigurationPackageEvidence(
                        kind=kind,  # type: ignore[arg-type]
                        reference=f"evidence/{kind}.json",
                        summary=f"{kind} evidence.",
                        checksum=(
                            None
                            if kind == "target_test"
                            else f"sha256:{'b' * 64}"
                        ),
                    )
                    for kind in (
                        "target_test",
                        "recovery",
                        "security",
                        "operations",
                        "accessibility",
                        "privacy",
                        "documentation",
                    )
                ),
            )

    def test_integration_package_preflight_checks_provider_contract_and_health(self) -> None:
        package = ConfigurationPackageManifest(
            package_id="integration.example",
            name="Example integration",
            version="1.0.0",
            package_class="integration",
            evidence=_evidence(
                "target_test", "recovery", "operations", "documentation"
            ),
            provider_expectations=(
                ConfigurationProviderExpectation(
                    provider_id="connectors.example",
                    authority_mode="external_mirror",
                    minimum_maturity="read",
                ),
            ),
        )

        healthy = dry_run_configuration_package(
            package,
            (),
            ConfigurationPreflightContext(
                external_provider_declarations={
                    "connectors.example": {
                        "authority_modes": ["external_mirror"],
                        "maturity": "synchronize",
                    }
                },
                external_provider_states={
                    "connectors.example": {"health": "healthy"}
                },
            ),
        )
        unhealthy = dry_run_configuration_package(
            package,
            (),
            ConfigurationPreflightContext(
                external_provider_declarations={
                    "connectors.example": {
                        "authority_modes": ["external_authoritative"],
                        "maturity": "discover",
                    }
                },
                external_provider_states={
                    "connectors.example": {"health": "error"}
                },
            ),
        )

        self.assertFalse(
            any(item.severity == "blocker" for item in healthy.diagnostics)
        )
        self.assertEqual(
            {
                "external_provider_authority_mode_unsupported",
                "external_provider_maturity_insufficient",
                "external_provider_unhealthy",
            },
            {item.code for item in unhealthy.diagnostics},
        )

    def test_provider_preflight_selects_the_requested_runtime_binding(self) -> None:
        package = ConfigurationPackageManifest(
            package_id="integration.calendar",
            name="Calendar integration",
            version="1.0.0",
            package_class="integration",
            evidence=_evidence(
                "target_test", "recovery", "operations", "documentation"
            ),
            provider_expectations=(
                ConfigurationProviderExpectation(
                    provider_id="calendar.caldav_sync",
                    authority_mode="governed_sync",
                    minimum_maturity="synchronize",
                    binding_ref="calendar:sync-source:required",
                    freshness_expectation="current",
                    recovery_expectation="ready",
                ),
            ),
        )
        context = ConfigurationPreflightContext(
            external_provider_declarations={
                "calendar.caldav_sync": {
                    "authority_modes": ["governed_sync"],
                    "maturity": "synchronize",
                    "behavior": {},
                }
            },
            external_provider_states={
                "calendar.caldav_sync": {
                    "health": "warning",
                    "bindings": [
                        {
                            "binding_ref": "calendar:sync-source:other",
                            "authority_mode": "governed_sync",
                            "health": "error",
                            "freshness": "stale",
                            "recovery": "attention",
                        },
                        {
                            "binding_ref": "calendar:sync-source:required",
                            "authority_mode": "governed_sync",
                            "health": "healthy",
                            "freshness": "current",
                            "recovery": "ready",
                        },
                    ],
                }
            },
        )

        result = dry_run_configuration_package(package, (), context)

        self.assertFalse(
            any(item.severity == "blocker" for item in result.diagnostics)
        )

    def test_derived_package_may_tighten_but_not_remove_parent_constraints(self) -> None:
        parent = ConfigurationPackageManifest(
            package_id="product.base",
            name="Base",
            version="1.0.0",
            required_modules=(
                ConfigurationModuleRequirement("connectors", "1.0.0"),
            ),
            required_capabilities=("connectors.profiles",),
            provider_expectations=(
                ConfigurationProviderExpectation(
                    provider_id="connectors.example",
                    authority_mode="external_mirror",
                    minimum_maturity="read",
                ),
            ),
        )
        child = ConfigurationPackageManifest(
            package_id="sector.example",
            name="Sector example",
            version="1.0.0",
            package_class="sector",
            parents=(
                ConfigurationPackageParent("product.base", "1.0.0"),
            ),
            evidence=_evidence("documentation"),
        )

        codes = {
            item.code
            for item in validate_configuration_package_derivation(child, parent)
        }
        self.assertEqual(
            {
                "package_parent_module_constraint_loosened",
                "package_parent_capability_constraint_loosened",
                "package_parent_provider_constraint_removed",
            },
            codes,
        )


if __name__ == "__main__":
    unittest.main()
