"""Local SQLite database for job metadata caching.

Provides write-through caching: every API response is transparently
persisted locally so users have a backup of their job history.
"""

from __future__ import annotations

import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import aiosqlite

from manta_client.config import config_manager

_CREATE_TABLE = """
CREATE TABLE IF NOT EXISTS jobs (
    job_id        TEXT PRIMARY KEY,
    user_id       TEXT,
    status        TEXT NOT NULL DEFAULT 'pending',
    prompt        TEXT,
    model         TEXT,
    title         TEXT,
    summary       TEXT,
    result        TEXT,
    error         TEXT,
    artifacts     TEXT,
    session_id    TEXT,
    parent_job_id TEXT,
    workspace_id  INTEGER,
    skill_slugs   TEXT,
    created_at    TEXT,
    updated_at    TEXT,
    completed_at  TEXT,
    session_synced INTEGER NOT NULL DEFAULT 0,
    synced_at     TEXT
);
"""

# Columns to extract from API response dicts
_JOB_COLUMNS = (
    "job_id",
    "user_id",
    "status",
    "prompt",
    "model",
    "title",
    "summary",
    "result",
    "error",
    "artifacts",
    "session_id",
    "parent_job_id",
    "workspace_id",
    "skill_slugs",
    "created_at",
    "updated_at",
    "completed_at",
)


def _db_path() -> Path:
    """Return path to the local SQLite database."""
    return config_manager.config_dir / "manta.db"


def session_file_path(session_id: str) -> Path:
    """Return path for a local session JSONL file."""
    return config_manager.config_dir / "sessions" / f"{session_id}.jsonl"


async def _connect() -> aiosqlite.Connection:
    """Open a connection and ensure the schema exists."""
    config_manager.ensure_config_dir()
    db = await aiosqlite.connect(_db_path())
    await db.execute(_CREATE_TABLE)
    await db.commit()
    return db


def _extract(job: dict[str, Any]) -> dict[str, Any]:
    """Extract relevant columns from an API job dict."""
    row: dict[str, Any] = {}
    for col in _JOB_COLUMNS:
        val = job.get(col)
        if col in ("artifacts", "skill_slugs") and val is not None:
            val = json.dumps(val)
        row[col] = val
    return row


async def upsert_job(job: dict[str, Any]) -> None:
    """Insert or update a single job, preserving the session_synced flag."""
    row = _extract(job)
    now = datetime.now(timezone.utc).isoformat()

    db = await _connect()
    try:
        await db.execute(
            """
            INSERT INTO jobs (
                job_id, user_id, status, prompt, model, title, summary,
                result, error, artifacts, session_id, parent_job_id,
                workspace_id, skill_slugs, created_at, updated_at, completed_at,
                session_synced, synced_at
            ) VALUES (
                :job_id, :user_id, :status, :prompt, :model, :title, :summary,
                :result, :error, :artifacts, :session_id, :parent_job_id,
                :workspace_id, :skill_slugs, :created_at, :updated_at, :completed_at,
                0, :synced_at
            )
            ON CONFLICT(job_id) DO UPDATE SET
                user_id       = :user_id,
                status        = :status,
                prompt        = COALESCE(:prompt, jobs.prompt),
                model         = COALESCE(:model, jobs.model),
                title         = COALESCE(:title, jobs.title),
                summary       = COALESCE(:summary, jobs.summary),
                result        = COALESCE(:result, jobs.result),
                error         = :error,
                artifacts     = COALESCE(:artifacts, jobs.artifacts),
                session_id    = COALESCE(:session_id, jobs.session_id),
                parent_job_id = COALESCE(:parent_job_id, jobs.parent_job_id),
                workspace_id  = COALESCE(:workspace_id, jobs.workspace_id),
                skill_slugs      = COALESCE(:skill_slugs, jobs.skill_slugs),
                created_at    = COALESCE(:created_at, jobs.created_at),
                updated_at    = COALESCE(:updated_at, jobs.updated_at),
                completed_at  = COALESCE(:completed_at, jobs.completed_at),
                synced_at     = :synced_at
            """,
            {**row, "synced_at": now},
        )
        await db.commit()
    finally:
        await db.close()


async def upsert_jobs(jobs: list[dict[str, Any]]) -> None:
    """Batch upsert multiple jobs in a single transaction."""
    if not jobs:
        return
    now = datetime.now(timezone.utc).isoformat()

    db = await _connect()
    try:
        for job in jobs:
            row = _extract(job)
            await db.execute(
                """
                INSERT INTO jobs (
                    job_id, user_id, status, prompt, model, title, summary,
                    result, error, artifacts, session_id, parent_job_id,
                    workspace_id, skill_slugs, created_at, updated_at, completed_at,
                    session_synced, synced_at
                ) VALUES (
                    :job_id, :user_id, :status, :prompt, :model, :title, :summary,
                    :result, :error, :artifacts, :session_id, :parent_job_id,
                    :workspace_id, :skill_slugs, :created_at, :updated_at, :completed_at,
                    0, :synced_at
                )
                ON CONFLICT(job_id) DO UPDATE SET
                    user_id       = :user_id,
                    status        = :status,
                    prompt        = COALESCE(:prompt, jobs.prompt),
                    model         = COALESCE(:model, jobs.model),
                    title         = COALESCE(:title, jobs.title),
                    summary       = COALESCE(:summary, jobs.summary),
                    result        = COALESCE(:result, jobs.result),
                    error         = :error,
                    artifacts     = COALESCE(:artifacts, jobs.artifacts),
                    session_id    = COALESCE(:session_id, jobs.session_id),
                    parent_job_id = COALESCE(:parent_job_id, jobs.parent_job_id),
                    workspace_id  = COALESCE(:workspace_id, jobs.workspace_id),
                    skill_slugs      = COALESCE(:skill_slugs, jobs.skill_slugs),
                    created_at    = COALESCE(:created_at, jobs.created_at),
                    updated_at    = COALESCE(:updated_at, jobs.updated_at),
                    completed_at  = COALESCE(:completed_at, jobs.completed_at),
                    synced_at     = :synced_at
                """,
                {**row, "synced_at": now},
            )
        await db.commit()
    finally:
        await db.close()


async def get_local_job(job_id: str) -> dict[str, Any] | None:
    """Fetch a single job from local DB. Returns None if not found."""
    db = await _connect()
    try:
        db.row_factory = aiosqlite.Row
        cursor = await db.execute("SELECT * FROM jobs WHERE job_id = ?", (job_id,))
        row = await cursor.fetchone()
        if row is None:
            return None
        result = dict(row)
        # Parse JSON fields back to objects
        for json_field in ("artifacts", "skill_slugs"):
            if result.get(json_field):
                try:
                    result[json_field] = json.loads(result[json_field])
                except (json.JSONDecodeError, TypeError):
                    pass
        return result
    finally:
        await db.close()


async def mark_session_synced(job_id: str) -> None:
    """Mark that the session file for this job has been downloaded."""
    db = await _connect()
    try:
        now = datetime.now(timezone.utc).isoformat()
        await db.execute(
            "UPDATE jobs SET session_synced = 1, synced_at = ? WHERE job_id = ?",
            (now, job_id),
        )
        await db.commit()
    finally:
        await db.close()
