from __future__ import annotations

import copy
import json
import shutil
import threading
import uuid
from pathlib import Path

from app.errors import VaaniError
from app.util import utcnow

_lock = threading.Lock()
_root: Path | None = None


def init(root: Path) -> None:
    global _root
    root = Path(root)
    (root / "jobs").mkdir(parents=True, exist_ok=True)
    (root / "models").mkdir(parents=True, exist_ok=True)
    _root = root


def jobs_root() -> Path:
    if _root is None:
        raise RuntimeError("The job store is not ready.")
    return _root / "jobs"


def job_id(value: str) -> str:
    try:
        parsed = uuid.UUID(str(value))
    except ValueError as exc:
        raise VaaniError("That job is not on this desk.", "not_found", 404) from exc
    return str(parsed)


def job_dir(value: str) -> Path:
    return jobs_root() / job_id(value)


def _blank_step() -> dict:
    return {"state": "pending", "detail": "", "started_at": None, "ended_at": None}


def new_job(source: dict, options: dict) -> dict:
    now = utcnow()
    return {
        "id": str(uuid.uuid4()),
        "created_at": now,
        "updated_at": now,
        "status": "queued",
        "cancel_requested": False,
        "rights_confirmed_at": now if options.get("rights") else None,
        "source": source,
        "options": options,
        "steps": {
            "preparing": _blank_step(),
            "audio": _blank_step(),
            "transcript": _blank_step(),
            "ready": _blank_step(),
        },
        "error": None,
        "warnings": [],
        "detected_language": None,
        "artifacts": {},
        "progress": {"detail": "Waiting"},
    }


def _read(value: str) -> dict:
    path = job_dir(value) / "job.json"
    if not path.is_file():
        raise VaaniError("That job is not on this desk.", "not_found", 404)
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except json.JSONDecodeError as exc:
        raise VaaniError("That job could not be read.", "corrupt", 500) from exc
    if not isinstance(data, dict):
        raise VaaniError("That job could not be read.", "corrupt", 500)
    return data


def _write(job: dict) -> None:
    job["updated_at"] = utcnow()
    dest = job_dir(job["id"]) / "job.json"
    dest.parent.mkdir(parents=True, exist_ok=True)
    temporary = dest.with_suffix(".json.tmp")
    temporary.write_text(json.dumps(job, ensure_ascii=False, indent=2), encoding="utf-8")
    temporary.replace(dest)


def create(job: dict) -> dict:
    with _lock:
        directory = job_dir(job["id"])
        directory.mkdir(parents=True, exist_ok=False)
        _write(job)
        return copy.deepcopy(job)


def load(value: str) -> dict:
    with _lock:
        return copy.deepcopy(_read(value))


def update(value: str, mutate) -> dict:
    with _lock:
        job = _read(value)
        mutate(job)
        _write(job)
        return copy.deepcopy(job)


def delete(value: str) -> None:
    with _lock:
        directory = job_dir(value)
        if not directory.exists():
            raise VaaniError("That job is not on this desk.", "not_found", 404)
        shutil.rmtree(directory)


def list_jobs() -> list[dict]:
    with _lock:
        root = jobs_root()
        jobs: list[dict] = []
        if not root.exists():
            return jobs
        for directory in root.iterdir():
            path = directory / "job.json"
            if not path.is_file():
                continue
            try:
                data = json.loads(path.read_text(encoding="utf-8"))
            except json.JSONDecodeError:
                continue
            if isinstance(data, dict):
                jobs.append(data)
        jobs.sort(key=lambda item: item.get("created_at") or "", reverse=True)
        return jobs


def summary(job: dict) -> dict:
    source = job.get("source") or {}
    options = job.get("options") or {}
    return {
        "id": job.get("id"),
        "status": job.get("status"),
        "title": source.get("title") or "Untitled",
        "created_at": job.get("created_at"),
        "language": options.get("language"),
    }
