"""Publishing / SEO / PWA validators — Phase 11.7."""
from __future__ import annotations

from typing import Any

from app.models.types import (
    DomainEdgeStatus,
    PublishReleaseStatus,
    PublishTarget,
    PwaStatus,
    RobotsStatus,
    SeoStatus,
    SeoTarget,
    SitemapStatus,
)
from app.validators.foundation import ValidationError, validate_non_empty
from shared.exceptions import AppError

_FORBIDDEN_EDGE_KEYS = frozenset(
    {
        "nginx_config",
        "ssl_private_key",
        "cert_pem",
        "private_key",
        "dns_engine",
        "ssl_engine",
        "edge_engine",
    }
)

PUBLISH_STATUS_TRANSITIONS: dict[
    PublishReleaseStatus, frozenset[PublishReleaseStatus]
] = {
    PublishReleaseStatus.PENDING: frozenset(
        {
            PublishReleaseStatus.PUBLISHED,
            PublishReleaseStatus.FAILED,
            PublishReleaseStatus.ROLLED_BACK,
        }
    ),
    PublishReleaseStatus.PUBLISHED: frozenset({PublishReleaseStatus.ROLLED_BACK}),
    PublishReleaseStatus.FAILED: frozenset(
        {PublishReleaseStatus.PENDING, PublishReleaseStatus.ROLLED_BACK}
    ),
    PublishReleaseStatus.ROLLED_BACK: frozenset(),
}

EDGE_STATUS_TRANSITIONS: dict[DomainEdgeStatus, frozenset[DomainEdgeStatus]] = {
    DomainEdgeStatus.PENDING: frozenset(
        {
            DomainEdgeStatus.PROVISIONING,
            DomainEdgeStatus.READY,
            DomainEdgeStatus.FAILED,
            DomainEdgeStatus.RETIRED,
        }
    ),
    DomainEdgeStatus.PROVISIONING: frozenset(
        {DomainEdgeStatus.READY, DomainEdgeStatus.FAILED, DomainEdgeStatus.RETIRED}
    ),
    DomainEdgeStatus.READY: frozenset(
        {DomainEdgeStatus.FAILED, DomainEdgeStatus.RETIRED}
    ),
    DomainEdgeStatus.FAILED: frozenset(
        {DomainEdgeStatus.PENDING, DomainEdgeStatus.PROVISIONING, DomainEdgeStatus.RETIRED}
    ),
    DomainEdgeStatus.RETIRED: frozenset(),
}

_SIMPLE = {
    SeoStatus: {
        SeoStatus.DRAFT: frozenset({SeoStatus.ACTIVE, SeoStatus.ARCHIVED}),
        SeoStatus.ACTIVE: frozenset({SeoStatus.DRAFT, SeoStatus.ARCHIVED}),
        SeoStatus.ARCHIVED: frozenset(),
    },
    SitemapStatus: {
        SitemapStatus.DRAFT: frozenset({SitemapStatus.ACTIVE, SitemapStatus.ARCHIVED}),
        SitemapStatus.ACTIVE: frozenset({SitemapStatus.DRAFT, SitemapStatus.ARCHIVED}),
        SitemapStatus.ARCHIVED: frozenset(),
    },
    RobotsStatus: {
        RobotsStatus.DRAFT: frozenset({RobotsStatus.ACTIVE, RobotsStatus.ARCHIVED}),
        RobotsStatus.ACTIVE: frozenset({RobotsStatus.DRAFT, RobotsStatus.ARCHIVED}),
        RobotsStatus.ARCHIVED: frozenset(),
    },
    PwaStatus: {
        PwaStatus.DRAFT: frozenset({PwaStatus.READY, PwaStatus.ARCHIVED}),
        PwaStatus.READY: frozenset({PwaStatus.DRAFT, PwaStatus.ARCHIVED}),
        PwaStatus.ARCHIVED: frozenset(),
    },
}


def ensure_json_object(value: Any, field: str) -> dict | None:
    if value is None:
        return None
    if not isinstance(value, dict):
        raise ValidationError(f"{field} باید object باشد", {"field": field})
    banned = _FORBIDDEN_EDGE_KEYS.intersection(value.keys())
    if banned:
        raise ValidationError(
            "کلیدهای مالکیت DNS/SSL/edge در payload مجاز نیست",
            {"field": field, "forbidden_keys": sorted(banned)},
        )
    return value


def validate_external_ref(value: str | None, field: str) -> str | None:
    if value is None:
        return None
    cleaned = validate_non_empty(value, field)
    if len(cleaned) > 255:
        raise ValidationError(
            f"{field} بیش از حد طولانی است", {"field": field, "max_length": 255}
        )
    lowered = cleaned.lower()
    if lowered.startswith("data:") or "-----begin" in lowered:
        raise ValidationError(
            "محتوای باینری/PEM در experience_db مجاز نیست؛ فقط ref",
            {"field": field},
        )
    return cleaned


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


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


def ensure_publish_status_transition(
    current: PublishReleaseStatus, target: PublishReleaseStatus
) -> None:
    if current == target:
        return
    if target not in PUBLISH_STATUS_TRANSITIONS.get(current, frozenset()):
        raise AppError(
            "گذار وضعیت انتشار مجاز نیست",
            error_code="invalid_publish_status_transition",
            status_code=409,
            details={"current": current.value, "target": target.value},
        )


def ensure_edge_status_transition(
    current: DomainEdgeStatus, target: DomainEdgeStatus
) -> None:
    if current == target:
        return
    if target not in EDGE_STATUS_TRANSITIONS.get(current, frozenset()):
        raise AppError(
            "گذار وضعیت edge دامنه مجاز نیست",
            error_code="invalid_domain_edge_status_transition",
            status_code=409,
            details={"current": current.value, "target": target.value},
        )


def ensure_simple_status_transition(current, target, *, label: str) -> None:
    if current == target:
        return
    table = _SIMPLE.get(type(current), {})
    if target not in table.get(current, frozenset()):
        raise AppError(
            f"گذار وضعیت {label} مجاز نیست",
            error_code=f"invalid_{label}_status_transition",
            status_code=409,
            details={"current": current.value, "target": target.value},
        )
