"""Short-lived deploy ticket storage for Flow Builder beta deploys."""

from __future__ import annotations

import hashlib
import json
import secrets
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from threading import RLock
from typing import Any, Optional


def _utcnow() -> datetime:
    return datetime.now(timezone.utc)


def _normalize_payload(value: Any) -> Any:
    if hasattr(value, "model_dump"):
        return value.model_dump(mode="json", exclude_none=True)
    if isinstance(value, dict):
        return {k: _normalize_payload(v) for k, v in value.items()}
    if isinstance(value, (list, tuple)):
        return [_normalize_payload(v) for v in value]
    return value


def _payload_hash(value: Any) -> str:
    payload = json.dumps(
        _normalize_payload(value),
        sort_keys=True,
        separators=(",", ":"),
    )
    return hashlib.sha256(payload.encode("utf-8")).hexdigest()


@dataclass(frozen=True)
class FlowDeployTicket:
    ticket_id: str
    account_id: str
    flow_api_name: str
    flow_xml: str
    xml_hash: str
    plan_hash: str
    context_hash: str
    audit_hash: str
    audit_requires_acknowledgment: bool
    audit_acknowledged: bool
    issued_at: datetime
    expires_at: datetime


class FlowDeployTicketStore:
    """In-memory beta ticket store. Tickets are short-lived and single-use."""

    _tickets: dict[str, FlowDeployTicket] = {}
    _lock = RLock()
    _ttl_seconds = 15 * 60

    @classmethod
    def issue_ticket(
        cls,
        *,
        account_id: str,
        flow_api_name: str,
        flow_xml: str,
        plan: Any,
        context: Any,
        audit: Any,
        audit_acknowledged: bool,
    ) -> FlowDeployTicket:
        now = _utcnow()
        ticket = FlowDeployTicket(
            ticket_id=secrets.token_urlsafe(24),
            account_id=str(account_id),
            flow_api_name=flow_api_name,
            flow_xml=flow_xml,
            xml_hash=_payload_hash(flow_xml),
            plan_hash=_payload_hash(plan),
            context_hash=_payload_hash(context),
            audit_hash=_payload_hash(audit),
            audit_requires_acknowledgment=bool(
                audit and getattr(audit, "requires_acknowledgment", False)
            ),
            audit_acknowledged=bool(audit_acknowledged),
            issued_at=now,
            expires_at=now + timedelta(seconds=cls._ttl_seconds),
        )
        with cls._lock:
            cls._purge_expired_locked(now)
            cls._tickets[ticket.ticket_id] = ticket
        return ticket

    @classmethod
    def consume_ticket(
        cls,
        ticket_id: str,
        *,
        account_id: str,
    ) -> tuple[Optional[FlowDeployTicket], Optional[str]]:
        now = _utcnow()
        with cls._lock:
            cls._purge_expired_locked(now)
            ticket = cls._tickets.get(ticket_id)
            if ticket is None:
                return None, "missing"
            if ticket.account_id != str(account_id):
                return None, "account_mismatch"
            del cls._tickets[ticket_id]
            return ticket, None

    @classmethod
    def clear_all(cls) -> None:
        with cls._lock:
            cls._tickets.clear()

    @classmethod
    def _purge_expired_locked(cls, now: Optional[datetime] = None) -> None:
        current = now or _utcnow()
        expired = [
            ticket_id
            for ticket_id, ticket in cls._tickets.items()
            if ticket.expires_at <= current
        ]
        for ticket_id in expired:
            cls._tickets.pop(ticket_id, None)
