"""Capability adapters for platform-native runtimes that already exist in Cloud.

These adapters are deliberately thin: Feature Pack contracts are the desired-state
source, Cloud owns execution, and the adapter only reports the native runtime surface
and whether the installed pack cohort is healthy. No adapter knows a hosting provider
or returns credentials/secret values.
"""

from __future__ import annotations

from typing import Any, ClassVar
from uuid import UUID

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from src.domain.capabilities.lifecycle import (
    CapabilityIssue,
    CapabilityProvisioner,
    CapabilityState,
    CapabilityStatus,
    IssueSeverity,
    PlanAction,
    ProvisionPlan,
    ProvisionResult,
)
from src.domain.capabilities.runtime import (
    CapabilityRuntimeRequirements,
    RuntimePortRequirement,
    RuntimeRouteRequirement,
)
from src.domain.feature_packs.models import FeaturePack, ProjectFeaturePack
from src.domain.feature_packs.normalize import load_pack_contract


class NativeCapabilityProvisioner(CapabilityProvisioner):
    """Read/reconcile adapter for an already-materialized native Cloud capability."""

    capability: ClassVar[str]
    version: ClassVar[int] = 1
    service_name: ClassVar[str]
    route_prefix: ClassVar[str | None] = None
    ports: ClassVar[tuple[RuntimePortRequirement, ...]] = ()
    usage_markers: ClassVar[tuple[str, ...]] = ()

    def __init__(self, db: AsyncSession):
        self.db = db

    async def declare(self, project: Any, desired_state: dict[str, Any]) -> dict[str, Any]:
        return {"capability": self.capability, "version": self.version, **desired_state}

    async def plan(self, project: Any) -> ProvisionPlan:
        usage = await self._usage(project)
        if not usage["declared"]:
            return ProvisionPlan(self.capability, self.version, action=PlanAction.NOOP)
        if usage["blocked"]:
            return ProvisionPlan(
                self.capability,
                self.version,
                changes=("resolve_native_pack_blockers",),
                blockers=tuple(usage["blocked"]),
                action=PlanAction.BLOCKED,
            )
        return ProvisionPlan(
            self.capability,
            self.version,
            changes=("verify_native_runtime",),
            action=PlanAction.RECONCILE,
        )

    async def apply(self, project: Any, plan: ProvisionPlan) -> ProvisionResult:
        return await self.reconcile(project)

    async def reconcile(self, project: Any) -> ProvisionResult:
        report = await self.status(project)
        return ProvisionResult(
            self.capability,
            self.version,
            report.state,
            blockers=tuple(
                issue.code for issue in report.issues if issue.severity == IssueSeverity.ERROR
            ),
            drift=report.drift,
            details=report.details,
        )

    async def status(self, project: Any) -> CapabilityStatus:
        usage = await self._usage(project)
        if not usage["declared"]:
            return CapabilityStatus(
                capability=self.capability,
                version=self.version,
                state=CapabilityState.DECLARED,
                details={"declared": False, "source_packs": []},
            )

        issues: list[CapabilityIssue] = []
        for blocker in usage["blocked"]:
            issues.append(
                CapabilityIssue(
                    code="NATIVE_PACK_BLOCKED",
                    message=f"Installed Feature Pack is blocked: {blocker}",
                    repairable=True,
                )
            )
        for warning in usage["warnings"]:
            issues.append(
                CapabilityIssue(
                    code=warning,
                    message=(
                        "Optional external configuration is not required for the platform "
                        "capability."
                    ),
                    severity=IssueSeverity.WARNING,
                    repairable=True,
                )
            )
        state = CapabilityState.BLOCKED if usage["blocked"] else CapabilityState.READY
        if not usage["blocked"] and usage["warnings"]:
            state = CapabilityState.READY
        details = {
            "declared": True,
            "source_packs": usage["source_packs"],
            "platform_state": "healthy" if not usage["blocked"] else "blocked",
            "runtime_projection": "required",
            "logical_ports": [port.port_id for port in self.ports],
        }
        return CapabilityStatus(
            capability=self.capability,
            version=self.version,
            state=state,
            drift=bool(usage["blocked"]),
            blocked_reason=usage["blocked"][0] if usage["blocked"] else None,
            details=details,
            runtime_state=CapabilityState.READY
            if not usage["blocked"]
            else CapabilityState.BLOCKED,
            issues=tuple(issues),
        )

    async def runtime_requirements(self, project: Any) -> CapabilityRuntimeRequirements:
        usage = await self._usage(project)
        if not usage["declared"]:
            return CapabilityRuntimeRequirements(self.capability, self.version)
        routes = (
            ()
            if self.route_prefix is None
            else (
                RuntimeRouteRequirement(
                    path_prefix=self.route_prefix,
                    purpose=f"{self.capability}_native_runtime",
                    methods=tuple(sorted({port.method for port in self.ports})),
                ),
            )
        )
        return CapabilityRuntimeRequirements(
            capability_key=self.capability,
            capability_version=self.version,
            routes=routes,
            ports=self.ports,
            platform_services=(self.service_name,),
            health_checks=(f"{self.service_name}.ready",),
        )

    async def _usage(self, project: Any) -> dict[str, Any]:
        project_id = _project_id(project)
        rows = (
            await self.db.execute(
                select(
                    ProjectFeaturePack.pack_slug,
                    ProjectFeaturePack.status,
                    ProjectFeaturePack.blocker,
                )
                .where(ProjectFeaturePack.project_id == project_id)
                .order_by(ProjectFeaturePack.pack_slug)
            )
        ).all()
        if not rows:
            return {"declared": False, "source_packs": [], "blocked": [], "warnings": []}
        slugs = [row[0] for row in rows]
        pack_rows = (
            await self.db.execute(
                select(FeaturePack.slug, FeaturePack.composition).where(FeaturePack.slug.in_(slugs))
            )
        ).all()
        compositions = {row[0]: row[1] or {} for row in pack_rows}
        source_packs: list[str] = []
        blocked: list[str] = []
        warnings: list[str] = []
        for slug, status, blocker in rows:
            contract = load_pack_contract(slug, compositions.get(slug, {}))
            if _contract_uses_capability(contract, self.capability, self.usage_markers):
                source_packs.append(slug)
                if status == "blocked":
                    blocked.append(slug)
                if self.capability == "notifications" and contract.provider_bindings:
                    if any(
                        binding.configuration_required for binding in contract.provider_bindings
                    ):
                        warnings.append("NOTIFICATION_CONNECTOR_CONFIGURATION_REQUIRED")
                if blocker and status in {"blocked", "configuration_required"}:
                    warnings.append("NATIVE_PACK_CONFIGURATION_PENDING")
        return {
            "declared": bool(source_packs),
            "source_packs": sorted(source_packs),
            "blocked": sorted(set(blocked)),
            "warnings": sorted(set(warnings)),
        }


