"""Job queue for Gremlin Autopilot.

Provides:
- SQLite-based job persistence
- Job lifecycle management
- Job history queries
"""

from __future__ import annotations

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

from g_gremlin.paths import gremlin_path

from .models import JobStatus, PlanAction

logger = logging.getLogger(__name__)

# Default database path
JOBS_DB_PATH = gremlin_path("autopilot", "jobs.db")

# SQL schema
SCHEMA = """
CREATE TABLE IF NOT EXISTS jobs (
    job_id TEXT PRIMARY KEY,
    status TEXT NOT NULL,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL,
    context_hash TEXT,
    message TEXT,
    actions_json TEXT,
    results_json TEXT,
    session_id TEXT
);

CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status);
CREATE INDEX IF NOT EXISTS idx_jobs_created ON jobs(created_at);
CREATE INDEX IF NOT EXISTS idx_jobs_session ON jobs(session_id);
"""


class Job:
    """Represents a job in the queue."""

    def __init__(
        self,
        job_id: str,
        status: JobStatus = JobStatus.PENDING,
        context_hash: str | None = None,
        message: str | None = None,
        actions: list[PlanAction] | None = None,
        results: list[dict[str, Any]] | None = None,
        session_id: str | None = None,
        created_at: datetime | None = None,
        updated_at: datetime | None = None,
    ):
        self.job_id = job_id
        self.status = status
        self.context_hash = context_hash
        self.message = message
        self.actions = actions or []
        self.results = results or []
        self.session_id = session_id
        self.created_at = created_at or datetime.now(timezone.utc)
        self.updated_at = updated_at or datetime.now(timezone.utc)

    def to_dict(self) -> dict[str, Any]:
        """Convert job to dictionary."""
        return {
            "job_id": self.job_id,
            "status": self.status.value if isinstance(self.status, JobStatus) else self.status,
            "context_hash": self.context_hash,
            "message": self.message,
            "actions": [a.model_dump() if hasattr(a, "model_dump") else a for a in self.actions],
            "results": self.results,
            "session_id": self.session_id,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }


