from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Iterable, Literal, TypeGuard, cast

from .types import CustomerWebhookEvent


@dataclass(frozen=True)
class WebhookParseIssue:
    path: str
    message: str


class WebhookParseError(ValueError):
    def __init__(self, issues: list[WebhookParseIssue]):
        super().__init__("Invalid customer webhook payload")
        self.issues = issues


def _is_str(x: Any) -> TypeGuard[str]:
    return isinstance(x, str)


def _require_dict(x: Any, *, path: str, issues: list[WebhookParseIssue]) -> dict[str, Any] | None:
    if not isinstance(x, dict):
        issues.append(WebhookParseIssue(path, "expected object"))
        return None
    return cast(dict[str, Any], x)


def _require_literal_str(
    obj: dict[str, Any],
    key: str,
    *,
    allowed: Iterable[str] | None,
    path: str,
    issues: list[WebhookParseIssue],
) -> str | None:
    value = obj.get(key)
    if not _is_str(value):
        issues.append(WebhookParseIssue(f"{path}.{key}", "expected string"))
        return None
    if allowed is not None and value not in set(allowed):
        issues.append(WebhookParseIssue(f"{path}.{key}", f"expected one of {sorted(set(allowed))}"))
        return None
    return value


def _validate_common_fields(data: dict[str, Any], *, base_path: str, issues: list[WebhookParseIssue]) -> None:
    _require_literal_str(data, "correlationId", allowed=None, path=base_path, issues=issues)
    _require_literal_str(data, "environment", allowed=("TEST", "LIVE"), path=base_path, issues=issues)
    _require_literal_str(data, "destinationNumber", allowed=None, path=base_path, issues=issues)
    _require_literal_str(data, "projectSlug", allowed=None, path=base_path, issues=issues)
    _require_literal_str(data, "content", allowed=None, path=base_path, issues=issues)


def parse_customer_webhook(payload: Any) -> CustomerWebhookEvent:
    issues: list[WebhookParseIssue] = []
    obj = _require_dict(payload, path="$", issues=issues)
    if obj is None:
        raise WebhookParseError(issues)

    event = _require_literal_str(
        obj,
        "event",
        allowed=("message.sent", "message.read", "message.failed", "message.reply"),
        path="$",
        issues=issues,
    )
    data_any = obj.get("data")
    data = _require_dict(data_any, path="$.data", issues=issues)

    if event is None or data is None:
        raise WebhookParseError(issues)

    if event in ("message.sent", "message.read", "message.failed"):
        _require_literal_str(data, "messageId", allowed=None, path="$.data", issues=issues)
        _validate_common_fields(data, base_path="$.data", issues=issues)

        if event == "message.sent":
            _require_literal_str(data, "status", allowed=("SENT",), path="$.data", issues=issues)
            _require_literal_str(data, "sentAt", allowed=None, path="$.data", issues=issues)
        elif event == "message.read":
            _require_literal_str(data, "status", allowed=("READ",), path="$.data", issues=issues)
            _require_literal_str(data, "readAt", allowed=None, path="$.data", issues=issues)
        else:
            _require_literal_str(data, "status", allowed=("FAILED",), path="$.data", issues=issues)
            _require_literal_str(data, "failedAt", allowed=None, path="$.data", issues=issues)
            _require_literal_str(data, "errorMessage", allowed=None, path="$.data", issues=issues)

    if event == "message.reply":
        _require_literal_str(data, "from", allowed=None, path="$.data", issues=issues)
        _validate_common_fields(data, base_path="$.data", issues=issues)
        _require_literal_str(data, "replyContent", allowed=None, path="$.data", issues=issues)
        _require_literal_str(data, "receivedAt", allowed=None, path="$.data", issues=issues)
        _require_literal_str(data, "lastMessageId", allowed=None, path="$.data", issues=issues)

    if issues:
        raise WebhookParseError(issues)

    return cast(CustomerWebhookEvent, obj)


def is_customer_webhook_event(payload: Any) -> TypeGuard[CustomerWebhookEvent]:
    try:
        parse_customer_webhook(payload)
        return True
    except WebhookParseError:
        return False