class FilesProvisioner(NativeCapabilityProvisioner):
    capability = "files"
    service_name = "p2hs-files"
    route_prefix = "/api/storage"
    usage_markers = ("files.", "storage.")
    ports = (
        RuntimePortRequirement(
            "storage.list", "files", 1, "GET", "/api/storage/:bucket/files", "session"
        ),
        RuntimePortRequirement(
            "storage.get", "files", 1, "GET", "/api/storage/:bucket/files/:fid", "session"
        ),
        RuntimePortRequirement(
            "storage.presign_upload", "files", 1, "POST", "/api/storage/:bucket/presign", "session"
        ),
        RuntimePortRequirement(
            "storage.confirm",
            "files",
            1,
            "POST",
            "/api/storage/:bucket/files/:fid/confirm",
            "session",
        ),
        RuntimePortRequirement(
            "storage.delete", "files", 1, "DELETE", "/api/storage/:bucket/files/:fid", "session"
        ),
    )


class RecordsProvisioner(NativeCapabilityProvisioner):
    capability = "records"
    service_name = "p2hs-records"
    route_prefix = "/api/records"
    usage_markers = ("record.", "data.entity")
    ports = (
        RuntimePortRequirement(
            "records.query", "records", 1, "POST", "/api/records/:entity/query", "staff"
        ),
        RuntimePortRequirement(
            "records.create", "records", 1, "POST", "/api/records/:entity", "staff"
        ),
        RuntimePortRequirement(
            "records.update", "records", 1, "PATCH", "/api/records/:entity/:id", "staff"
        ),
        RuntimePortRequirement(
            "records.delete", "records", 1, "DELETE", "/api/records/:entity/:id", "staff"
        ),
        RuntimePortRequirement(
            "records.aggregate", "records", 1, "POST", "/api/records/:entity/aggregate", "session"
        ),
    )


class NotificationsProvisioner(NativeCapabilityProvisioner):
    capability = "notifications"
    service_name = "p2hs-notifications"
    usage_markers = ("notification.", "notifications.")


class WebhooksProvisioner(NativeCapabilityProvisioner):
    capability = "webhooks"
    service_name = "p2hs-webhooks"
    usage_markers = ("webhook.", "external.entity_ingest")


class SecretsProvisioner(NativeCapabilityProvisioner):
    capability = "secrets"
    service_name = "p2hs-secrets"
    usage_markers = ("secret.", "secrets.")


def _contract_uses_capability(contract: Any, capability: str, markers: tuple[str, ...]) -> bool:
    provided = [item.id for item in contract.provides]
    required = [item.id for item in (*contract.requires, *contract.optional_requires)]
    if any(item == capability or item.startswith(f"{capability}.") for item in provided + required):
        return True
    if capability == "records" and contract.entities:
        return True
    if capability == "notifications" and any(
        event.recipient is not None for event in contract.events
    ):
        return True
    if capability == "webhooks" and contract.webhook_bindings:
        return True
    if capability == "secrets" and any(
        binding.configuration_required for binding in contract.provider_bindings
    ):
        return True
    return any(item.startswith(markers) for item in provided + required)


def _project_id(project: Any) -> UUID:
    value = project if isinstance(project, (UUID, str)) else project.id
    return value if isinstance(value, UUID) else UUID(str(value))
