"""Local-first license validation primitives."""

from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime, timezone
import json
import logging
import os
from pathlib import Path
from typing import Any, Dict, Iterable, Mapping

import yaml
from importlib import resources

LOGGER = logging.getLogger("tokenmoulds.licensing")
DEFAULT_LICENSE_ID = "lic_local_dev"
DEFAULT_LAYERS = ("core", "fork", "org", "group", "personal")


class LicenseValidationError(RuntimeError):
    """Raised when a license fails validation."""


@dataclass(frozen=True)
class LicenseContext:
    license_id: str
    repository: str | None = None
    required_layers: tuple[str, ...] = DEFAULT_LAYERS


@dataclass(frozen=True)
class LicenseValidationResult:
    license_id: str
    plan: str
    expires_at: datetime | None
    allowed: bool
    mode: str
    repository: str | None = None
    layers: Mapping[str, bool] | None = None
    metadata: Mapping[str, Any] | None = None


class LicenseValidator:
    """Validates license contexts against a registry file."""

    def __init__(
        self, registry_path: str | Path | None = None, *, mode: str | None = None
    ):
        self.mode = (
            (mode or os.getenv("TOKENMOULDS_LICENSE_MODE", "strict")).lower().strip()
        )
        if self.mode not in {"strict", "permissive"}:
            self.mode = "strict"

        env_path = os.getenv("TOKENMOULDS_LICENSE_REGISTRY")
        if registry_path:
            self.registry_path = Path(registry_path)
        elif env_path:
            self.registry_path = Path(env_path)
        else:
            self.registry_path = None

        self._registry = self._load_registry()

    def _load_registry(self) -> Mapping[str, dict[str, Any]]:
        text: str | None = None
        if self.registry_path:
            if not self.registry_path.exists():
                return {}
            text = self.registry_path.read_text(encoding="utf-8")
        else:
            try:
                traversable = (
                    resources.files("tokenmoulds.licensing.registries")
                    / "local-dev.yaml"
                )
                text = traversable.read_text(encoding="utf-8")
            except (FileNotFoundError, ModuleNotFoundError):
                return {}

        payload = yaml.safe_load(text)
        if not isinstance(payload, dict):
            return {}
        licenses = payload.get("licenses", {})
        if not isinstance(licenses, dict):
            return {}
        normalized: Dict[str, dict[str, Any]] = {}
        for license_id, data in licenses.items():
            if isinstance(data, dict):
                normalized[str(license_id)] = data
        return normalized

    def validate(self, context: LicenseContext) -> LicenseValidationResult:
        """Validate the provided context and return the decision."""

        if self.mode == "permissive":
            result = LicenseValidationResult(
                license_id=context.license_id or DEFAULT_LICENSE_ID,
                plan="dev",
                expires_at=None,
                allowed=True,
                mode=self.mode,
                repository=context.repository,
                layers={layer: True for layer in DEFAULT_LAYERS},
            )
            self._log_audit(result, {"strategy": "permissive_override"})
            return result

        license_id = context.license_id or DEFAULT_LICENSE_ID
        record = self._registry.get(license_id)
        if record is None:
            raise LicenseValidationError(
                f"License '{license_id}' not found in registry ({self.registry_path})."
            )

        expires_at = self._parse_datetime(record.get("expires_at"))
        if expires_at and expires_at <= datetime.now(timezone.utc):
            raise LicenseValidationError(
                f"License '{license_id}' expired on {expires_at.isoformat()} UTC."
            )

        repo_patterns = record.get("repositories") or record.get("repos") or ["*"]
        if context.repository and not self._repo_allowed(
            repo_patterns, context.repository
        ):
            raise LicenseValidationError(
                f"License '{license_id}' is not authorized for repository '{context.repository}'."
            )

        layers = record.get("layers") or {}
        for layer in context.required_layers or DEFAULT_LAYERS:
            if not layers.get(layer, False):
                raise LicenseValidationError(
                    f"License '{license_id}' does not permit access to the '{layer}' layer."
                )

        result = LicenseValidationResult(
            license_id=license_id,
            plan=str(record.get("plan", "unknown")),
            expires_at=expires_at,
            allowed=True,
            mode=self.mode,
            repository=context.repository,
            layers={layer: bool(layers.get(layer, False)) for layer in DEFAULT_LAYERS},
            metadata=record.get("metadata")
            if isinstance(record.get("metadata"), Mapping)
            else None,
        )
        self._log_audit(
            result,
            {
                "repository": context.repository,
                "required_layers": list(context.required_layers),
                "mode": self.mode,
            },
        )
        return result

    def _repo_allowed(self, patterns: Iterable[str], repository: str) -> bool:
        repository = repository.lower()
        for pattern in patterns or []:
            if not isinstance(pattern, str):
                continue
            normalized = pattern.lower().strip()
            if normalized == "*":
                return True
            if "/" not in normalized:
                continue
            org, repo = normalized.split("/", 1)
            repo_candidate = repository.split("/", 1)
            actual_org = repo_candidate[0]
            actual_repo = repo_candidate[1] if len(repo_candidate) > 1 else ""
            org_match = org == "*" or org == actual_org
            repo_match = repo == "*" or repo == actual_repo
            if org_match and repo_match:
                return True
        return False

    def _parse_datetime(self, value: Any) -> datetime | None:
        if value in (None, "", 0):
            return None
        if isinstance(value, datetime):
            return value if value.tzinfo else value.replace(tzinfo=timezone.utc)
        if isinstance(value, (int, float)):
            return datetime.fromtimestamp(value, tz=timezone.utc)
        if isinstance(value, str):
            text = value.strip()
            if text.endswith("Z"):
                text = text[:-1] + "+00:00"
            try:
                parsed = datetime.fromisoformat(text)
            except ValueError as exc:
                raise LicenseValidationError(
                    f"Invalid expires_at value '{value}': {exc}"
                ) from exc
            return parsed if parsed.tzinfo else parsed.replace(tzinfo=timezone.utc)
        raise LicenseValidationError(f"Unsupported expires_at type: {type(value)!r}")

    def _log_audit(
        self, result: LicenseValidationResult, context: Mapping[str, Any]
    ) -> None:
        payload = {
            "event": "license.validate",
            "license_id": result.license_id,
            "plan": result.plan,
            "mode": result.mode,
            "repository": result.repository,
            "expires_at": result.expires_at.isoformat() if result.expires_at else None,
            "context": context,
        }
        LOGGER.info(json.dumps(payload, sort_keys=True))
