from __future__ import annotations

from abc import ABC, abstractmethod
import asyncio
from dataclasses import dataclass, replace
from typing import Any

from jsonpatch import JsonPatchException, apply_patch
from jsonpointer import JsonPointerException
from pydantic import BaseModel, ConfigDict, ValidationError

from vibe.core.config.patch import ConfigPatch
from vibe.core.config.types import (
    MISSING_CONFIG_FILE_FINGERPRINT,
    ConcurrencyConflictError,
    ConflictStrategy,
    LayerConfigSnapshot,
)


class RawConfig(BaseModel):
    """Permissive default schema that preserves all fields as extras."""

    model_config = ConfigDict(extra="allow")


class ConfigLayerError(Exception):
    """Base error for all ConfigLayer errors."""

    def __init__(self, layer_name: str, message: str) -> None:
        super().__init__(message)
        self.layer_name = layer_name


class UntrustedLayerError(ConfigLayerError):
    """Raised when attempting to load data from an untrusted layer."""

    def __init__(self, layer_name: str) -> None:
        super().__init__(layer_name, f"Layer '{layer_name}' is not trusted")


class EmptyLayerError(ConfigLayerError):
    """Raised when a trusted layer has no data."""

    def __init__(self, layer_name: str) -> None:
        super().__init__(layer_name, f"Layer '{layer_name}' has no data after load")


class TrustNotResolvedError(ConfigLayerError):
    """Raised when grant_trust/revoke_trust is called before trust has been resolved."""

    def __init__(self, layer_name: str) -> None:
        super().__init__(
            layer_name, f"Layer '{layer_name}': trust has not been resolved yet"
        )


class LayerNotLoadedError(ConfigLayerError):
    """Raised when a layer operation requires cached data and fingerprint."""

    def __init__(self, layer_name: str) -> None:
        super().__init__(
            layer_name, f"Layer '{layer_name}' must be loaded before applying patches"
        )


class ConfigPatchApplicationError(ConfigLayerError):
    """Raised when a patch cannot be applied to cached layer data."""

    def __init__(self, layer_name: str) -> None:
        super().__init__(layer_name, f"Layer '{layer_name}': failed to apply patch")


class TrustResolutionError(ConfigLayerError):
    """Raised when trust status is not resolvable."""

    def __init__(self, layer_name: str) -> None:
        super().__init__(
            layer_name,
            f"Layer '{layer_name}': _check_trust() must return bool, got None",
        )


class LayerImplementationError(ConfigLayerError):
    """Raised when a subclass-provided method fails."""

    def __init__(self, layer_name: str, method_name: str) -> None:
        super().__init__(layer_name, f"Layer '{layer_name}': {method_name}() failed")
        self.method_name = method_name


@dataclass(frozen=True, slots=True)
class _LayerState[S: BaseModel]:
    is_trusted: bool | None = None
    data: S | None = None
    fingerprint: str | None = None


@dataclass(frozen=True, slots=True)
class _GrantTrust:
    pass


@dataclass(frozen=True, slots=True)
class _RevokeTrust:
    pass


@dataclass(frozen=True, slots=True)
class _ResolveTrust:
    pass


@dataclass(frozen=True, slots=True)
class _Load:
    force: bool = False


@dataclass(frozen=True, slots=True)
class _InvalidateCache:
    pass


@dataclass(frozen=True, slots=True)
class _ApplyPatch:
    patch: ConfigPatch
    on_conflict: ConflictStrategy


