"""Shared primitives for the Mission Core Plugin SDK v0alpha2 contracts."""

from __future__ import annotations

from collections.abc import Mapping
from typing import Annotated, Any, Literal, Never, Self

from pydantic import AfterValidator, BaseModel, ConfigDict, Field, JsonValue, model_validator

ApiVersion = Literal["missioncore.nodedc/plugin-sdk/v0alpha2"]
API_VERSION: ApiVersion = "missioncore.nodedc/plugin-sdk/v0alpha2"


def _reject_blank(value: str) -> str:
    if not value.strip():
        raise ValueError("must contain a non-whitespace character")
    return value


Identifier = Annotated[
    str,
    Field(min_length=1, max_length=192, pattern=r"^[A-Za-z0-9][A-Za-z0-9._:/-]*$"),
    AfterValidator(_reject_blank),
]
ShortText = Annotated[
    str,
    Field(min_length=1, max_length=256),
    AfterValidator(_reject_blank),
]
LongText = Annotated[
    str,
    Field(min_length=1, max_length=4096),
    AfterValidator(_reject_blank),
]
Sha256Digest = Annotated[str, Field(pattern=r"^sha256:[0-9a-f]{64}$")]
JsonObject = dict[str, JsonValue]


class _FrozenDict(dict[Any, Any]):
    """A JSON-serializable mapping that rejects mutation after construction."""

    @staticmethod
    def _immutable(*_: object, **__: object) -> Never:
        raise TypeError("Mission Core Plugin SDK contracts are deeply immutable")

    __setitem__ = _immutable
    __delitem__ = _immutable
    clear = _immutable
    pop = _immutable
    popitem = _immutable
    setdefault = _immutable
    update = _immutable
    __ior__ = _immutable

    def __copy__(self) -> _FrozenDict:
        return self

    def __deepcopy__(self, _memo: dict[int, Any]) -> _FrozenDict:
        return self


class _FrozenList(list[Any]):
    """A JSON-serializable sequence that rejects mutation after construction."""

    @staticmethod
    def _immutable(*_: object, **__: object) -> Never:
        raise TypeError("Mission Core Plugin SDK contracts are deeply immutable")

    __setitem__ = _immutable
    __delitem__ = _immutable
    append = _immutable
    clear = _immutable
    extend = _immutable
    insert = _immutable
    pop = _immutable
    remove = _immutable
    reverse = _immutable
    sort = _immutable
    __iadd__ = _immutable
    __imul__ = _immutable

    def __copy__(self) -> _FrozenList:
        return self

    def __deepcopy__(self, _memo: dict[int, Any]) -> _FrozenList:
        return self


def _deep_freeze(value: Any) -> Any:
    if isinstance(value, (_FrozenDict, _FrozenList)):
        return value
    if isinstance(value, dict):
        return _FrozenDict({key: _deep_freeze(child) for key, child in value.items()})
    if isinstance(value, list):
        return _FrozenList(_deep_freeze(child) for child in value)
    if isinstance(value, tuple):
        return tuple(_deep_freeze(child) for child in value)
    if isinstance(value, (set, frozenset)):
        return frozenset(_deep_freeze(child) for child in value)
    return value


class ContractModel(BaseModel):
    """Closed and immutable base for every wire-safe SDK contract."""

    model_config = ConfigDict(extra="forbid", frozen=True)

    @model_validator(mode="after")
    def contract_values_are_deeply_immutable(self) -> ContractModel:
        """Freeze mutable containers after Pydantic has validated their contents."""

        for field_name in type(self).model_fields:
            value = getattr(self, field_name)
            frozen = _deep_freeze(value)
            if frozen is not value:
                object.__setattr__(self, field_name, frozen)
        return self

    def model_copy(
        self,
        *,
        update: Mapping[str, Any] | None = None,
        deep: bool = False,
    ) -> Self:
        """Revalidate updates so a copied contract cannot bypass deep freezing."""

        if update:
            candidate = self.model_dump(round_trip=True, exclude_unset=True)
            candidate.update(update)
            return type(self).model_validate(candidate)
        return super().model_copy(deep=deep)
