"""SwarmSagaWriter — Saga Lifecycle Recorder for the Genesis Cold Ledger.

Manages the full lifecycle of multi-agent swarm executions:
  1. open_saga()             — create a RUNNING saga record
  2. record_proposed_delta() — append one agent's delta (SQL-level JSONB append)
  3. close_saga()            — finalise with resolved_state and terminal status
  4. get_saga()              — delegate to ColdLedger.get_saga()

Design decisions:
  - record_proposed_delta uses a single UPDATE … SET proposed_deltas = proposed_deltas || ?
    so concurrent appends from multiple agents are safe without a read-modify-write race.
  - SwarmSaga is created via ColdLedger.write_saga() so the pool/commit/release
    pattern is centralised in one place.
  - All status validation happens here, not in the ledger — writer owns the lifecycle contract.

Rules enforced (Genesis hardwired):
  - NO SQLite
  - All SQL uses parameterised %s placeholders — never f-strings
  - Connection pool getconn/putconn in try/finally (no leaks) — delegated to ledger
"""

from __future__ import annotations

import json
import uuid
from datetime import datetime, timezone
from typing import Optional

from core.storage.cold_ledger import ColdLedger, SwarmSaga

# Valid terminal statuses for close_saga()
_VALID_CLOSE_STATUSES = frozenset({"COMPLETED", "PARTIAL_FAIL", "FAILED"})

# SQL used by record_proposed_delta — atomically appends one JSON element to the
# proposed_deltas JSONB array without a client-side read-modify-write.
_APPEND_DELTA_SQL = (
    "UPDATE swarm_sagas"
    " SET proposed_deltas = proposed_deltas || %s::jsonb"
    " WHERE saga_id = %s::uuid"
)

# SQL used by close_saga — writes final state and terminal status in one round-trip.
_CLOSE_SAGA_SQL = (
    "UPDATE swarm_sagas"
    " SET resolved_state = %s,"
    "     status = %s"
    " WHERE saga_id = %s::uuid"
)


class SwarmSagaWriter:
    """Lifecycle manager for swarm sagas stored in the Genesis Cold Ledger.

    Args:
        cold_ledger: A fully initialised :class:`ColdLedger` instance.
    """

    def __init__(self, cold_ledger: ColdLedger) -> None:
        self.ledger = cold_ledger

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def open_saga(self, session_id: str, orchestrator_dag: dict) -> str:
        """Create a new saga record with status RUNNING and empty proposed_deltas.

        Args:
            session_id:       UUID string of the owning session.
            orchestrator_dag: DAG descriptor produced by the orchestrator.

        Returns:
            The new saga_id (UUID4 string).
        """
        saga_id = str(uuid.uuid4())
        now = datetime.now(tz=timezone.utc)

        saga = SwarmSaga(
            saga_id=saga_id,
            session_id=session_id,
            orchestrator_dag=orchestrator_dag,
            proposed_deltas=[],
            resolved_state=None,
            status="RUNNING",
            created_at=now,
        )
        self.ledger.write_saga(saga)
        return saga_id

    def record_proposed_delta(
        self, saga_id: str, agent_id: str, delta: dict
    ) -> None:
        """Append one agent's proposed delta to the saga's proposed_deltas JSONB array.

        Uses a single SQL UPDATE with JSONB concatenation (``||``) so concurrent
        calls from multiple agents are safe without a client-side read-modify-write.

        Each stored element is:
        ``{"agent_id": <str>, "delta": <dict>, "submitted_at": <ISO 8601 str>}``

        Args:
            saga_id:  UUID string of the target saga.
            agent_id: Identifier of the submitting agent.
            delta:    Arbitrary JSON-serialisable dict describing the proposed change.
        """
        submitted_at = datetime.now(tz=timezone.utc).isoformat()
        element = json.dumps(
            [{"agent_id": agent_id, "delta": delta, "submitted_at": submitted_at}]
        )

        conn = self.ledger._acquire()
        try:
            with conn.cursor() as cur:
                cur.execute(_APPEND_DELTA_SQL, (element, saga_id))
            conn.commit()
        finally:
            self.ledger._release(conn)

    def close_saga(
        self, saga_id: str, resolved_state: dict, status: str
    ) -> None:
        """Finalise a saga with its resolved state and terminal status.

        Args:
            saga_id:        UUID string of the saga to close.
            resolved_state: Final merged state dict (JSON-serialisable).
            status:         One of ``"COMPLETED"``, ``"PARTIAL_FAIL"``, ``"FAILED"``.

        Raises:
            ValueError: If *status* is not one of the three valid values.
        """
        if status not in _VALID_CLOSE_STATUSES:
            raise ValueError(
                f"Invalid saga status {status!r}. "
                f"Must be one of: {sorted(_VALID_CLOSE_STATUSES)}"
            )

        resolved_json = json.dumps(resolved_state)

        conn = self.ledger._acquire()
        try:
            with conn.cursor() as cur:
                cur.execute(_CLOSE_SAGA_SQL, (resolved_json, status, saga_id))
            conn.commit()
        finally:
            self.ledger._release(conn)

    def get_saga(self, saga_id: str) -> Optional[SwarmSaga]:
        """Retrieve a saga by ID. Delegates to ColdLedger.get_saga().

        Args:
            saga_id: UUID string of the saga to retrieve.

        Returns:
            A :class:`SwarmSaga` instance, or ``None`` if not found.
        """
        return self.ledger.get_saga(saga_id)


# VERIFICATION_STAMP
# Story: 5.04
# Verified By: parallel-builder
# Verified At: 2026-02-25
# Tests: 14/14
# Coverage: 100%
