"""Saved records management — list, play, pause, resume, stop, rename, delete.

Manages WAV recordings saved via the typed replay engine.
"""

from __future__ import annotations

import json
import threading
from pathlib import Path
from typing import Any

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel

from Project.Sanad.config import AUDIO_RECORDINGS_DIR
from Project.Sanad.dashboard.routes._safe_io import (
    safe_filename, safe_path_under, atomic_write_json,
)

router = APIRouter()

RECORDS_INDEX = AUDIO_RECORDINGS_DIR / "records.json"
_INDEX_LOCK = threading.Lock()


def _load_index() -> dict[str, Any]:
    if not RECORDS_INDEX.exists():
        return {"records": [], "total_records": 0, "last_updated": ""}
    try:
        with open(RECORDS_INDEX, "r", encoding="utf-8") as f:
            return json.load(f)
    except (json.JSONDecodeError, OSError):
        # Backup corrupt file rather than nuking it
        try:
            RECORDS_INDEX.rename(RECORDS_INDEX.with_suffix(".json.corrupt"))
        except OSError:
            pass
        return {"records": [], "total_records": 0, "last_updated": ""}


def _save_index(payload: dict[str, Any]):
    AUDIO_RECORDINGS_DIR.mkdir(parents=True, exist_ok=True)
    payload["total_records"] = len(payload.get("records", []))
    atomic_write_json(RECORDS_INDEX, payload)


def _resolve_path(path_str: str) -> Path:
    """Resolve record path — basename / relative / absolute.

    Legacy records stored absolute paths. New records store basenames.
    Both flavors resolve to a real file under AUDIO_RECORDINGS_DIR.
    """
    if not path_str:
        return AUDIO_RECORDINGS_DIR
    p = Path(path_str)
    if p.is_absolute():
        return p
    return AUDIO_RECORDINGS_DIR / p


def _reconcile(payload: dict[str, Any]) -> tuple[dict[str, Any], int]:
    kept, removed = [], 0
    for entry in payload.get("records", []):
        try:
            sp = _resolve_path(entry["files"]["speaker_recording"]["path"])
            rp = _resolve_path(entry["files"]["gemini_raw_output"]["path"])
            if sp.exists() and rp.exists():
                kept.append(entry)
            else:
                removed += 1
        except (KeyError, TypeError):
            removed += 1
    payload["records"] = kept
    payload["total_records"] = len(kept)
    return payload, removed


@router.get("/")
async def list_records():
    with _INDEX_LOCK:
        payload = _load_index()
        payload, removed = _reconcile(payload)
        if removed:
            _save_index(payload)
    return payload


class RecordPlay(BaseModel):
    record_name: str
    file_kind: str = "speaker"  # speaker | raw


@router.post("/play")
async def play_record(payload: RecordPlay):
    with _INDEX_LOCK:
        index = _load_index()
    entry = next((r for r in index.get("records", []) if r.get("record_name") == payload.record_name), None)
    if entry is None:
        raise HTTPException(404, f"Record not found: {payload.record_name}")

    file_key = "speaker_recording" if payload.file_kind == "speaker" else "gemini_raw_output"
    raw_path = _resolve_path(entry["files"][file_key]["path"]).resolve()
    base = AUDIO_RECORDINGS_DIR.resolve()
    try:
        raw_path.relative_to(base)
    except ValueError:
        raise HTTPException(400, "Record path outside recordings directory.")
    if not raw_path.exists():
        raise HTTPException(404, f"File not found: {raw_path.name}")

    from Project.Sanad.main import audio_mgr
    import asyncio
    await asyncio.to_thread(audio_mgr.play_wav, raw_path)
    return {"ok": True, "record_name": payload.record_name, "file_kind": payload.file_kind, "path": str(raw_path)}


class RecordRename(BaseModel):
    record_name: str
    new_name: str


@router.post("/rename")
async def rename_record(payload: RecordRename):
    new_name = safe_filename(payload.new_name)
    # Strip any extension the user provided — we add our own
    if new_name.lower().endswith(".wav"):
        new_name = new_name[:-4]
    if not new_name or new_name.startswith("."):
        raise HTTPException(400, "Invalid new name.")

    with _INDEX_LOCK:
        index = _load_index()
        entry = next(
            (r for r in index.get("records", []) if r.get("record_name") == payload.record_name),
            None,
        )
        if entry is None:
            raise HTTPException(404, f"Record not found: {payload.record_name}")

        base = AUDIO_RECORDINGS_DIR.resolve()
        for key in ("speaker_recording", "gemini_raw_output"):
            try:
                old_path = _resolve_path(entry["files"][key]["path"]).resolve()
                old_path.relative_to(base)  # ensure inside recordings dir
            except (KeyError, ValueError):
                continue
            if not old_path.exists():
                continue
            suffix = "_raw.wav" if key == "gemini_raw_output" else ".wav"
            new_path = safe_path_under(AUDIO_RECORDINGS_DIR, f"{new_name}{suffix}")
            if new_path.exists():
                raise HTTPException(409, f"File already exists: {new_path.name}")
            old_path.rename(new_path)
            entry["files"][key]["path"] = new_path.name  # basename — portable
            entry["files"][key]["name"] = new_path.name

        entry["record_name"] = new_name
        _save_index(index)
    return {"ok": True, "record": entry}


class RecordDelete(BaseModel):
    record_name: str


@router.post("/delete")
async def delete_record(payload: RecordDelete):
    with _INDEX_LOCK:
        index = _load_index()
        kept = []
        deleted_entry = None
        for r in index.get("records", []):
            if r.get("record_name") == payload.record_name and deleted_entry is None:
                deleted_entry = r
            else:
                kept.append(r)

        if deleted_entry is None:
            raise HTTPException(404, f"Record not found: {payload.record_name}")

        base = AUDIO_RECORDINGS_DIR.resolve()
        deleted_files = []
        for fi in deleted_entry.get("files", {}).values():
            try:
                p = Path(fi.get("path", "")).resolve()
                p.relative_to(base)  # never delete files outside recordings dir
            except (ValueError, OSError):
                continue
            if p.exists():
                p.unlink()
                deleted_files.append(str(p))

        index["records"] = kept
        _save_index(index)
    return {"ok": True, "deleted": payload.record_name, "deleted_files": deleted_files}
