"""
从 S/A 级需求关联视频中挖掘拓展需求。

任务层负责查库与组装上下文；Agent 仅做语义判断与落库。
"""
from __future__ import annotations

import json
import logging
import uuid
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
from typing import Any
from zoneinfo import ZoneInfo

from agents.demand_video_expand_agent.run import (
    DemandExpandContext,
    VideoPoint,
    extract_saved_count,
    judge_demand_expansion,
)
from supply_infra.config import get_infra_settings
from supply_infra.db.models.demand_grade import DemandGrade
from supply_infra.db.repositories.demand_grade_repo import DemandGradeRepository
from supply_infra.db.repositories.demand_video_expansion_repo import (
    DemandVideoExpansionRunRepository,
)
from supply_infra.db.repositories.multi_demand_video_point_repo import (
    MultiDemandVideoPointRepository,
)
from supply_infra.db.session import get_session

logger = logging.getLogger(__name__)

_DEFAULT_WORKERS = 5


def _resolve_biz_dt(biz_dt: str | None) -> str:
    if biz_dt:
        text = str(biz_dt).strip()
        if len(text) == 8 and text.isdigit():
            return text
        raise ValueError(f"biz_dt 格式无效，应为 YYYYMMDD: {biz_dt!r}")

    with get_session() as session:
        latest = DemandGradeRepository(session).get_latest_biz_dt()
    if latest:
        return str(latest)

    timezone = ZoneInfo(get_infra_settings().scheduler_timezone)
    return datetime.now(timezone).strftime("%Y%m%d")


def _parse_video_ids(raw: Any) -> list[str]:
    if raw is None:
        return []
    items: list[Any]
    if isinstance(raw, str):
        text = raw.strip()
        if not text:
            return []
        try:
            parsed = json.loads(text)
            items = list(parsed) if isinstance(parsed, list) else [text]
        except (ValueError, TypeError):
            items = [part.strip() for part in text.split(",") if part.strip()]
    elif isinstance(raw, (list, tuple)):
        items = list(raw)
    else:
        return []
    out: list[str] = []
    seen: set[str] = set()
    for item in items:
        vid = str(item).strip() if item is not None else ""
        if not vid or vid in seen:
            continue
        seen.add(vid)
        out.append(vid)
    return out


def _to_float(value: Any) -> float | None:
    if value is None:
        return None
    return float(value)


def load_expand_contexts(
    session,
    biz_dt: str,
    *,
    run_id: str,
    skip_finished: bool = True,
) -> tuple[list[DemandExpandContext], dict[str, int]]:
    """加载待拓展判断的需求上下文，并返回跳过统计。"""
    stats = {
        "total_sa": 0,
        "skipped_no_video": 0,
        "skipped_no_points": 0,
        "skipped_already_done": 0,
    }

    grades = DemandGradeRepository(session).list_by_biz_dt_and_grades(biz_dt, ("S", "A"))
    stats["total_sa"] = len(grades)

    finished_ids: set[int] = set()
    if skip_finished:
        finished_ids = DemandVideoExpansionRunRepository(session).list_finished_grade_ids(
            biz_dt
        )

    rows_with_video: list[tuple[DemandGrade, list[str]]] = []
    all_video_ids: set[str] = set()

    for row in grades:
        if int(row.id) in finished_ids:
            stats["skipped_already_done"] += 1
            continue

        video_ids = _parse_video_ids(row.video_list)
        if not video_ids:
            stats["skipped_no_video"] += 1
            continue

        rows_with_video.append((row, video_ids))
        all_video_ids.update(video_ids)

    points_by_vid = MultiDemandVideoPointRepository(session).list_by_video_ids(all_video_ids)

    contexts: list[DemandExpandContext] = []
    for row, video_ids in rows_with_video:
        points: list[VideoPoint] = []
        for vid in video_ids:
            for point in points_by_vid.get(vid, []):
                points.append(
                    VideoPoint(
                        video_id=vid,
                        point_type=str(point.get("point_type") or ""),
                        point_data=point.get("point_data"),
                        point_desc=point.get("point_desc"),
                    )
                )
        if not points:
            stats["skipped_no_points"] += 1
            continue

        contexts.append(
            DemandExpandContext(
                biz_dt=biz_dt,
                run_id=run_id,
                demand_grade_id=int(row.id),
                demand_name=str(row.demand_name),
                grade=str(row.grade),
                score=_to_float(row.score),
                video_ids=video_ids,
                points=points,
            )
        )

    return contexts, stats


def _record_run(
    *,
    biz_dt: str,
    run_id: str,
    source_demand_grade_id: int,
    saved_count: int,
    status: str,
    error_message: str | None = None,
) -> None:
    with get_session() as session:
        DemandVideoExpansionRunRepository(session).upsert_run(
            biz_dt=biz_dt,
            run_id=run_id,
            source_demand_grade_id=source_demand_grade_id,
            saved_count=saved_count,
            status=status,
            error_message=error_message,
        )


