"""Tier enforcement functions — gate commands by license tier.

Every check accepts a TierInfo model and raises a ComplyFormError
subclass on violation. The enforce_with_refresh helper wraps checks
with auto-refresh logic.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, ConfigDict

from complyform.core.auth.tier_config import TIER_CONFIG
from complyform.core.errors import (
    BatchOperationTierError,
    BatchSizeLimitError,
    DiscoverTooManyStatesError,
    FeatureNotAvailableError,
    RemediationTierError,
    ScanSourceTierError,
)

if TYPE_CHECKING:
    from collections.abc import Callable

    from complyform.core.auth.license import LicenseCache, LicenseManager


class TierInfo(BaseModel):
    """Resolved tier information from license cache or default (community)."""

    model_config = ConfigDict(strict=True)

    tier: str
    assess_frameworks: list[str]
    remediate_frameworks: list[str]
    features: list[str]
    scan_sources: list[str]
    project_limit: int | None
    batch_limit: int | None
    discover_limit: int | None
    expires_at: str | None
    retention_months: int | None = None
    dormancy_days: int | None = None
    member_limit: int | None = None
    available_regions: list[str] | None = None


def default_community_tier_info() -> TierInfo:
    """Returns TierInfo populated from TIER_CONFIG['community']."""
    cfg = TIER_CONFIG["community"]
    return TierInfo(
        tier="community",
        assess_frameworks=list(_as_str_list(cfg["assess_frameworks"])),
        remediate_frameworks=list(_as_str_list(cfg["remediate_frameworks"])),
        features=list(_as_str_list(cfg["features"])),
        scan_sources=list(_as_str_list(cfg["scan_sources"])),
        project_limit=_as_optional_int(cfg["project_limit"]),
        batch_limit=_as_optional_int(cfg["batch_limit"]),
        discover_limit=_as_optional_int(cfg["discover_limit"]),
        expires_at=None,
        retention_months=_as_optional_int(cfg.get("retention_months")),
        dormancy_days=_as_optional_int(cfg.get("dormancy_days")),
        member_limit=_as_optional_int(cfg.get("member_limit")),
        available_regions=_as_str_list(cfg.get("available_regions"))
        if cfg.get("available_regions")
        else None,
    )


def resolve_tier_info(license_cache: LicenseCache | None) -> TierInfo:
    """Build TierInfo from cache or fall back to community defaults."""
    if license_cache is None:
        return default_community_tier_info()

    from complyform.core.auth.license import LicenseManager

    mgr = LicenseManager()
    if not mgr.is_within_grace_period(license_cache):
        return default_community_tier_info()

    tier = license_cache.tier
    cfg = TIER_CONFIG.get(tier)
    if cfg is None:
        return default_community_tier_info()

    return TierInfo(
        tier=tier,
        assess_frameworks=license_cache.assess_frameworks,
        remediate_frameworks=license_cache.remediate_frameworks,
        features=license_cache.features,
        scan_sources=license_cache.scan_sources
        or list(_as_str_list(cfg["scan_sources"])),
        project_limit=license_cache.project_limit,
        batch_limit=license_cache.batch_limit,
        discover_limit=license_cache.discover_limit,
        expires_at=license_cache.expires_at,
        retention_months=_as_optional_int(cfg.get("retention_months")),
        dormancy_days=_as_optional_int(cfg.get("dormancy_days")),
        member_limit=_as_optional_int(cfg.get("member_limit")),
        available_regions=_as_str_list(cfg.get("available_regions"))
        if cfg.get("available_regions")
        else None,
    )


def check_framework_access(
    frameworks: list[str],
    command: str,
    tier_info: TierInfo,
) -> None:
    """Gate framework access by tier.

    Assess is always allowed; remediate/validate/fix check tiers.
    """
    if command == "assess":
        return

    allowed = tier_info.remediate_frameworks
    if allowed == ["all"]:
        return

    allowed_set = frozenset(allowed)
    for fw in frameworks:
        if fw not in allowed_set:
            raise RemediationTierError(fw, tier_info.tier)


def check_scan_source(source: str, tier_info: TierInfo) -> None:
    """Verify scan source is permitted for tier."""
    if source in tier_info.scan_sources:
        return
    raise ScanSourceTierError(source, tier_info.tier)


def check_feature(feature: str, tier_info: TierInfo) -> None:
    """Verify feature is available for tier."""
    if feature not in tier_info.features:
        raise FeatureNotAvailableError(feature, tier_info.tier)


def check_batch_limit(count: int, tier_info: TierInfo) -> None:
    """Check batch operation size against tier limits."""
    if tier_info.batch_limit is None:
        raise BatchOperationTierError()

    if count > tier_info.batch_limit:
        raise BatchSizeLimitError(count, tier_info.batch_limit)


def check_discover_limit(count: int, tier_info: TierInfo) -> None:
    """Check discover count against tier limits."""
    limit = tier_info.discover_limit
    if limit is not None and count > limit:
        raise DiscoverTooManyStatesError(count, limit, ".")


def enforce_with_refresh(
    license_mgr: LicenseManager,
    tier_info: TierInfo,
    check_fn: Callable[..., None],
    *args: Any,
) -> TierInfo:
    """Run a tier check with auto-refresh on failure.

    Returns (possibly updated) TierInfo.
    """
    try:
        check_fn(*args)
        return tier_info
    except (RemediationTierError, FeatureNotAvailableError, ScanSourceTierError) as e:
        updated = license_mgr.attempt_auto_refresh(e)
        if updated is not None:
            check_fn(*args)
            return updated
        raise


# -- Internal helpers ---------------------------------------------------------


def _as_str_list(val: object) -> list[str]:
    if isinstance(val, list):
        return [str(v) for v in val]
    return []


def _as_optional_int(val: object) -> int | None:
    if val is None:
        return None
    if isinstance(val, int):
        return val
    return None
