"""
从全部 S/A 级需求及其拓展点位触发 find_agent 视频发现；有效视频满 200 提前结束。

任务层负责查库与组装上下文；Agent 负责搜索、画像与分池落库。
"""
from __future__ import annotations

import logging
import uuid
from concurrent.futures import FIRST_COMPLETED, ThreadPoolExecutor, wait
from datetime import datetime
from time import monotonic
from typing import Any
from zoneinfo import ZoneInfo

from agents.find_agent.demand_run import (
    FindDemandContext,
    discover_videos_for_demand,
    filter_pending_contexts,
    list_find_demand_contexts,
    serialize_find_demand_context,
)
from supply_infra.config import get_infra_settings
from supply_infra.db.repositories.demand_grade_repo import DemandGradeRepository
from supply_infra.db.session import ensure_mysql_pool_capacity, get_session
from supply_infra.services.video_discovery_service import get_video_discovery_service

logger = logging.getLogger(__name__)

_DEFAULT_WORKERS = 1
_DAILY_PASSED_VIDEO_LIMIT = 200


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 process_single_discover(
    ctx: FindDemandContext,
    *,
    force: bool = False,
) -> dict[str, Any]:
    """并发 worker：对单条需求记录执行 find_agent。"""
    summary = serialize_find_demand_context(ctx)
    logger.info(
        "discover videos started: grade_id=%s demand=%s videos=%d points=%d",
        ctx.demand_grade_id,
        ctx.demand_name,
        ctx.video_count,
        ctx.point_count,
    )
    try:
        execution = discover_videos_for_demand(ctx, force=force)
        if execution.skipped:
            return {
                "success": True,
                "skipped": True,
                **summary,
                "skip_reason": execution.skip_reason,
            }

        if not execution.succeeded:
            return {
                "success": False,
                "skipped": False,
                "run_id": execution.run_id,
                **summary,
                "error": execution.failure_reason or "find_agent_incomplete",
                "iterations": (
                    execution.agent_result.iterations
                    if execution.agent_result is not None
                    else None
                ),
            }

        agent_result = execution.agent_result
        assert agent_result is not None
        logger.info(
            "discover videos done: run_id=%s grade_id=%s demand=%s videos=%d points=%d iterations=%d",
            execution.run_id,
            ctx.demand_grade_id,
            ctx.demand_name,
            ctx.video_count,
            ctx.point_count,
            agent_result.iterations,
        )
        return {
            "success": True,
            "skipped": False,
            "run_id": execution.run_id,
            **summary,
            "iterations": agent_result.iterations,
            "content_chars": len(agent_result.content or ""),
        }
    except Exception as exc:
        error_text = str(exc)
        logger.exception(
            "discover videos failed: grade_id=%s demand=%s videos=%d",
            ctx.demand_grade_id,
            ctx.demand_name,
            ctx.video_count,
        )
        return {
            "success": False,
            "skipped": False,
            **summary,
            "error": error_text,
        }


def _count_passed_videos(biz_dt: str) -> int:
    return get_video_discovery_service().count_passed_videos(biz_dt)


