"""Phase 12.0 Hospitality validators."""
from __future__ import annotations

import re
from datetime import datetime

from shared.exceptions import AppError

from app.models.types import (
    BundleKey,
    BundleStatus,
    ConnectorKind,
    ConnectorStatus,
    DispatchStatus,
    KitchenItemStatus,
    KitchenTicketStatus,
    LifecycleStatus,
    MenuStatus,
    ModifierSelectionMode,
    ReservationStatus,
    ServiceRequestKind,
    ServiceRequestStatus,
    TableAssignmentStatus,
    TableStatus,
    VenueFormat,
    WaitlistStatus,
)

CODE_RE = re.compile(r"^[A-Za-z0-9_\-]{2,50}$")
SETTING_KEY_RE = re.compile(r"^[A-Za-z0-9_.\-]{2,100}$")
FEATURE_KEY_RE = re.compile(r"^[A-Za-z0-9_.\-]{2,100}$")


def validate_non_empty(value: str | None, *, field: str) -> str:
    if value is None or not str(value).strip():
        raise AppError(
            f"{field} الزامی است",
            status_code=422,
            error_code="validation_error",
            details={"field": field},
        )
    return str(value).strip()


def validate_code(code: str | None, *, field: str = "code") -> str:
    cleaned = validate_non_empty(code, field=field).upper()
    if not CODE_RE.match(cleaned):
        raise AppError(
            f"فرمت {field} نامعتبر است",
            status_code=422,
            error_code="invalid_code",
            details={"field": field},
        )
    return cleaned


def validate_setting_key(key: str | None) -> str:
    cleaned = validate_non_empty(key, field="key")
    if not SETTING_KEY_RE.match(cleaned):
        raise AppError(
            "فرمت key نامعتبر است",
            status_code=422,
            error_code="invalid_setting_key",
        )
    return cleaned


def validate_feature_key(key: str | None) -> str:
    cleaned = validate_non_empty(key, field="feature_key")
    if not FEATURE_KEY_RE.match(cleaned):
        raise AppError(
            "فرمت feature_key نامعتبر است",
            status_code=422,
            error_code="invalid_feature_key",
        )
    return cleaned


def validate_currency_code(code: str) -> str:
    cleaned = code.strip().upper()
    if len(cleaned) != 3:
        raise AppError(
            "کد ارز باید ۳ حرفی باشد",
            status_code=422,
            error_code="invalid_currency_code",
        )
    return cleaned


def validate_non_negative_int(value: int | None, *, field: str) -> int | None:
    if value is None:
        return None
    resolved = int(value)
    if resolved < 0:
        raise AppError(
            f"{field} نمی‌تواند منفی باشد",
            status_code=422,
            error_code="invalid_amount",
            details={"field": field},
        )
    return resolved


def validate_positive_int(value: int | None, *, field: str) -> int:
    if value is None:
        raise AppError(
            f"{field} الزامی است",
            status_code=422,
            error_code="validation_error",
            details={"field": field},
        )
    resolved = int(value)
    if resolved < 1:
        raise AppError(
            f"{field} باید حداقل ۱ باشد",
            status_code=422,
            error_code="invalid_amount",
            details={"field": field},
        )
    return resolved


def _enum_or_error(enum_cls, value, *, error_code: str, message: str):
    if isinstance(value, enum_cls):
        return value
    try:
        return enum_cls(value)
    except ValueError as exc:
        raise AppError(message, status_code=422, error_code=error_code) from exc


def validate_lifecycle_status(status: LifecycleStatus | str) -> LifecycleStatus:
    return _enum_or_error(
        LifecycleStatus,
        status,
        error_code="invalid_lifecycle_status",
        message="وضعیت نامعتبر است",
    )


def validate_venue_format(value: VenueFormat | str) -> VenueFormat:
    return _enum_or_error(
        VenueFormat,
        value,
        error_code="invalid_venue_format",
        message="فرمت واحد پذیرایی نامعتبر است",
    )


