# -*- coding: utf-8 -*-
"""
队列文件读写与任务抢占，与 queue_task_schema.txt 约定一致。
（模块名 task_queue 避免与标准库 queue 冲突）
"""
import json
import os
import uuid
import logging
from pathlib import Path
from typing import Optional, List, Dict, Any

logger = logging.getLogger(__name__)

QUEUE_DIR_NAME = "queue"
CONFIG_FILE_NAME = "queue_config.json"


def _format_time(seconds: float) -> str:
    if seconds < 0:
        return "0"
    if seconds < 3600:
        m = int(seconds // 60)
        s = seconds % 60
        return f"{m}:{s:05.2f}" if s != int(s) else f"{m}:{int(s):02d}"
    h = int(seconds // 3600)
    m = int((seconds % 3600) // 60)
    s = seconds % 60
    return f"{h}:{m:02d}:{s:05.2f}" if s != int(s) else f"{h}:{m:02d}:{int(s):02d}"


class QueueStore:
    def __init__(self, data_dir: str, path_replace: Optional[tuple] = None):
        """
        :param data_dir: 数据目录，如 ~/.videoconverter/data
        :param path_replace: (old_prefix, new_prefix) 将任务中的路径前缀替换，便于本机/服务器迁移
        """
        self.data_dir = Path(data_dir).resolve()
        self.queue_dir = self.data_dir / QUEUE_DIR_NAME
        self.config_file = self.data_dir / CONFIG_FILE_NAME
        self.path_replace = path_replace  # (old, new)
        self._task_cache: Dict[Path, tuple] = {}  # path -> (mtime, status, created_time)，减少重复读盘
        self._ensure_dirs()

    def _ensure_dirs(self) -> None:
        self.queue_dir.mkdir(parents=True, exist_ok=True)
        if not self.config_file.exists():
            self._write_config({"queue_paused": "true"})

    def _apply_path(self, path: str) -> str:
        if not path or not self.path_replace:
            return path
        old, new = self.path_replace
        if path.startswith(old):
            return new + path[len(old):].lstrip(os.sep)
        return path

    def _read_config(self) -> dict:
        if not self.config_file.exists():
            return {}
        try:
            with open(self.config_file, "r", encoding="utf-8") as f:
                return json.load(f)
        except Exception:
            return {}

    def _write_config(self, config: dict) -> None:
        tmp = self.config_file.with_suffix(self.config_file.suffix + ".tmp")
        with open(tmp, "w", encoding="utf-8") as f:
            json.dump(config, f, indent=2, ensure_ascii=False)
        tmp.replace(self.config_file)

    def is_paused(self) -> bool:
        cfg = self._read_config()
        return cfg.get("queue_paused", "true") == "true"

    def set_paused(self, paused: bool) -> None:
        cfg = self._read_config()
        cfg["queue_paused"] = "true" if paused else "false"
        self._write_config(cfg)

    def _task_file(self, task_id: str) -> Path:
        return self.queue_dir / f"{task_id}.json"

    def _lock_file(self, task_id: str) -> Path:
        return self.queue_dir / f"{task_id}.lock"

    def _log_file(self, task_id: str) -> Path:
        return self.queue_dir / f"{task_id}.log"

    def _list_task_files(self) -> List[Path]:
        if not self.queue_dir.exists():
            return []
        return sorted(self.queue_dir.glob("*.json"), key=lambda p: p.name)

    def recover_orphaned_processing(self) -> int:
        """断电/强制关机后：将无 .lock 的 PROCESSING 任务重置为 PENDING，便于重启后继续。返回恢复数量。"""
        count = 0
        for p in self._list_task_files():
            task_id = p.stem
            lock_path = self._lock_file(task_id)
            if lock_path.exists():
                continue
            try:
                with open(p, "r", encoding="utf-8") as f:
                    data = json.load(f)
                if data.get("status") != "PROCESSING":
                    continue
                data["status"] = "PENDING"
                data["progress"] = 0.0
                data["progress_text"] = "等待重试（已恢复）"
                if "start_time" in data:
                    del data["start_time"]
                tmp = p.with_suffix(p.suffix + ".tmp")
                with open(tmp, "w", encoding="utf-8") as f:
                    json.dump(data, f, indent=2, ensure_ascii=False)
                tmp.replace(p)
                self._task_cache.pop(p, None)
                count += 1
                logger.info("恢复孤儿任务为 PENDING: %s", task_id)
            except Exception as e:
                logger.warning("恢复任务失败 %s: %s", task_id, e)
        return count

    def acquire_pending_task(self) -> Optional[Dict[str, Any]]:
        """原子抢占一个 PENDING 任务，返回任务 dict 或 None。用缓存避免每轮读取全部 JSON。"""
        files = self._list_task_files()
        pending_with_time = []
        for p in files:
            try:
                mtime = p.stat().st_mtime
                cached = self._task_cache.get(p)
                if cached is not None and cached[0] == mtime:
                    _mtime, status, created_time = cached
                    if status != "PENDING":
                        continue
                    pending_with_time.append((created_time, p))
                    continue
                with open(p, "r", encoding="utf-8") as f:
                    data = json.load(f)
                status = data.get("status")
                created_time = data.get("created_time", 0)
                self._task_cache[p] = (mtime, status, created_time)
                if status != "PENDING":
                    continue
                pending_with_time.append((created_time, p))
            except Exception as e:
                logger.warning("读取任务文件失败 %s: %s", p.name, e)
        pending_with_time.sort(key=lambda x: x[0])

        for _, path in pending_with_time:
            task_id = path.stem
            lock_path = self._lock_file(task_id)
            try:
                with open(lock_path, "x"):
                    pass
            except FileExistsError:
                continue
            try:
                with open(path, "r", encoding="utf-8") as f:
                    data = json.load(f)
                if data.get("status") != "PENDING":
                    lock_path.unlink(missing_ok=True)
                    continue
                data["status"] = "PROCESSING"
                data["start_time"] = int(__import__("time").time() * 1000)
                with open(path, "w", encoding="utf-8") as f:
                    json.dump(data, f, indent=2, ensure_ascii=False)
                self._apply_paths_to_task(data)
                logger.debug("抢占任务: %s", task_id)
                return data
            except Exception as e:
                logger.exception("抢占任务失败 %s: %s", task_id, e)
                raise
            finally:
                lock_path.unlink(missing_ok=True)
        if len(self._task_cache) > 50000:
            self._task_cache.clear()
        return None

    def _apply_paths_to_task(self, task: dict) -> None:
        for key in ("input_file", "output_dir"):
            if key in task and task[key]:
                task[key] = self._apply_path(task[key])
        cfg = task.get("config") or {}
        for key in ("inputPath", "outputPath"):
            if key in cfg and cfg[key]:
                cfg[key] = self._apply_path(cfg[key])
        task["config"] = cfg

    def update_progress(self, task_id: str, progress: float, progress_text: str) -> None:
        self._update_task(task_id, {
            "progress": max(0, min(100, progress)),
            "progress_text": progress_text or "",
        })

    def complete_task(self, task_id: str) -> None:
        self._update_task(task_id, {
            "status": "COMPLETED",
            "progress": 100.0,
            "progress_text": "已完成",
            "end_time": int(__import__("time").time() * 1000),
        })

    def fail_task(self, task_id: str, error_message: str) -> None:
        self._update_task(task_id, {
            "status": "FAILED",
            "end_time": int(__import__("time").time() * 1000),
            "error_message": error_message or "",
        })

    def _update_task(self, task_id: str, updates: dict) -> None:
        path = self._task_file(task_id)
        if not path.exists():
            return
        with open(path, "r", encoding="utf-8") as f:
            data = json.load(f)
        data.update(updates)
        tmp = path.with_suffix(path.suffix + ".tmp")
        with open(tmp, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2, ensure_ascii=False)
        tmp.replace(path)

    def add_log(self, task_id: str, level: str, message: str) -> None:
        log_path = self._log_file(task_id)
        line = f"{int(__import__('time').time()*1000)}\t{level}\t{message}\n"
        with open(log_path, "a", encoding="utf-8") as f:
            f.write(line)

    def has_merge_task_for_video_id(self, video_id: str) -> bool:
        """是否存在该 video_id 的 MERGE 任务，遇第一个即返回，避免全量 get_tasks_by_video_id。"""
        for p in self._list_task_files():
            try:
                with open(p, "r", encoding="utf-8") as f:
                    d = json.load(f)
                if d.get("video_id") == video_id and d.get("task_type") == "MERGE":
                    return True
            except Exception:
                pass
        return False

    def get_tasks_by_video_id(self, video_id: str) -> List[Dict[str, Any]]:
        out = []
        for p in self._list_task_files():
            try:
                with open(p, "r", encoding="utf-8") as f:
                    d = json.load(f)
                if d.get("video_id") == video_id:
                    self._apply_paths_to_task(d)
                    out.append(d)
            except Exception:
                pass
        out.sort(key=lambda x: x.get("created_time", 0))
        return out

    def create_desubtitle_task(self, input_file: str, output_dir: str, config: dict,
                               parent_task_id: str, video_id: str) -> str:
        task_id = str(uuid.uuid4())
        task = {
            "task_id": task_id,
            "task_type": "DESUBTITLE",
            "parent_task_id": parent_task_id or "",
            "video_id": video_id or "",
            "input_file": input_file,
            "output_dir": output_dir,
            "config": dict(config) if config else {},
            "status": "PENDING",
            "progress": 0.0,
            "progress_text": "等待去字幕...",
            "created_time": int(__import__("time").time() * 1000),
            "error_message": "",
        }
        path = self._task_file(task_id)
        with open(path, "w", encoding="utf-8") as f:
            json.dump(task, f, indent=2, ensure_ascii=False)
        logger.info("创建去字幕任务: %s -> %s", input_file, task_id)
        return task_id

    def create_merge_task(self, video_id: str, output_dir: str, config: dict) -> str:
        task_id = str(uuid.uuid4())
        task = {
            "task_id": task_id,
            "task_type": "MERGE",
            "parent_task_id": "",
            "video_id": video_id,
            "input_file": "",
            "output_dir": output_dir,
            "config": dict(config) if config else {},
            "status": "PENDING",
            "progress": 0.0,
            "progress_text": "等待合成...",
            "created_time": int(__import__("time").time() * 1000),
            "error_message": "",
        }
        path = self._task_file(task_id)
        with open(path, "w", encoding="utf-8") as f:
            json.dump(task, f, indent=2, ensure_ascii=False)
        logger.info("创建合成任务: videoId=%s -> %s", video_id, task_id)
        return task_id
