"""Durable job queue for control-plane execution."""

from __future__ import annotations

import json
import sqlite3
from contextlib import contextmanager
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Iterator


DEFAULT_MAX_ATTEMPTS = 5
DEFAULT_VISIBILITY_TIMEOUT_SECONDS = 120
DEFAULT_BACKOFF_SECONDS_BASE = 15
DEFAULT_BACKOFF_SECONDS_MAX = 600


def control_plane_job_queue_db_path(state_dir: Path | str) -> Path:
    return Path(state_dir) / "db" / "control_plane_job_queue.sqlite"


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


def _utc_now_iso() -> str:
    return _utc_now().isoformat()


def _parse_iso(value: str | None) -> datetime | None:
    if not value:
        return None
    try:
        return datetime.fromisoformat(str(value).replace("Z", "+00:00"))
    except Exception:
        return None


def _to_json(payload: dict[str, Any]) -> str:
    return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True)


def _from_json(raw: str | None) -> dict[str, Any]:
    if not raw:
        return {}
    try:
        payload = json.loads(raw)
    except Exception:
        return {}
    return payload if isinstance(payload, dict) else {}


class ControlPlaneJobQueue:
    """SQLite-backed queue with lease/retry/dead-letter semantics."""

    def __init__(self, db_path: Path | str):
        self.db_path = Path(db_path).resolve()
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._init_db()

    @contextmanager
    def _connect(self) -> Iterator[sqlite3.Connection]:
        conn = sqlite3.connect(self.db_path, timeout=10)
        conn.row_factory = sqlite3.Row
        conn.execute("PRAGMA journal_mode=WAL")
        conn.execute("PRAGMA busy_timeout=10000")
        try:
            yield conn
            conn.commit()
        except Exception:
            conn.rollback()
            raise
        finally:
            conn.close()

    def _init_db(self) -> None:
        with self._connect() as conn:
            conn.executescript(
                """
                CREATE TABLE IF NOT EXISTS control_plane_job_queue (
                    job_id TEXT PRIMARY KEY,
                    job_type TEXT NOT NULL,
                    actor TEXT NOT NULL,
                    payload_json TEXT NOT NULL,
                    status TEXT NOT NULL,
                    attempts INTEGER NOT NULL DEFAULT 0,
                    max_attempts INTEGER NOT NULL DEFAULT 5,
                    leased_until TEXT,
                    available_at TEXT NOT NULL,
                    last_error TEXT,
                    created_at TEXT NOT NULL,
                    updated_at TEXT NOT NULL,
                    processed_at TEXT
                );

                CREATE INDEX IF NOT EXISTS idx_cp_job_queue_status_available
                    ON control_plane_job_queue(status, available_at ASC, created_at ASC, job_id ASC);
                """
            )

    def enqueue_job(
        self,
        *,
        job_id: str,
        job_type: str,
        payload: dict[str, Any],
        actor: str,
        max_attempts: int = DEFAULT_MAX_ATTEMPTS,
    ) -> dict[str, Any]:
        now = _utc_now_iso()
        normalized_job_id = str(job_id or "").strip()
        if not normalized_job_id:
            raise ValueError("job_id is required")
        with self._connect() as conn:
            conn.execute(
                """
                INSERT INTO control_plane_job_queue (
                    job_id,
                    job_type,
                    actor,
                    payload_json,
                    status,
                    attempts,
                    max_attempts,
                    leased_until,
                    available_at,
                    last_error,
                    created_at,
                    updated_at,
                    processed_at
                ) VALUES (?, ?, ?, ?, 'pending', 0, ?, NULL, ?, NULL, ?, ?, NULL)
                """,
                (
                    normalized_job_id,
                    str(job_type or "").strip().lower() or "generate",
                    str(actor or "").strip() or "system",
                    _to_json(payload if isinstance(payload, dict) else {}),
                    max(1, int(max_attempts)),
                    now,
                    now,
                    now,
                ),
            )
        row = self.get_job(normalized_job_id)
        if row is None:
            raise RuntimeError("queued job could not be loaded")
        return row

    def get_job(self, job_id: str) -> dict[str, Any] | None:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT * FROM control_plane_job_queue WHERE job_id = ? LIMIT 1",
                (str(job_id or "").strip(),),
            ).fetchone()
        return self._row_to_job(row)

    def list_jobs(self, *, status: str | None = None, limit: int = 100) -> list[dict[str, Any]]:
        bounded_limit = min(max(int(limit), 1), 500)
        with self._connect() as conn:
            if status:
                rows = conn.execute(
                    """
                    SELECT *
                    FROM control_plane_job_queue
                    WHERE status = ?
                    ORDER BY created_at DESC, job_id DESC
                    LIMIT ?
                    """,
                    (str(status).strip().lower(), bounded_limit),
                ).fetchall()
            else:
                rows = conn.execute(
                    """
                    SELECT *
                    FROM control_plane_job_queue
                    ORDER BY created_at DESC, job_id DESC
                    LIMIT ?
                    """,
                    (bounded_limit,),
                ).fetchall()
        return [item for item in (self._row_to_job(row) for row in rows) if item is not None]

    def queue_counts(self) -> dict[str, int]:
        with self._connect() as conn:
            rows = conn.execute(
                """
                SELECT status, COUNT(*) AS count
                FROM control_plane_job_queue
                GROUP BY status
                """
            ).fetchall()
        counts = {str(row["status"]): int(row["count"]) for row in rows}
        return {
            "pending": counts.get("pending", 0),
            "processing": counts.get("processing", 0),
            "succeeded": counts.get("succeeded", 0),
            "dead_letter": counts.get("dead_letter", 0),
        }

    def lease_next(
        self,
        *,
        visibility_timeout_seconds: int = DEFAULT_VISIBILITY_TIMEOUT_SECONDS,
    ) -> dict[str, Any] | None:
        with self._connect() as conn:
            now = _utc_now()
            now_iso = now.isoformat()
            row = conn.execute(
                """
                SELECT job_id
                FROM control_plane_job_queue
                WHERE attempts < max_attempts
                  AND available_at <= ?
                  AND (
                        status = 'pending'
                        OR (status = 'processing' AND (leased_until IS NULL OR leased_until < ?))
                      )
                ORDER BY available_at ASC, created_at ASC, job_id ASC
                LIMIT 1
                """,
                (now_iso, now_iso),
            ).fetchone()
            if row is None:
                return None
            return self._lease_job_locked(
                conn,
                job_id=str(row["job_id"]),
                visibility_timeout_seconds=visibility_timeout_seconds,
            )

    def _lease_job_locked(
        self,
        conn: sqlite3.Connection,
        *,
        job_id: str,
        visibility_timeout_seconds: int,
    ) -> dict[str, Any] | None:
        if not job_id:
            return None
        now = _utc_now()
        now_iso = now.isoformat()
        lease_until = (now + timedelta(seconds=max(1, int(visibility_timeout_seconds)))).isoformat()
        cursor = conn.execute(
            """
            UPDATE control_plane_job_queue
            SET
                status = 'processing',
                attempts = attempts + 1,
                leased_until = ?,
                updated_at = ?
            WHERE job_id = ?
              AND attempts < max_attempts
              AND available_at <= ?
              AND (
                    status = 'pending'
                    OR (status = 'processing' AND (leased_until IS NULL OR leased_until < ?))
                  )
            """,
            (lease_until, now_iso, job_id, now_iso, now_iso),
        )
        if (cursor.rowcount or 0) <= 0:
            return None
        row = conn.execute(
            "SELECT * FROM control_plane_job_queue WHERE job_id = ? LIMIT 1",
            (job_id,),
        ).fetchone()
        return self._row_to_job(row)

    def mark_succeeded(self, *, job_id: str) -> None:
        now = _utc_now_iso()
        with self._connect() as conn:
            conn.execute(
                """
                UPDATE control_plane_job_queue
                SET
                    status = 'succeeded',
                    leased_until = NULL,
                    last_error = NULL,
                    processed_at = ?,
                    updated_at = ?
                WHERE job_id = ?
                """,
                (now, now, str(job_id or "").strip()),
            )

    def mark_failed(
        self,
        *,
        job_id: str,
        error: str,
        retryable: bool = True,
        backoff_seconds_base: int = DEFAULT_BACKOFF_SECONDS_BASE,
        backoff_seconds_max: int = DEFAULT_BACKOFF_SECONDS_MAX,
    ) -> str:
        row = self.get_job(job_id)
        if row is None:
            return "missing"
        attempts = int(row.get("attempts") or 0)
        max_attempts = int(row.get("max_attempts") or DEFAULT_MAX_ATTEMPTS)
        should_dead_letter = (not retryable) or attempts >= max_attempts
        now = _utc_now()
        now_iso = now.isoformat()
        if should_dead_letter:
            with self._connect() as conn:
                conn.execute(
                    """
                    UPDATE control_plane_job_queue
                    SET
                        status = 'dead_letter',
                        leased_until = NULL,
                        last_error = ?,
                        processed_at = ?,
                        updated_at = ?
                    WHERE job_id = ?
                    """,
                    (str(error or "").strip()[:4000], now_iso, now_iso, str(job_id or "").strip()),
                )
            return "dead_letter"

        base = max(0, int(backoff_seconds_base))
        cap = max(base, int(backoff_seconds_max))
        delay = min(base * (2 ** max(attempts - 1, 0)), cap) if base > 0 else 0
        available_at = (now + timedelta(seconds=delay)).isoformat()
        with self._connect() as conn:
            conn.execute(
                """
                UPDATE control_plane_job_queue
                SET
                    status = 'pending',
                    leased_until = NULL,
                    available_at = ?,
                    last_error = ?,
                    updated_at = ?
                WHERE job_id = ?
                """,
                (
                    available_at,
                    str(error or "").strip()[:4000],
                    now_iso,
                    str(job_id or "").strip(),
                ),
            )
        return "pending"

    def retry_job(self, *, job_id: str) -> dict[str, Any] | None:
        now = _utc_now_iso()
        with self._connect() as conn:
            cursor = conn.execute(
                """
                UPDATE control_plane_job_queue
                SET
                    status = 'pending',
                    attempts = 0,
                    leased_until = NULL,
                    available_at = ?,
                    last_error = NULL,
                    processed_at = NULL,
                    updated_at = ?
                WHERE job_id = ?
                """,
                (now, now, str(job_id or "").strip()),
            )
            if (cursor.rowcount or 0) <= 0:
                return None
        return self.get_job(job_id)

    def _row_to_job(self, row: sqlite3.Row | None) -> dict[str, Any] | None:
        if row is None:
            return None
        payload = {
            "job_id": str(row["job_id"]),
            "job_type": str(row["job_type"]),
            "actor": str(row["actor"]),
            "payload": _from_json(row["payload_json"]),
            "status": str(row["status"]),
            "attempts": int(row["attempts"] or 0),
            "max_attempts": int(row["max_attempts"] or DEFAULT_MAX_ATTEMPTS),
            "leased_until": row["leased_until"],
            "available_at": row["available_at"],
            "last_error": row["last_error"],
            "created_at": row["created_at"],
            "updated_at": row["updated_at"],
            "processed_at": row["processed_at"],
        }
        leased_until = _parse_iso(payload.get("leased_until"))
        available_at = _parse_iso(payload.get("available_at"))
        if leased_until is not None:
            payload["leased_until_epoch"] = int(leased_until.timestamp())
        if available_at is not None:
            payload["available_at_epoch"] = int(available_at.timestamp())
        return payload


__all__ = [
    "ControlPlaneJobQueue",
    "DEFAULT_BACKOFF_SECONDS_BASE",
    "DEFAULT_BACKOFF_SECONDS_MAX",
    "DEFAULT_MAX_ATTEMPTS",
    "DEFAULT_VISIBILITY_TIMEOUT_SECONDS",
    "control_plane_job_queue_db_path",
]
