from __future__ import annotations

from collections.abc import Iterable, Mapping
from typing import Any


ALLOWED_ACTIONS = ("idle", "sleep", "walk", "look", "sit", "tail_wag")


class StateTransitionError(ValueError):
    pass


def assert_can_create_build(
    *,
    consent_status: str,
    boundary_acknowledged: bool,
    media_quality_statuses: Iterable[str],
) -> None:
    statuses = tuple(media_quality_statuses)
    if consent_status != "granted" or not boundary_acknowledged:
        raise StateTransitionError("consent and boundary acknowledgement are required")
    if not statuses:
        raise StateTransitionError("at least one media item is required")
    if any(status != "passed" for status in statuses):
        raise StateTransitionError("all media must pass quality checks")


def assert_can_approve_build(
    *,
    build_status: str,
    approval_answers: Mapping[str, Any],
) -> None:
    if build_status != "preview_ready":
        raise StateTransitionError("build must be preview_ready before approval")
    required_answers = (
        "looks_like_pet",
        "feels_comfortable",
        "actions_fit_long_term",
    )
    if any(approval_answers.get(answer) is not True for answer in required_answers):
        raise StateTransitionError("all three preview confirmations are required")


def assert_can_mark_package_ready(
    *,
    build_status: str,
    user_approved: bool,
    entitlement_status: str,
) -> None:
    if build_status != "approved" or not user_approved:
        raise StateTransitionError("approved build with user approval is required")
    if entitlement_status != "active":
        raise StateTransitionError("active entitlement is required before packaging")


def assert_can_download_package(
    *,
    package_status: str,
    package_user_approved: bool,
    entitlement_status: str,
    checksum_valid: bool,
    runtime_compatible: bool,
    allowed_actions: Iterable[str],
) -> None:
    if package_status != "approved_for_download":
        raise StateTransitionError("package must be approved_for_download")
    if not package_user_approved:
        raise StateTransitionError("user-approved package is required")
    if entitlement_status != "active":
        raise StateTransitionError("active entitlement is required for download")
    if not checksum_valid:
        raise StateTransitionError("valid checksum is required for download")
    if not runtime_compatible:
        raise StateTransitionError("compatible runtime version is required")
    unexpected_actions = set(allowed_actions) - set(ALLOWED_ACTIONS)
    if unexpected_actions:
        raise StateTransitionError("package contains unsupported actions")


def assert_can_start_full_build(
    *,
    entitlement_status: str,
    quantity_total: int,
    quantity_used: int,
    expires_at: str | None,
    now: str,
) -> None:
    _assert_consumable_entitlement(
        entitlement_type="custom_pet_build",
        entitlement_status=entitlement_status,
        quantity_total=quantity_total,
        quantity_used=quantity_used,
        expires_at=expires_at,
        now=now,
    )


def assert_can_start_revision(
    *,
    entitlement_status: str,
    quantity_total: int,
    quantity_used: int,
    expires_at: str | None,
    now: str,
) -> None:
    _assert_consumable_entitlement(
        entitlement_type="revision_credit",
        entitlement_status=entitlement_status,
        quantity_total=quantity_total,
        quantity_used=quantity_used,
        expires_at=expires_at,
        now=now,
    )


def assert_can_reserve_model_cost(
    *,
    current_cost_cents: int,
    estimated_cost_cents: int,
    cap_cents: int = 500,
) -> None:
    if current_cost_cents < 0 or estimated_cost_cents < 0 or cap_cents < 0:
        raise StateTransitionError("model cost values must be non-negative")
    if current_cost_cents + estimated_cost_cents > cap_cents:
        raise StateTransitionError("model cost cap exceeded")


def assert_can_run_package(
    *,
    package_status: str,
    package_user_approved: bool,
    runtime_entitlement_status: str,
    runtime_entitlement_expires_at: str | None,
    now: str,
    checksum_valid: bool,
    runtime_compatible: bool,
    allowed_actions: Iterable[str],
) -> None:
    if package_status != "approved_for_download":
        raise StateTransitionError("package must be approved_for_download")
    if not package_user_approved:
        raise StateTransitionError("user-approved package is required")
    if runtime_entitlement_status != "active" or _is_expired(runtime_entitlement_expires_at, now):
        raise StateTransitionError("active package_runtime_access entitlement is required")
    if not checksum_valid:
        raise StateTransitionError("valid checksum is required to run package")
    if not runtime_compatible:
        raise StateTransitionError("compatible runtime version is required to run package")
    unexpected_actions = set(allowed_actions) - set(ALLOWED_ACTIONS)
    if unexpected_actions:
        raise StateTransitionError("package contains unsupported actions")


def _assert_consumable_entitlement(
    *,
    entitlement_type: str,
    entitlement_status: str,
    quantity_total: int,
    quantity_used: int,
    expires_at: str | None,
    now: str,
) -> None:
    if entitlement_status != "active":
        raise StateTransitionError(f"active {entitlement_type} entitlement is required")
    if _is_expired(expires_at, now):
        raise StateTransitionError(f"active {entitlement_type} entitlement is required")
    if quantity_used >= quantity_total:
        raise StateTransitionError(f"available {entitlement_type} entitlement is required")


def _is_expired(expires_at: str | None, now: str) -> bool:
    return bool(expires_at and expires_at <= now)
