# -*- coding: utf-8 -*-
"""
指数日快照回填任务（DWS_INDEX_BACKFILL）

逐天调用 RelationIndexTask / WinbackIndexTask / NewconvIndexTask，
为 3 张指数表生成历史日快照。

CHANGE 2026-04-12 | 性能优化：
- 任务实例复用（创建 1 次，循环 N 天复用）
- 减少 765 次 Task 初始化和参数表查询

CLI 用法：
    python -m cli.main --tasks DWS_INDEX_BACKFILL \\
        --window-start 2025-08-01 --window-end 2026-04-11

admin-web：在 ETL 任务配置页面选择 DWS_INDEX_BACKFILL，设置时间窗口。
"""
from __future__ import annotations

import time
from datetime import date, datetime, timedelta
from typing import Any, Dict, Optional

from ..base_task import BaseTask, TaskContext
from ..dws.index.relation_index_task import RelationIndexTask
from ..dws.index.winback_index_task import WinbackIndexTask
from ..dws.index.newconv_index_task import NewconvIndexTask


class IndexBackfillTask(BaseTask):
    """指数日快照回填工具任务。"""

    def get_task_code(self) -> str:
        return "DWS_INDEX_BACKFILL"

    def execute(self, context: Optional[TaskContext] = None) -> Dict[str, Any]:
        """主流程：解析日期范围，逐天执行 3 个指数任务。"""
        start_date, end_date = self._parse_date_range(context)
        store_id = self._resolve_store_id(context)

        total_days = (end_date - start_date).days + 1
        self.logger.info(
            "DWS_INDEX_BACKFILL: %s ~ %s (%d天), store_id=%s",
            start_date, end_date, total_days, store_id,
        )

        # 创建 1 次实例，循环复用（避免 765 次 __init__ + 参数查询）
        task_instances = [
            RelationIndexTask(self.config, self.db, self.api, self.logger),
            WinbackIndexTask(self.config, self.db, self.api, self.logger),
            NewconvIndexTask(self.config, self.db, self.api, self.logger),
        ]
        task_names = ["RS", "WBI", "NCI"]
        completed = 0
        errors = 0
        t0 = time.time()

        current = start_date
        while current <= end_date:
            ctx = self._build_day_context(current, store_id)
            day_num = (current - start_date).days + 1
            day_t0 = time.time()

            for i, task in enumerate(task_instances):
                try:
                    task.execute(ctx)
                    completed += 1
                except Exception:
                    self.logger.exception(
                        "DWS_INDEX_BACKFILL: %s %s 失败",
                        task.__class__.__name__, current,
                    )
                    errors += 1
                self.logger.info(
                    "DWS_INDEX_BACKFILL: %s [%d/%d] %s (%d/3)",
                    current, day_num, total_days, task_names[i], i + 1,
                )

            elapsed = time.time() - day_t0
            total_elapsed_so_far = time.time() - t0
            avg_per_day = total_elapsed_so_far / day_num
            eta = avg_per_day * (total_days - day_num)
            self.logger.info(
                "DWS_INDEX_BACKFILL: %s [%d/%d %.0f%%] %.1fs/天 ETA %.0fs",
                current, day_num, total_days, day_num / total_days * 100,
                elapsed, eta,
            )

            current += timedelta(days=1)

        total_elapsed = time.time() - t0
        self.logger.info(
            "DWS_INDEX_BACKFILL 完成: %d/%d 成功, %d 失败, %.0fs",
            completed, total_days * 3, errors, total_elapsed,
        )

        return {
            "status": "SUCCESS" if errors == 0 else "PARTIAL",
            "counts": {
                "days": total_days,
                "completed": completed,
                "errors": errors,
                "elapsed_sec": round(total_elapsed, 1),
            },
        }

    def _parse_date_range(self, context: Optional[TaskContext]) -> tuple[date, date]:
        """从 config 或 context 解析日期范围。"""
        wo = self.config.get("run.window_override") or {}
        start_str = wo.get("start")
        end_str = wo.get("end")

        if start_str and end_str:
            return self._parse_date(start_str), self._parse_date(end_str)

        if context and context.window_start and context.window_end:
            return context.window_start.date(), context.window_end.date()

        raise ValueError(
            "DWS_INDEX_BACKFILL 需要指定日期范围。"
            "CLI: --window-start 2025-08-01 --window-end 2026-04-11"
        )

    @staticmethod
    def _parse_date(s) -> date:
        if isinstance(s, date) and not isinstance(s, datetime):
            return s
        if isinstance(s, datetime):
            return s.date()
        return date.fromisoformat(str(s).strip()[:10])

    def _resolve_store_id(self, context: Optional[TaskContext]) -> int:
        if context and getattr(context, "store_id", None):
            return int(context.store_id)
        sid = self.config.get("app.store_id")
        if sid:
            return int(sid)
        raise ValueError("DWS_INDEX_BACKFILL 需要 store_id")

    def _build_day_context(self, d: date, store_id: int) -> TaskContext:
        from zoneinfo import ZoneInfo
        tz = ZoneInfo(self.config.get("app.timezone", "Asia/Shanghai"))
        as_of = datetime(d.year, d.month, d.day, 23, 59, 0, tzinfo=tz)
        window_start = as_of - timedelta(days=90)
        return TaskContext(
            store_id=store_id,
            window_start=window_start,
            window_end=as_of,
            window_minutes=int((as_of - window_start).total_seconds() / 60),
            as_of_date=as_of,
        )