def validate_bundle_key(value: BundleKey | str) -> BundleKey:
    return _enum_or_error(
        BundleKey,
        value,
        error_code="invalid_bundle_key",
        message="کلید بسته نامعتبر است",
    )


def validate_bundle_status(value: BundleStatus | str) -> BundleStatus:
    return _enum_or_error(
        BundleStatus,
        value,
        error_code="invalid_bundle_status",
        message="وضعیت بسته نامعتبر است",
    )


def validate_menu_status(value: MenuStatus | str) -> MenuStatus:
    return _enum_or_error(
        MenuStatus,
        value,
        error_code="invalid_menu_status",
        message="وضعیت منو نامعتبر است",
    )


def validate_table_status(value: TableStatus | str) -> TableStatus:
    return _enum_or_error(
        TableStatus,
        value,
        error_code="invalid_table_status",
        message="وضعیت میز نامعتبر است",
    )


def ensure_optimistic_version(entity_version: int, expected: int | None) -> None:
    if expected is None:
        return
    if entity_version != expected:
        raise AppError(
            "نسخه رکورد تغییر کرده است؛ دوباره تلاش کنید",
            status_code=409,
            error_code="version_conflict",
            details={"expected": expected, "actual": entity_version},
        )


def forbid_format_specific_engines(payload: dict) -> None:
    """Reject payloads that inject format-specific engine keys into foundation."""
    forbidden = {
        "pos_engine",
        "kitchen_engine",
        "ordering_engine",
        "hardcoded_venue_rules",
        "vendor_sdk",
    }
    present = forbidden.intersection(payload.keys())
    if present:
        raise AppError(
            "موتورهای اختصاصی یا SDK فروشنده در لایه foundation مجاز نیست",
            status_code=422,
            error_code="format_specific_forbidden",
            details={"fields": sorted(present)},
        )


TIME_RE = re.compile(r"^([01]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$")
LOCALE_RE = re.compile(r"^[a-z]{2}(-[A-Z]{2})?$")


def validate_time_hhmm(value: str, *, field: str) -> str:
    cleaned = validate_non_empty(value, field=field)
    if not TIME_RE.match(cleaned):
        raise AppError(
            f"فرمت {field} باید HH:MM یا HH:MM:SS باشد",
            status_code=422,
            error_code="invalid_time",
            details={"field": field},
        )
    if len(cleaned) == 5:
        return f"{cleaned}:00"
    return cleaned


def validate_time_range(start_time: str, end_time: str) -> tuple[str, str]:
    start = validate_time_hhmm(start_time, field="start_time")
    end = validate_time_hhmm(end_time, field="end_time")
    if end <= start:
        raise AppError(
            "زمان پایان باید بعد از زمان شروع باشد",
            status_code=422,
            error_code="invalid_time_range",
        )
    return start, end


def validate_weekday(value: int | None) -> int | None:
    if value is None:
        return None
    if value < 0 or value > 6:
        raise AppError(
            "weekday باید بین ۰ تا ۶ باشد",
            status_code=422,
            error_code="invalid_weekday",
        )
    return value


def validate_locale(locale: str) -> str:
    cleaned = validate_non_empty(locale, field="locale")
    if not LOCALE_RE.match(cleaned):
        raise AppError(
            "فرمت locale نامعتبر است",
            status_code=422,
            error_code="invalid_locale",
        )
    return cleaned


def validate_file_ref(file_ref: str) -> str:
    cleaned = validate_non_empty(file_ref, field="file_ref")
    if len(cleaned) > 255:
        raise AppError(
            "file_ref بیش از حد طولانی است",
            status_code=422,
            error_code="invalid_file_ref",
        )
    return cleaned


