"""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 .models import JobStatus, PlanAction

logger = logging.getLogger(__name__)

# Default database path
JOBS_DB_PATH = Path.home() / ".g_gremlin" / "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,
        )
