"""Localization & media-ref validators — Phase 11.5."""
from __future__ import annotations

import re
from typing import Any

from app.models.types import (
    LocalizationStatus,
    LocalizationTarget,
    MediaKind,
    MediaRefStatus,
    TextDirection,
)
from app.validators.foundation import ValidationError, validate_non_empty
from shared.exceptions import AppError

LOCALE_RE = re.compile(r"^[a-z]{2}([_-][A-Za-z0-9]{2,8})?$")

LOCALIZATION_STATUS_TRANSITIONS: dict[
    LocalizationStatus, frozenset[LocalizationStatus]
] = {
    LocalizationStatus.DRAFT: frozenset(
        {LocalizationStatus.ACTIVE, LocalizationStatus.ARCHIVED}
    ),
    LocalizationStatus.ACTIVE: frozenset(
        {LocalizationStatus.DRAFT, LocalizationStatus.ARCHIVED}
    ),
    LocalizationStatus.ARCHIVED: frozenset(),
}

MEDIA_REF_STATUS_TRANSITIONS: dict[MediaRefStatus, frozenset[MediaRefStatus]] = {
    MediaRefStatus.DRAFT: frozenset({MediaRefStatus.ACTIVE, MediaRefStatus.ARCHIVED}),
    MediaRefStatus.ACTIVE: frozenset({MediaRefStatus.DRAFT, MediaRefStatus.ARCHIVED}),
    MediaRefStatus.ARCHIVED: frozenset(),
}

_FORBIDDEN_MEDIA_PAYLOAD_KEYS = frozenset(
    {
        "binary",
        "blob",
        "bytes",
        "content_bytes",
        "file_bytes",
        "base64",
        "data_uri",
        "raw_content",
    }
)


def validate_locale_code(locale: str) -> str:
    locale = validate_non_empty(locale, "locale").strip().replace("_", "-")
    if not LOCALE_RE.match(locale):
        raise ValidationError(
            "locale نامعتبر است",
            {"field": "locale", "pattern": LOCALE_RE.pattern},
        )
    return locale


def validate_text_direction(
    value: TextDirection | str | None, *, required: bool = True
) -> TextDirection | None:
    if value is None:
        if required:
            raise ValidationError(
                "text_direction الزامی است", {"field": "text_direction"}
            )
        return None
    if isinstance(value, TextDirection):
        return value
    try:
        return TextDirection(str(value).strip().lower())
    except ValueError as exc:
        raise ValidationError(
            "text_direction نامعتبر است",
            {
                "field": "text_direction",
                "allowed": [d.value for d in TextDirection],
            },
        ) from exc


def validate_localization_target(
    value: LocalizationTarget | str,
) -> LocalizationTarget:
    if isinstance(value, LocalizationTarget):
        return value
    try:
        return LocalizationTarget(str(value).strip().lower())
    except ValueError as exc:
        raise ValidationError(
            "target_type نامعتبر است",
            {
                "field": "target_type",
                "allowed": [t.value for t in LocalizationTarget],
            },
        ) from exc


def validate_media_kind(value: MediaKind | str) -> MediaKind:
    if isinstance(value, MediaKind):
        return value
    try:
        return MediaKind(str(value).strip().lower())
    except ValueError as exc:
        raise ValidationError(
            "kind نامعتبر است",
            {"field": "kind", "allowed": [k.value for k in MediaKind]},
        ) from exc


def validate_storage_file_ref(file_ref: str) -> str:
    cleaned = validate_non_empty(file_ref, "storage_file_ref")
    if len(cleaned) > 255:
        raise ValidationError(
            "storage_file_ref بیش از حد طولانی است",
            {"field": "storage_file_ref", "max_length": 255},
        )
    # Reject inline binary payloads masquerading as refs.
    lowered = cleaned.lower()
    if lowered.startswith("data:") or "base64," in lowered:
        raise ValidationError(
            "binary media در experience_db مجاز نیست؛ فقط Storage ref",
            {"field": "storage_file_ref"},
        )
    return cleaned


def ensure_no_binary_media_payload(metadata_json: dict | None) -> dict | None:
    if metadata_json is None:
        return None
    if not isinstance(metadata_json, dict):
        raise ValidationError(
            "metadata_json باید object باشد", {"field": "metadata_json"}
        )
    banned = _FORBIDDEN_MEDIA_PAYLOAD_KEYS.intersection(metadata_json.keys())
    if banned:
        raise ValidationError(
            "payload باینری در media ref مجاز نیست",
            {"field": "metadata_json", "forbidden_keys": sorted(banned)},
        )
    return metadata_json


def ensure_localization_status_transition(
    current: LocalizationStatus, target: LocalizationStatus
) -> None:
    if current == target:
        return
    allowed = LOCALIZATION_STATUS_TRANSITIONS.get(current, frozenset())
    if target not in allowed:
        raise AppError(
            "گذار وضعیت localization مجاز نیست",
            error_code="invalid_localization_status_transition",
            status_code=409,
            details={"current": current.value, "target": target.value},
        )


def ensure_media_ref_status_transition(
    current: MediaRefStatus, target: MediaRefStatus
) -> None:
    if current == target:
        return
    allowed = MEDIA_REF_STATUS_TRANSITIONS.get(current, frozenset())
    if target not in allowed:
        raise AppError(
            "گذار وضعیت media ref مجاز نیست",
            error_code="invalid_media_ref_status_transition",
            status_code=409,
            details={"current": current.value, "target": target.value},
        )


def ensure_content_json(value: Any) -> dict | None:
    if value is None:
        return None
    if not isinstance(value, dict):
        raise ValidationError(
            "content_json باید object باشد", {"field": "content_json"}
        )
    return value