def validate_modifier_selection(
    mode: ModifierSelectionMode | str,
    *,
    min_select: int,
    max_select: int | None,
    is_required: bool,
) -> tuple[ModifierSelectionMode, int, int | None]:
    resolved = _enum_or_error(
        ModifierSelectionMode,
        mode,
        error_code="invalid_selection_mode",
        message="حالت انتخاب modifier نامعتبر است",
    )
    min_v = validate_non_negative_int(min_select, field="min_select") or 0
    if is_required and min_v < 1:
        min_v = 1
    max_v = None
    if max_select is not None:
        max_v = validate_non_negative_int(max_select, field="max_select")
        if max_v is not None and max_v < min_v:
            raise AppError(
                "max_select نمی‌تواند کمتر از min_select باشد",
                status_code=422,
                error_code="invalid_selection_bounds",
            )
    return resolved, min_v, max_v


# Phase 12.3 — Table Service & Reservations
def validate_reservation_status(value: ReservationStatus | str) -> ReservationStatus:
    return _enum_or_error(
        ReservationStatus,
        value,
        error_code="invalid_reservation_status",
        message="وضعیت رزرو نامعتبر است",
    )


def validate_table_assignment_status(value: TableAssignmentStatus | str) -> TableAssignmentStatus:
    return _enum_or_error(
        TableAssignmentStatus,
        value,
        error_code="invalid_table_assignment_status",
        message="وضعیت تخصیص میز نامعتبر است",
    )


def validate_service_request_kind(value: ServiceRequestKind | str) -> ServiceRequestKind:
    return _enum_or_error(
        ServiceRequestKind,
        value,
        error_code="invalid_service_request_kind",
        message="نوع درخواست خدمت نامعتبر است",
    )


def validate_service_request_status(value: ServiceRequestStatus | str) -> ServiceRequestStatus:
    return _enum_or_error(
        ServiceRequestStatus,
        value,
        error_code="invalid_service_request_status",
        message="وضعیت درخواست خدمت نامعتبر است",
    )


def validate_waitlist_status(value: WaitlistStatus | str) -> WaitlistStatus:
    return _enum_or_error(
        WaitlistStatus,
        value,
        error_code="invalid_waitlist_status",
        message="وضعیت لیست انتظار نامعتبر است",
    )


def validate_reservation_window(reserved_from: datetime, reserved_to: datetime) -> tuple[datetime, datetime]:
    if reserved_to <= reserved_from:
        raise AppError(
            "زمان پایان رزرو باید بعد از زمان شروع باشد",
            status_code=422,
            error_code="invalid_reservation_window",
        )
    return reserved_from, reserved_to


# Phase 12.6 — Kitchen
def validate_kitchen_ticket_status(value: KitchenTicketStatus | str) -> KitchenTicketStatus:
    return _enum_or_error(
        KitchenTicketStatus,
        value,
        error_code="invalid_kitchen_ticket_status",
        message="وضعیت تیکت آشپزخانه نامعتبر است",
    )


def validate_kitchen_item_status(value: KitchenItemStatus | str) -> KitchenItemStatus:
    return _enum_or_error(
        KitchenItemStatus,
        value,
        error_code="invalid_kitchen_item_status",
        message="وضعیت آیتم آشپزخانه نامعتبر است",
    )


# Phase 12.7 — Connectors
def validate_connector_kind(value: ConnectorKind | str) -> ConnectorKind:
    return _enum_or_error(
        ConnectorKind,
        value,
        error_code="invalid_connector_kind",
        message="نوع رابط اتصال نامعتبر است",
    )


def validate_connector_status(value: ConnectorStatus | str) -> ConnectorStatus:
    return _enum_or_error(
        ConnectorStatus,
        value,
        error_code="invalid_connector_status",
        message="وضعیت رابط اتصال نامعتبر است",
    )


def validate_dispatch_status(value: DispatchStatus | str) -> DispatchStatus:
    return _enum_or_error(
        DispatchStatus,
        value,
        error_code="invalid_dispatch_status",
        message="وضعیت ارسال نامعتبر است",
    )