class ConfigLayer[S: BaseModel](ABC):
    """Each layer represents a named source that produces a sparse
    dictionary of configuration values.
    """

    def __init__(self, *, name: str, output_schema: type[S] = RawConfig) -> None:
        self.name = name
        self.output_schema = output_schema

        self._state: _LayerState[S] = _LayerState()
        self._lock = asyncio.Lock()

    # --- Overridable ---

    async def _check_trust(self) -> bool:
        """Resolve whether this layer should be trusted.

        Override in subclasses to implement custom trust logic.
        E.g. a user-local layer returns ``True``,
        a project layer checks persisted permissions.

        Returns ``False`` by default (untrusted).
        """
        return False

    @abstractmethod
    async def _build_config_snapshot(self) -> LayerConfigSnapshot:
        """Read and return sparse config with its backing-store fingerprint.

        Subclasses only need to implement the raw read logic; caching
        is handled by the base.
        """
        ...

    async def _on_trust_changed(self, old: bool | None, new: bool | None) -> None:
        """Called when the trust status changes.

        Override to persist trust status or react to trust transitions.
        Default is a no-op.
        """
        return

    @abstractmethod
    async def _save_to_store(self, _next_config: S) -> str:
        """Persist full layer data and return the store's new fingerprint.

        The base class applies patches and validates the result before calling this.
        """
        ...

    # --- Internal ---

    async def _notify_trust_change(self, old: bool | None, new: bool | None) -> None:
        """Call ``_on_trust_changed`` and wrap any error."""
        if old == new:
            return
        try:
            await self._on_trust_changed(old, new)
        except Exception as e:
            raise LayerImplementationError(self.name, "_on_trust_changed") from e

    async def _resolve_check_trust(self) -> bool:
        """Call ``_check_trust`` and wrap any error."""
        try:
            return await self._check_trust()
        except Exception as e:
            raise LayerImplementationError(self.name, "_check_trust") from e

    async def _dispatch(self, action: Any) -> _LayerState[S]:
        """Serialize all state mutations through a single lock."""
        async with self._lock:
            state = _LayerState(
                is_trusted=self._state.is_trusted,
                data=self._state.data,
                fingerprint=self._state.fingerprint,
            )

            match action:
                case _GrantTrust():
                    new_state = await self._handle_grant_trust(state)
                case _RevokeTrust():
                    new_state = await self._handle_revoke_trust(state)
                case _ResolveTrust():
                    new_state = await self._handle_resolve_trust(state)
                case _Load(force=force):
                    new_state = await self._handle_load(state, force)
                case _InvalidateCache():
                    new_state = await self._handle_invalidate_cache(state)
                case _ApplyPatch(patch=patch, on_conflict=on_conflict):
                    new_state = await self._handle_apply_patch(
                        state, patch, on_conflict
                    )
                case _:
                    raise NotImplementedError(f"Unknown action: {action!r}")

            self._state = new_state

            return new_state

    async def _handle_grant_trust(self, state: _LayerState[S]) -> _LayerState[S]:
        if state.is_trusted is None:
            raise TrustNotResolvedError(self.name)

        if state.is_trusted is True:
            return state

        await self._notify_trust_change(state.is_trusted, True)

        return _LayerState(
            is_trusted=True, data=state.data, fingerprint=state.fingerprint
        )

    async def _handle_revoke_trust(self, state: _LayerState[S]) -> _LayerState[S]:
        if state.is_trusted is None:
            raise TrustNotResolvedError(self.name)

        if state.is_trusted is False:
            return state

        await self._notify_trust_change(state.is_trusted, False)

        return _LayerState(is_trusted=False, data=None, fingerprint=None)

    async def _handle_resolve_trust(self, state: _LayerState[S]) -> _LayerState[S]:
        is_trusted = await self._resolve_check_trust()

        await self._notify_trust_change(state.is_trusted, is_trusted)

        return _LayerState(
            is_trusted=is_trusted,
            data=state.data if is_trusted else None,
            fingerprint=state.fingerprint if is_trusted else None,
        )

    async def _handle_load(self, state: _LayerState[S], force: bool) -> _LayerState[S]:
        if state.is_trusted is not None:
            is_trusted = state.is_trusted
        else:
            is_trusted = await self._resolve_check_trust()

        await self._notify_trust_change(state.is_trusted, is_trusted)

        if not is_trusted:
            return _LayerState(is_trusted=is_trusted, data=None, fingerprint=None)

        next_state = _LayerState(
            is_trusted=is_trusted, data=state.data, fingerprint=state.fingerprint
        )

        if next_state.data is None or force:
            try:
                snapshot = await self._build_config_snapshot()
                next_state = _LayerState(
                    is_trusted=next_state.is_trusted,
                    data=self.validate_output(snapshot.data),
                    fingerprint=snapshot.fingerprint,
                )
            except ConcurrencyConflictError:
                raise
            except Exception as e:
                raise LayerImplementationError(
                    self.name, "_build_config_snapshot"
                ) from e

        return next_state

    async def _handle_invalidate_cache(self, state: _LayerState[S]) -> _LayerState[S]:
        return _LayerState(is_trusted=state.is_trusted, data=None, fingerprint=None)

    async def _handle_apply_patch(
        self, state: _LayerState[S], patch: ConfigPatch, on_conflict: ConflictStrategy
    ) -> _LayerState[S]:

        if (
            state.data is None
            or state.fingerprint is None
            or state.fingerprint == MISSING_CONFIG_FILE_FINGERPRINT
        ):
            raise LayerNotLoadedError(self.name)

        match on_conflict:
            case ConflictStrategy.CANCEL:
                if patch.fingerprint != state.fingerprint:
                    raise ConcurrencyConflictError(
                        expected_fp=patch.fingerprint, actual_fp=state.fingerprint
                    )
            case ConflictStrategy.REPLACE:
                pass
            case _:
                raise ValueError(f"Unsupported conflict strategy: {on_conflict!r}")

        try:
            new_data = apply_patch(state.data.model_dump(), patch.to_json_patch())
            validated_new_data = self.validate_output(new_data)
        except (JsonPatchException, JsonPointerException, ValidationError) as e:
            raise ConfigPatchApplicationError(self.name) from e

        try:
            new_fingerprint = await self._save_to_store(validated_new_data)
        except NotImplementedError:
            raise
        except Exception as e:
            raise LayerImplementationError(self.name, "_save_to_store") from e
        return replace(state, data=validated_new_data, fingerprint=new_fingerprint)

    # --- Public ---

    @property
    def is_trusted(self) -> bool | None:
        """Current trust status. ``None`` if unresolved."""
        return self._state.is_trusted

    async def resolve_trust(self) -> bool:
        """Resolve and cache trust status."""
        state = await self._dispatch(_ResolveTrust())

        if state.is_trusted is None:
            raise TrustResolutionError(self.name)

        return state.is_trusted

    async def grant_trust(self) -> None:
        """Explicitly mark this layer as trusted."""
        await self._dispatch(_GrantTrust())

    async def revoke_trust(self) -> None:
        """Explicitly mark this layer as untrusted and clear cached data."""
        await self._dispatch(_RevokeTrust())

    async def invalidate_cache(self) -> None:
        """Clear the in-memory cache so the next ``load()`` re-reads."""
        await self._dispatch(_InvalidateCache())

    async def load(self, *, force: bool = False) -> S:
        """Load data from this layer, enforcing trust and caching the result.

        Use ``force=True`` to bypass caching.
        """
        state = await self._dispatch(_Load(force=force))

        if state.is_trusted is None:
            raise TrustResolutionError(self.name)

        if not state.is_trusted:
            raise UntrustedLayerError(self.name)

        if state.data is None:
            raise EmptyLayerError(self.name)

        return state.data.model_copy(deep=True)

    @property
    def fingerprint(self) -> str | None:
        """Cached opaque fingerprint token for this layer. ``None`` if unresolved."""
        return self._state.fingerprint

    async def apply(
        self,
        patch: ConfigPatch,
        *,
        on_conflict: ConflictStrategy = ConflictStrategy.CANCEL,
    ) -> None:
        """Persist a patch to this layer's backing store."""
        await self._dispatch(_ApplyPatch(patch=patch, on_conflict=on_conflict))

    def validate_output(self, data: dict[str, Any]) -> S:
        """Validate *data* against ``output_schema``."""
        return self.output_schema.model_validate(data)

    def __repr__(self) -> str:
        return f"{type(self).__name__}(name={self.name!r})"
