from __future__ import annotations

import ast
import json
import sqlite3
from datetime import datetime, timedelta
from typing import Any


class JobRepository:
    def begin(
        self,
        connection: sqlite3.Connection,
        *,
        kind: str,
        run_key: str,
        requested_date: str,
        trigger: str,
        started_at: datetime,
        stale_after_seconds: int,
    ) -> sqlite3.Row:
        stale_before = (started_at - timedelta(seconds=stale_after_seconds)).isoformat(
            timespec="seconds"
        )
        connection.execute(
            """
            UPDATE job_runs SET status = 'failed', finished_at = ?, duration_ms = ?,
                error_code = 'stale_job', error_message = '任务进程中断或超过最长运行时间'
            WHERE kind = ? AND status = 'running' AND started_at < ?
            """,
            (
                started_at.isoformat(timespec="seconds"),
                stale_after_seconds * 1000,
                kind,
                stale_before,
            ),
        )
        attempt_row = connection.execute(
            """
            SELECT COALESCE(MAX(attempt), 0) + 1 AS attempt
            FROM job_runs WHERE kind = ? AND run_key = ?
            """,
            (kind, run_key),
        ).fetchone()
        attempt = int(attempt_row["attempt"] if attempt_row else 1)
        cursor = connection.execute(
            """
            INSERT INTO job_runs (
                kind, run_key, requested_date, trigger, status, attempt, started_at
            ) VALUES (?, ?, ?, ?, 'running', ?, ?)
            """,
            (
                kind,
                run_key,
                requested_date,
                trigger,
                attempt,
                started_at.isoformat(timespec="seconds"),
            ),
        )
        return connection.execute(
            "SELECT * FROM job_runs WHERE id = ?", (cursor.lastrowid,)
        ).fetchone()

    def finish(
        self,
        connection: sqlite3.Connection,
        run_id: int,
        *,
        finished_at: datetime,
        duration_ms: int,
        coverage: float | None,
        source_set: list[str],
        output_version: str,
        payload: dict[str, Any],
    ) -> None:
        connection.execute(
            """
            UPDATE job_runs SET status = 'completed', finished_at = ?, duration_ms = ?,
                coverage = ?, source_set_json = ?, output_version = ?, payload_json = ?
            WHERE id = ? AND status = 'running'
            """,
            (
                finished_at.isoformat(timespec="seconds"),
                duration_ms,
                coverage,
                _json(source_set),
                output_version,
                _json(payload),
                run_id,
            ),
        )

    def fail(
        self,
        connection: sqlite3.Connection,
        run_id: int,
        *,
        finished_at: datetime,
        duration_ms: int,
        error_code: str,
        error_message: str,
    ) -> None:
        connection.execute(
            """
            UPDATE job_runs SET status = 'failed', finished_at = ?, duration_ms = ?,
                error_code = ?, error_message = ? WHERE id = ? AND status = 'running'
            """,
            (
                finished_at.isoformat(timespec="seconds"),
                duration_ms,
                error_code,
                error_message[:500],
                run_id,
            ),
        )

    def latest(self, connection: sqlite3.Connection, limit: int = 40) -> tuple[sqlite3.Row, ...]:
        return tuple(
            connection.execute(
                "SELECT * FROM job_runs ORDER BY started_at DESC, id DESC LIMIT ?",
                (limit,),
            ).fetchall()
        )

    def latest_for_kind(
        self, connection: sqlite3.Connection, kind: str
    ) -> sqlite3.Row | None:
        return connection.execute(
            "SELECT * FROM job_runs WHERE kind = ? ORDER BY started_at DESC, id DESC LIMIT 1",
            (kind,),
        ).fetchone()

    def latest_success(
        self, connection: sqlite3.Connection, kind: str, requested_date: str = ""
    ) -> sqlite3.Row | None:
        return connection.execute(
            """
            SELECT * FROM job_runs
            WHERE kind = ? AND status = 'completed' AND (? = '' OR requested_date = ?)
            ORDER BY finished_at DESC, id DESC LIMIT 1
            """,
            (kind, requested_date, requested_date),
        ).fetchone()


def public_job(row: sqlite3.Row) -> dict[str, Any]:
    result = dict(row)
    result["source_set"] = json.loads(str(result.pop("source_set_json")))
    result["payload"] = json.loads(str(result.pop("payload_json")))
    result["error_message"] = _public_error_message(str(result["error_message"]))
    return result


def _public_error_message(value: str) -> str:
    """Normalize AppError tuples written before structured error storage."""
    if not value.startswith("("):
        return value
    try:
        legacy = ast.literal_eval(value)
    except (SyntaxError, ValueError):
        return value
    if (
        isinstance(legacy, tuple)
        and len(legacy) == 3
        and isinstance(legacy[0], str)
        and isinstance(legacy[1], str)
        and isinstance(legacy[2], int)
    ):
        return legacy[1]
    return value


def _json(value: Any) -> str:
    return json.dumps(value, ensure_ascii=False, separators=(",", ":"), sort_keys=True)
