"""Phase 7.0 Loyalty validators."""
from __future__ import annotations

import re
from datetime import date

from shared.exceptions import AppError

from app.models.types import (
    CampaignStatus,
    MemberStatus,
    PointAccountStatus,
    ProgramStatus,
    RewardStatus,
    RewardType,
    TierStatus,
)

EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
PHONE_RE = re.compile(r"^[\d\s\+\-\(\)]{5,30}$")
CODE_RE = re.compile(r"^[A-Za-z0-9_\-]{2,50}$")


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_email(email: str | None, *, required: bool = False) -> str | None:
    if email is None or not email.strip():
        if required:
            raise AppError(
                "ایمیل الزامی است",
                status_code=422,
                error_code="email_required",
            )
        return None
    cleaned = email.strip().lower()
    if not EMAIL_RE.match(cleaned):
        raise AppError(
            "فرمت ایمیل نامعتبر است",
            status_code=422,
            error_code="invalid_email",
        )
    return cleaned


def validate_phone(phone: str | None, *, field: str = "phone") -> str | None:
    if phone is None or not phone.strip():
        return None
    cleaned = phone.strip()
    if not PHONE_RE.match(cleaned):
        raise AppError(
            f"فرمت {field} نامعتبر است",
            status_code=422,
            error_code="invalid_phone",
            details={"field": field},
        )
    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:
    resolved = 0 if value is None else int(value)
    if resolved < 0:
        raise AppError(
            f"{field} نمی‌تواند منفی باشد",
            status_code=422,
            error_code="invalid_amount",
            details={"field": field},
        )
    return resolved


def validate_program_status(status: ProgramStatus | str) -> ProgramStatus:
    if isinstance(status, ProgramStatus):
        return status
    try:
        return ProgramStatus(status)
    except ValueError as exc:
        raise AppError(
            "وضعیت برنامه وفاداری نامعتبر است",
            status_code=422,
            error_code="invalid_program_status",
        ) from exc


def validate_member_status(status: MemberStatus | str) -> MemberStatus:
    if isinstance(status, MemberStatus):
        return status
    try:
        return MemberStatus(status)
    except ValueError as exc:
        raise AppError(
            "وضعیت عضو نامعتبر است",
            status_code=422,
            error_code="invalid_member_status",
        ) from exc


def validate_tier_status(status: TierStatus | str) -> TierStatus:
    if isinstance(status, TierStatus):
        return status
    try:
        return TierStatus(status)
    except ValueError as exc:
        raise AppError(
            "وضعیت سطح عضویت نامعتبر است",
            status_code=422,
            error_code="invalid_tier_status",
        ) from exc


def validate_point_account_status(
    status: PointAccountStatus | str,
) -> PointAccountStatus:
    if isinstance(status, PointAccountStatus):
        return status
    try:
        return PointAccountStatus(status)
    except ValueError as exc:
        raise AppError(
            "وضعیت حساب امتیاز نامعتبر است",
            status_code=422,
            error_code="invalid_point_account_status",
        ) from exc


def validate_reward_type(reward_type: RewardType | str) -> RewardType:
    if isinstance(reward_type, RewardType):
        return reward_type
    try:
        return RewardType(reward_type)
    except ValueError as exc:
        raise AppError(
            "نوع پاداش نامعتبر است",
            status_code=422,
            error_code="invalid_reward_type",
        ) from exc


def validate_reward_status(status: RewardStatus | str) -> RewardStatus:
    if isinstance(status, RewardStatus):
        return status
    try:
        return RewardStatus(status)
    except ValueError as exc:
        raise AppError(
            "وضعیت پاداش نامعتبر است",
            status_code=422,
            error_code="invalid_reward_status",
        ) from exc


def validate_campaign_status(status: CampaignStatus | str) -> CampaignStatus:
    if isinstance(status, CampaignStatus):
        return status
    try:
        return CampaignStatus(status)
    except ValueError as exc:
        raise AppError(
            "وضعیت کمپین نامعتبر است",
            status_code=422,
            error_code="invalid_campaign_status",
        ) from exc


def validate_campaign_dates(
    starts_on: date | None, ends_on: date | None
) -> tuple[date | None, date | None]:
    if starts_on and ends_on and ends_on < starts_on:
        raise AppError(
            "تاریخ پایان کمپین نمی‌تواند قبل از تاریخ شروع باشد",
            status_code=422,
            error_code="invalid_campaign_dates",
        )
    return starts_on, ends_on


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


def release_unique_token(value: str, entity_id) -> str:
    """Free unique business keys on soft-delete so codes can be reused."""
    suffix = f"__del__{entity_id.hex[:8]}"
    if value.endswith(suffix):
        return value
    # Keep within typical 50-char code columns
    base = value[: max(0, 50 - len(suffix))]
    return f"{base}{suffix}"


def forbid_direct_balance_mutation(payload: dict) -> None:
    forbidden = {"balance", "points_balance", "available_balance", "ledger_balance"}
    present = forbidden.intersection(payload.keys())
    if present:
        raise AppError(
            "تغییر مستقیم موجودی امتیاز ممنوع است؛ فقط از طریق ledger",
            status_code=422,
            error_code="direct_balance_forbidden",
            details={"fields": sorted(present)},
        )
