from __future__ import annotations

from collections.abc import Mapping
from dataclasses import dataclass, field
from datetime import datetime
from typing import Literal
from urllib.parse import urlsplit


IntegrationMaturity = Literal[
    "discover",
    "link",
    "search",
    "read",
    "publish",
    "synchronize",
    "migrate",
    "replace",
]
SourceAuthorityMode = Literal[
    "native_authoritative",
    "external_authoritative",
    "external_mirror",
    "governed_sync",
    "governance_overlay",
    "linked_reference",
]

INTEGRATION_MATURITY_ORDER: tuple[IntegrationMaturity, ...] = (
    "discover",
    "link",
    "search",
    "read",
    "publish",
    "synchronize",
    "migrate",
    "replace",
)
SOURCE_AUTHORITY_MODES: tuple[SourceAuthorityMode, ...] = (
    "native_authoritative",
    "external_authoritative",
    "external_mirror",
    "governed_sync",
    "governance_overlay",
    "linked_reference",
)


class ExternalReferenceValidationError(ValueError):
    pass


@dataclass(frozen=True, slots=True)
class ExternalObjectReference:
    """Stable identity and provenance for an object owned by another system."""

    system: str
    object_type: str
    object_id: str
    maturity: IntegrationMaturity = "link"
    authority_mode: SourceAuthorityMode = "linked_reference"
    connector_id: str | None = None
    canonical_url: str | None = None
    version: str | None = None
    etag: str | None = None
    observed_at: datetime | None = None
    metadata: Mapping[str, object] = field(default_factory=dict)

    def __post_init__(self) -> None:
        for field_name in ("system", "object_type", "object_id"):
            value = str(getattr(self, field_name) or "").strip()
            if not value:
                raise ExternalReferenceValidationError(
                    f"External reference {field_name} is required."
                )
            if len(value) > 255:
                raise ExternalReferenceValidationError(
                    f"External reference {field_name} is limited to 255 characters."
                )
            object.__setattr__(self, field_name, value)
        if self.maturity not in INTEGRATION_MATURITY_ORDER:
            raise ExternalReferenceValidationError(
                f"Unsupported integration maturity: {self.maturity!r}."
            )
        if self.authority_mode not in SOURCE_AUTHORITY_MODES:
            raise ExternalReferenceValidationError(
                f"Unsupported source-authority mode: {self.authority_mode!r}."
            )
        if (
            self.authority_mode == "external_mirror"
            and not self.supports("read")
        ):
            raise ExternalReferenceValidationError(
                "External-mirror references require read maturity or higher."
            )
        if (
            self.authority_mode == "governed_sync"
            and not self.supports("synchronize")
        ):
            raise ExternalReferenceValidationError(
                "Governed-sync references require synchronize maturity or higher."
            )
        if self.connector_id is not None:
            connector_id = self.connector_id.strip()
            if not connector_id:
                raise ExternalReferenceValidationError(
                    "External reference connector_id cannot be blank."
                )
            object.__setattr__(self, "connector_id", connector_id)
        if self.canonical_url is not None:
            object.__setattr__(
                self,
                "canonical_url",
                _validated_reference_url(self.canonical_url),
            )

    @property
    def identity_key(self) -> str:
        return f"{self.system}:{self.object_type}:{self.object_id}"

    def supports(self, maturity: IntegrationMaturity) -> bool:
        return integration_maturity_rank(self.maturity) >= integration_maturity_rank(
            maturity
        )

    def to_dict(self) -> dict[str, object]:
        return {
            "system": self.system,
            "object_type": self.object_type,
            "object_id": self.object_id,
            "maturity": self.maturity,
            "authority_mode": self.authority_mode,
            "connector_id": self.connector_id,
            "canonical_url": self.canonical_url,
            "version": self.version,
            "etag": self.etag,
            "observed_at": (
                self.observed_at.isoformat() if self.observed_at is not None else None
            ),
            "metadata": dict(self.metadata),
        }


def integration_maturity_rank(maturity: IntegrationMaturity) -> int:
    try:
        return INTEGRATION_MATURITY_ORDER.index(maturity)
    except ValueError as exc:
        raise ExternalReferenceValidationError(
            f"Unsupported integration maturity: {maturity!r}."
        ) from exc


def _validated_reference_url(value: str) -> str:
    normalized = value.strip()
    parsed = urlsplit(normalized)
    if parsed.scheme not in {"http", "https"} or not parsed.hostname:
        raise ExternalReferenceValidationError(
            "External reference URLs must use HTTP or HTTPS."
        )
    if parsed.username is not None or parsed.password is not None:
        raise ExternalReferenceValidationError(
            "External reference URLs must not contain credentials."
        )
    return normalized


__all__ = [
    "ExternalObjectReference",
    "ExternalReferenceValidationError",
    "INTEGRATION_MATURITY_ORDER",
    "IntegrationMaturity",
    "SOURCE_AUTHORITY_MODES",
    "SourceAuthorityMode",
    "integration_maturity_rank",
]