class JobQueue:
    """SQLite-based job queue for autopilot."""

    def __init__(self, db_path: Path | None = None):
        """Initialize the job queue.

        Args:
            db_path: Path to the SQLite database file.
        """
        self.db_path = db_path or JOBS_DB_PATH
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._init_db()

    def _init_db(self) -> None:
        """Initialize the database schema."""
        with self._connect() as conn:
            conn.executescript(SCHEMA)

    @contextmanager
    def _connect(self) -> Iterator[sqlite3.Connection]:
        """Context manager for database connections."""
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        try:
            yield conn
            conn.commit()
        except Exception:
            conn.rollback()
            raise
        finally:
            conn.close()

    def create_job(
        self,
        job_id: str,
        actions: list[PlanAction],
        context_hash: str | None = None,
        message: str | None = None,
        session_id: str | None = None,
    ) -> Job:
        """Create a new job.

        Args:
            job_id: Unique job identifier.
            actions: List of actions to execute.
            context_hash: Hash of the sheet context.
            message: Job description or message.
            session_id: Session identifier.

        Returns:
            The created Job object.
        """
        now = datetime.now(timezone.utc)
        actions_json = json.dumps([
            a.model_dump() if hasattr(a, "model_dump") else a
            for a in actions
        ])

        with self._connect() as conn:
            conn.execute(
                """
                INSERT INTO jobs (job_id, status, created_at, updated_at,
                                 context_hash, message, actions_json, session_id)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    job_id,
                    JobStatus.PENDING.value,
                    now.isoformat(),
                    now.isoformat(),
                    context_hash,
                    message,
                    actions_json,
                    session_id,
                ),
            )

        return Job(
            job_id=job_id,
            status=JobStatus.PENDING,
            context_hash=context_hash,
            message=message,
            actions=actions,
            session_id=session_id,
            created_at=now,
            updated_at=now,
        )

    def get_job(self, job_id: str) -> Job | None:
        """Get a job by ID.

        Args:
            job_id: Job identifier.

        Returns:
            Job object if found, None otherwise.
        """
        with self._connect() as conn:
            row = conn.execute(
                "SELECT * FROM jobs WHERE job_id = ?",
                (job_id,),
            ).fetchone()

        if not row:
            return None

        return self._row_to_job(row)

    def update_status(
        self,
        job_id: str,
        status: JobStatus,
        message: str | None = None,
        results: list[dict[str, Any]] | None = None,
    ) -> bool:
        """Update job status.

        Args:
            job_id: Job identifier.
            status: New status.
            message: Optional status message.
            results: Optional execution results.

        Returns:
            True if job was updated, False if not found.
        """
        now = datetime.now(timezone.utc)

        with self._connect() as conn:
            updates = ["status = ?", "updated_at = ?"]
            params: list[Any] = [status.value, now.isoformat()]

            if message is not None:
                updates.append("message = ?")
                params.append(message)

            if results is not None:
                updates.append("results_json = ?")
                params.append(json.dumps(results))

            params.append(job_id)

            cursor = conn.execute(
                f"UPDATE jobs SET {', '.join(updates)} WHERE job_id = ?",
                params,
            )

            return cursor.rowcount > 0

    def list_jobs(
        self,
        status: JobStatus | None = None,
        session_id: str | None = None,
        limit: int = 50,
        offset: int = 0,
    ) -> list[Job]:
        """List jobs with optional filtering.

        Args:
            status: Filter by status.
            session_id: Filter by session.
            limit: Maximum number of jobs to return.
            offset: Number of jobs to skip.

        Returns:
            List of Job objects.
        """
        query = "SELECT * FROM jobs"
        params: list[Any] = []
        conditions = []

        if status:
            conditions.append("status = ?")
            params.append(status.value)

        if session_id:
            conditions.append("session_id = ?")
            params.append(session_id)

        if conditions:
            query += " WHERE " + " AND ".join(conditions)

        query += " ORDER BY created_at DESC LIMIT ? OFFSET ?"
        params.extend([limit, offset])

        with self._connect() as conn:
            rows = conn.execute(query, params).fetchall()

        return [self._row_to_job(row) for row in rows]

    def delete_job(self, job_id: str) -> bool:
        """Delete a job.

        Args:
            job_id: Job identifier.

        Returns:
            True if job was deleted, False if not found.
        """
        with self._connect() as conn:
            cursor = conn.execute(
                "DELETE FROM jobs WHERE job_id = ?",
                (job_id,),
            )
            return cursor.rowcount > 0

    def cleanup_old_jobs(self, days: int = 30) -> int:
        """Delete jobs older than specified days.

        Args:
            days: Age threshold in days.

        Returns:
            Number of jobs deleted.
        """
        cutoff = datetime.now(timezone.utc)
        cutoff_str = cutoff.isoformat()

        # We need to compute the cutoff properly
        from datetime import timedelta

        cutoff = datetime.now(timezone.utc) - timedelta(days=days)
        cutoff_str = cutoff.isoformat()

        with self._connect() as conn:
            cursor = conn.execute(
                "DELETE FROM jobs WHERE created_at < ?",
                (cutoff_str,),
            )
            deleted = cursor.rowcount

        logger.info(f"Cleaned up {deleted} jobs older than {days} days")
        return deleted

    def get_stats(self) -> dict[str, Any]:
        """Get job queue statistics.

        Returns:
            Dictionary with stats.
        """
        with self._connect() as conn:
            # Total count
            total = conn.execute("SELECT COUNT(*) FROM jobs").fetchone()[0]

            # Count by status
            status_counts = {}
            for row in conn.execute(
                "SELECT status, COUNT(*) FROM jobs GROUP BY status"
            ).fetchall():
                status_counts[row[0]] = row[1]

            # Oldest and newest
            oldest = conn.execute(
                "SELECT MIN(created_at) FROM jobs"
            ).fetchone()[0]
            newest = conn.execute(
                "SELECT MAX(created_at) FROM jobs"
            ).fetchone()[0]

        return {
            "total": total,
            "by_status": status_counts,
            "oldest": oldest,
            "newest": newest,
        }

    def _row_to_job(self, row: sqlite3.Row) -> Job:
        """Convert a database row to a Job object."""
        actions = []
        if row["actions_json"]:
            actions_data = json.loads(row["actions_json"])
            for a in actions_data:
                try:
                    actions.append(PlanAction(**a))
                except Exception:
                    actions.append(a)

        results = []
        if row["results_json"]:
            results = json.loads(row["results_json"])

        created_at = None
        if row["created_at"]:
            try:
                created_at = datetime.fromisoformat(row["created_at"].replace("Z", "+00:00"))
            except ValueError:
                pass

        updated_at = None
        if row["updated_at"]:
            try:
                updated_at = datetime.fromisoformat(row["updated_at"].replace("Z", "+00:00"))
            except ValueError:
                pass

        return Job(
            job_id=row["job_id"],
            status=JobStatus(row["status"]),
            context_hash=row["context_hash"],
            message=row["message"],
            actions=actions,
            results=results,
            session_id=row["session_id"],
            created_at=created_at,
            updated_at=updated_at,
        )