def discover_videos_from_demands(
    biz_dt: str | None = None,
    *,
    workers: int = _DEFAULT_WORKERS,
    offset: int = 0,
    limit: int | None = None,
    top_limit: int | None = None,
    skip_finished: bool = True,
    force: bool = False,
) -> dict[str, Any]:
    """
    对指定业务日全部 S/A 级需求（有拓展点位）逐条调用 find_agent。

    每条记录对应一个 demand_grade + 其下全部视频与全部拓展点位。
    执行前会预写 video_discovery_run，并按 biz_dt + demand_grade_id 跳过已执行记录。
    默认处理全部 S/A（S 优先于 A、再按 score 排序）；仅 CLI --top-limit 可人为截断。
    当日 primary 去重视频达到 200 时提前结束，否则跑完待处理队列。
    固定单线程串行执行 find_agent（workers 参数已废弃，始终为 1）。
    """
    started_at = datetime.now()
    batch_run_id = uuid.uuid4().hex
    if int(workers) != 1:
        logger.warning(
            "discover_videos_from_demands ignores workers=%s; running single-threaded",
            workers,
        )
    ensure_mysql_pool_capacity(1)

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

    preload_stats = {"total_loaded": len(contexts), "skipped_already_done": 0}
    if not force:
        contexts, preload_stats = filter_pending_contexts(
            contexts,
            resolved_biz_dt,
            skip_finished=skip_finished,
        )

    if offset > 0:
        contexts = contexts[int(offset) :]

    if limit is not None and limit >= 0:
        contexts = contexts[: int(limit)]

    passed_videos = _count_passed_videos(resolved_biz_dt)

    logger.info(
        "discover_videos_from_demands start: biz_dt=%s batch_run_id=%s workers=%s top_limit=%s offset=%s pending=%s skipped=%s passed_videos=%s passed_video_limit=%s",
        resolved_biz_dt,
        batch_run_id,
        workers,
        top_limit,
        offset,
        len(contexts),
        preload_stats.get("skipped_already_done", 0),
        passed_videos,
        _DAILY_PASSED_VIDEO_LIMIT,
    )

    result: dict[str, Any] = {
        "success": True,
        "batch_run_id": batch_run_id,
        "biz_dt": resolved_biz_dt,
        "started_at": started_at.isoformat(),
        "offset": int(offset),
        "top_limit": top_limit,
        **preload_stats,
        "total_records": len(contexts),
        "workers": 0,
        "processed": 0,
        "succeeded": 0,
        "skipped": 0,
        "failed": 0,
        "errors": [],
        "passed_videos": passed_videos,
        "passed_video_limit": _DAILY_PASSED_VIDEO_LIMIT,
        "stopped_by_passed_video_limit": passed_videos
        >= _DAILY_PASSED_VIDEO_LIMIT,
    }

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

    worker_count = 1
    result["workers"] = worker_count

    with ThreadPoolExecutor(max_workers=worker_count) as executor:
        next_context = 0
        while next_context < len(contexts):
            remaining_slots = _DAILY_PASSED_VIDEO_LIMIT - passed_videos
            if remaining_slots <= 0:
                result["stopped_by_passed_video_limit"] = True
                break

            batch_size = min(
                worker_count,
                remaining_slots,
                len(contexts) - next_context,
            )
            batch = contexts[next_context : next_context + batch_size]
            next_context += batch_size
            logger.info(
                "discover videos batch start: biz_dt=%s batch_size=%d "
                "completed_batches=%d/%d demands=%s",
                resolved_biz_dt,
                len(batch),
                next_context // batch_size,
                (len(contexts) + batch_size - 1) // batch_size,
                [
                    f"{ctx.demand_grade_id}:{ctx.demand_name}"
                    for ctx in batch
                ],
            )
            future_to_ctx = {
                executor.submit(process_single_discover, ctx, force=force): ctx
                for ctx in batch
            }
            pending = set(future_to_ctx.keys())
            batch_started = monotonic()
            while pending:
                completed, pending = wait(
                    pending,
                    timeout=60,
                    return_when=FIRST_COMPLETED,
                )
                if not completed:
                    pending_demands = [
                        f"{future_to_ctx[future].demand_grade_id}:"
                        f"{future_to_ctx[future].demand_name}"
                        for future in pending
                    ]
                    logger.info(
                        "discover videos still running: biz_dt=%s "
                        "batch_pending=%d processed=%d elapsed_seconds=%d "
                        "pending_demands=%s",
                        resolved_biz_dt,
                        len(pending),
                        result["processed"],
                        int(monotonic() - batch_started),
                        pending_demands,
                    )
                    continue

                for future in completed:
                    ctx = future_to_ctx[future]
                    try:
                        item_result = future.result()
                    except Exception as exc:
                        logger.exception(
                            "discover videos worker 出现未捕获错误: biz_dt=%s "
                            "grade_id=%s demand=%s",
                            resolved_biz_dt,
                            ctx.demand_grade_id,
                            ctx.demand_name,
                        )
                        result["failed"] += 1
                        result["processed"] += 1
                        result["errors"].append(
                            {
                                "demand_grade_id": ctx.demand_grade_id,
                                "demand_name": ctx.demand_name,
                                "error": str(exc),
                            }
                        )
                        logger.info(
                            "discover videos batch item failed: grade_id=%s "
                            "demand=%s batch_remaining=%d total_processed=%d",
                            ctx.demand_grade_id,
                            ctx.demand_name,
                            len(pending),
                            result["processed"],
                        )
                        continue

                    result["processed"] += 1
                    logger.info(
                        "discover videos batch item finished: grade_id=%s demand=%s "
                        "success=%s skipped=%s batch_remaining=%d total_processed=%d",
                        ctx.demand_grade_id,
                        ctx.demand_name,
                        item_result.get("success"),
                        item_result.get("skipped"),
                        len(pending),
                        result["processed"],
                    )
                    if item_result.get("skipped"):
                        result["skipped"] += 1
                        continue
                    if item_result.get("success"):
                        result["succeeded"] += 1
                        continue

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

            passed_videos = _count_passed_videos(resolved_biz_dt)
            result["passed_videos"] = passed_videos
            if passed_videos >= _DAILY_PASSED_VIDEO_LIMIT:
                result["stopped_by_passed_video_limit"] = True
                break

    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("discover_videos_from_demands finished: %s", result)
    return result


if __name__ == "__main__":
    import sys

    from supply_infra.scheduler.cli_result import run_cli

    def _read_flag_value(args: list[str], flag: str) -> int | None:
        if flag not in args:
            return None
        idx = args.index(flag)
        if idx + 1 >= len(args):
            return None
        return int(args[idx + 1])

    _args = sys.argv[1:]
    _biz_dt = _args[0] if _args and not _args[0].startswith("-") else None

    run_cli(
        lambda: discover_videos_from_demands(
            _biz_dt,
            workers=1,
            offset=_read_flag_value(_args, "--offset") or 0,
            limit=_read_flag_value(_args, "--limit"),
            top_limit=_read_flag_value(_args, "--top-limit"),
            skip_finished="--force" not in _args,
            force="--force" in _args,
        ),
        label="discover_videos_from_demands",
    )
