import asyncio
import queue
from datetime import datetime, timezone
from pathlib import Path

from ldnode import logger
from ldnode.runner.schemas import BaseWorkerState, Service
from ldnode.runner.config import Settings
from ldnode.runner.schemas import BaseContext
from ldnode.runner.events import Events
from .schemas.enumerations import SyncStatus
from .schemas.models import SyncResult
from .worker import LoaderWorker
from .data_time import (
    DATASET_REGISTRY,
    extract_data_time,
    extract_realtime_data_time,
    is_snapshot_history,
)


class LoaderService(Service):
    """加载服务"""
    # 周期上报间隔（秒）
    REPORT_INTERVAL = 60

    def __init__(self, settings: Settings, context: BaseContext):
        super().__init__(settings, context)
        self.worker: BaseWorkerState | None = None
        self.end_flag = asyncio.Event()
        self.listen_task: asyncio.Task | None = None
        self.report_task: asyncio.Task | None = None
        self.running = False
        # 数据集状态缓存：dataset_name -> {data_time, synced_at, status, file_etag}
        self._dataset_cache: dict[str, dict] = {}
        self._data_dir: Path | None = None
        # 文件名 -> dataset_name 的反向映射（用于 SyncResult 关联）
        self._filename_to_dataset: dict[str, tuple[str, str]] = {}

    def _init_filename_mapping(self) -> None:
        """构建文件名到数据集名称的映射"""
        for dataset_name, category, file in DATASET_REGISTRY:
            self._filename_to_dataset[file.key] = (dataset_name, category)

    def _build_full_report(self) -> list[dict]:
        """构建全量数据集状态报告"""
        now = datetime.now(timezone.utc).isoformat()
        datasets = []
        for dataset_name, category, file in DATASET_REGISTRY:
            # 优先使用缓存
            if dataset_name in self._dataset_cache:
                datasets.append(self._dataset_cache[dataset_name])
                continue
            # 缓存未命中，尝试从本地文件提取
            if is_snapshot_history(category):
                dt = extract_data_time(file, self._data_dir)
            else:
                dt = extract_realtime_data_time(file, self._data_dir)
            if dt is None:
                continue  # 文件不存在，跳过
            item = {
                "dataset": dataset_name,
                "category": category,
                "data_time": dt,
                "synced_at": now,
                "status": "ready",
                "file_etag": "",
            }
            self._dataset_cache[dataset_name] = item
            datasets.append(item)
        return datasets

    async def _submit_dataset_report(self, datasets: list[dict]) -> None:
        """提交数据集状态上报事件"""
        if not datasets:
            return
        await self.context.submit_event(
            "loader", Events.DATASET_STATUS_REPORT, {"datasets": datasets}
        )

    async def _periodic_report(self) -> None:
        """周期兜底上报（每 60 秒全量上报）"""
        while self.running:
            await asyncio.sleep(self.REPORT_INTERVAL)
            if not self.running:
                break
            try:
                datasets = self._build_full_report()
                await self._submit_dataset_report(datasets)
            except Exception as e:
                logger.loader.warning(f"周期上报失败: {e}")

    def _update_cache_from_sync(
        self, filename: str, is_ok: bool, etag: str
    ) -> dict | None:
        """根据 SyncResult 更新缓存，返回变化的数据集条目（用于立即上报）"""
        lookup = self._filename_to_dataset.get(filename)
        if lookup is None:
            return None
        dataset_name, category = lookup
        now = datetime.now(timezone.utc).isoformat()
        if not is_ok:
            old = self._dataset_cache.get(dataset_name, {})
            item = {
                "dataset": dataset_name,
                "category": category,
                "data_time": old.get("data_time", ""),
                "synced_at": old.get("synced_at", ""),  # 失败时保留上次成功的时间
                "status": "failed",
                "file_etag": etag,
            }
            self._dataset_cache[dataset_name] = item
            return item
        # 提取 data_time
        file = next((f for n, _, f in DATASET_REGISTRY if n == dataset_name), None)
        if file is None:
            return None
        if is_snapshot_history(category):
            dt = extract_data_time(file, self._data_dir)
        else:
            dt = extract_realtime_data_time(file, self._data_dir)
        if dt is None:
            return None
        old_cache = self._dataset_cache.get(dataset_name, {})
        old_dt = old_cache.get("data_time")
        old_status = old_cache.get("status")
        item = {
            "dataset": dataset_name,
            "category": category,
            "data_time": dt,
            "synced_at": now,
            "status": "ready",
            "file_etag": etag,
        }
        self._dataset_cache[dataset_name] = item
        # data_time 变化或状态恢复（failed→ready）时立即上报
        if old_dt != dt or old_status == "failed":
            return item
        return None

    async def listen_worker_output(self, state: BaseWorkerState) -> None:
        """监听工作进程输出"""
        changed_items: list[dict] = []
        while self.running:
            try:
                result: SyncResult = await state.recv()
                if result is not None:
                    is_ok = result.status == SyncStatus.SUCCESS or result.status == SyncStatus.NO_CHANGE
                    # 原有 DATA_SYNC 事件（保持兼容）
                    await self.context.submit_event(state.get_worker_id(), Events.DATA_SYNC, {
                        "filename": result.filename,
                        "is_ok": is_ok,
                        "latest_tag": result.latest_tag_or_error if is_ok else "",
                        "error_message": result.latest_tag_or_error if not is_ok else "",
                    })
                    # 更新数据集缓存，检测变化（含 failed→ready 状态恢复）
                    changed = self._update_cache_from_sync(
                        result.filename,
                        is_ok,
                        result.latest_tag_or_error if is_ok else "",
                    )
                    if changed:
                        changed_items.append(changed)
                    # 批量提交变化（避免逐条上报过于频繁）
                    if changed_items:
                        await self._submit_dataset_report(changed_items)
                        changed_items = []
            except queue.Empty:
                continue
            except Exception as e:
                logger.loader.warning(f"{state.get_worker_id()} 监听工作进程输出失败: {e}")

    async def start(self) -> None:
        """启动服务"""
        self.running = True
        self._data_dir = Path(self.settings.DATA_SYNC_LOCAL_DATA_DIR)
        self._init_filename_mapping()
        self.worker = await self.context.register_worker(
            worker_name="loader",
            worker_id=0,
            worker_cls=LoaderWorker,
        )
        # 启动周期上报任务
        self.report_task = asyncio.create_task(self._periodic_report())
        # 首次上线时全量上报一次
        initial = self._build_full_report()
        await self._submit_dataset_report(initial)
        # 监听工作进程输出
        self.listen_task = asyncio.create_task(self.listen_worker_output(self.worker))
        await self.end_flag.wait()

    async def shutdown(self) -> None:
        """关闭服务"""
        self.running = False
        if self.report_task:
            self.report_task.cancel()
        if self.listen_task:
            await self.listen_task
        self.end_flag.set()