def _process_single_expand(
    ctx: DemandExpandContext,
    *,
    biz_dt: str,
    run_id: str,
) -> dict[str, Any]:
    """并发 worker：对单个需求执行拓展判断并落库执行记录。"""
    try:
        agent_result = judge_demand_expansion(ctx)
        saved_count = extract_saved_count(agent_result)
        _record_run(
            biz_dt=biz_dt,
            run_id=run_id,
            source_demand_grade_id=ctx.demand_grade_id,
            saved_count=saved_count,
            status="finished",
        )
        logger.info(
            "expand demand done: grade_id=%s demand=%s saved=%d iterations=%d",
            ctx.demand_grade_id,
            ctx.demand_name,
            saved_count,
            agent_result.iterations,
        )
        return {
            "success": True,
            "demand_grade_id": ctx.demand_grade_id,
            "demand_name": ctx.demand_name,
            "saved_count": saved_count,
            "iterations": agent_result.iterations,
        }
    except Exception as exc:
        error_text = str(exc)
        _record_run(
            biz_dt=biz_dt,
            run_id=run_id,
            source_demand_grade_id=ctx.demand_grade_id,
            saved_count=0,
            status="failed",
            error_message=error_text,
        )
        logger.exception(
            "expand demand failed: grade_id=%s demand=%s",
            ctx.demand_grade_id,
            ctx.demand_name,
        )
        return {
            "success": False,
            "demand_grade_id": ctx.demand_grade_id,
            "demand_name": ctx.demand_name,
            "error": error_text,
        }


def expand_demand_from_video_points(
    biz_dt: str | None = None,
    *,
    skip_finished: bool = True,
    workers: int = _DEFAULT_WORKERS,
) -> dict[str, Any]:
    """
    对指定业务日的 S/A 需求执行视频点位拓展判断。

    程序负责查需求与点位；无视频或无点位则跳过；有数据则并发调用 Agent。
    """
    started_at = datetime.now()
    run_id = uuid.uuid4().hex

    try:
        resolved_biz_dt = _resolve_biz_dt(biz_dt)
    except Exception as exc:
        logger.exception("expand_demand_from_video_points preflight failed")
        return {
            "success": False,
            "error": str(exc),
            "started_at": started_at.isoformat(),
            "finished_at": datetime.now().isoformat(),
        }

    with get_session() as session:
        contexts, preload_stats = load_expand_contexts(
            session,
            resolved_biz_dt,
            run_id=run_id,
            skip_finished=skip_finished,
        )

    logger.info(
        "expand_demand_from_video_points start: biz_dt=%s run_id=%s workers=%s pending=%s",
        resolved_biz_dt,
        run_id,
        workers,
        len(contexts),
    )

    result: dict[str, Any] = {
        "success": True,
        "run_id": run_id,
        "biz_dt": resolved_biz_dt,
        "started_at": started_at.isoformat(),
        "workers": 0,
        **preload_stats,
        "processed": 0,
        "saved_total": 0,
        "failed": 0,
        "errors": [],
    }

    if not contexts:
        finished_at = datetime.now()
        result["finished_at"] = finished_at.isoformat()
        result["duration_seconds"] = round((finished_at - started_at).total_seconds(), 2)
        logger.info("expand_demand_from_video_points finished: %s", result)
        return result

    worker_count = max(1, min(int(workers), len(contexts)))
    result["workers"] = worker_count

    with ThreadPoolExecutor(max_workers=worker_count) as executor:
        futures = [
            executor.submit(_process_single_expand, ctx, biz_dt=resolved_biz_dt, run_id=run_id)
            for ctx in contexts
        ]
        for future in as_completed(futures):
            try:
                item_result = future.result()
            except Exception as exc:
                logger.exception("expand demand worker 出现未捕获错误: biz_dt=%s", resolved_biz_dt)
                result["failed"] += 1
                result["errors"].append({"error": str(exc)})
                continue

            result["processed"] += 1
            if item_result.get("success"):
                result["saved_total"] += int(item_result.get("saved_count") or 0)
                continue

            result["failed"] += 1
            result["errors"].append(
                {
                    "demand_grade_id": item_result.get("demand_grade_id"),
                    "demand_name": item_result.get("demand_name"),
                    "error": item_result.get("error"),
                }
            )

    finished_at = datetime.now()
    result["finished_at"] = finished_at.isoformat()
    result["duration_seconds"] = round((finished_at - started_at).total_seconds(), 2)
    result["success"] = result["failed"] == 0

    logger.info("expand_demand_from_video_points finished: %s", result)
    return result


if __name__ == "__main__":
    import sys

    from supply_infra.scheduler.cli_result import run_cli

    _args = sys.argv[1:]
    _biz_dt = _args[0] if _args and not _args[0].startswith("-") else None
    _workers_arg = None
    if _biz_dt and len(_args) > 1 and not _args[1].startswith("-"):
        _workers_arg = _args[1]
    _skip_finished = "--force" not in _args

    run_cli(
        lambda: expand_demand_from_video_points(
            _biz_dt,
            skip_finished=_skip_finished,
            workers=int(_workers_arg) if _workers_arg else 5,
        ),
        label="expand_demand_from_video_points",
    )
