"""计划组落库后执行分级任务（默认定时任务路径为代码自动分配，非统筹 Agent）。"""
from __future__ import annotations

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

from agents.demand_grade_agent.run import main as grade_demand_words
from supply_infra.config import get_infra_settings
from supply_infra.scheduler.auto_assign_grade_plan import auto_assign_daily_grade_plan
from supply_infra.db.repositories.demand_grade_plan_repo import DemandGradePlanRepository
from supply_infra.db.repositories.demand_grade_repo import DemandGradeRepository
from supply_infra.db.repositories.multi_demand_pool_di_repo import MultiDemandPoolDiRepository
from supply_infra.db.session import get_session
from supply_infra.scheduler.plan_group_batch import MAX_DEMANDS_PER_BATCH, split_even_batches

logger = logging.getLogger(__name__)

_DEFAULT_WORKERS = 5


def _resolve_biz_dt(biz_dt: str | None) -> str:
    if biz_dt:
        return biz_dt
    return datetime.now(ZoneInfo(get_infra_settings().scheduler_timezone)).strftime("%Y%m%d")


def _materialize_pending_group_items(biz_dt: str) -> int:
    """执行前为 pending 计划组物化待分级需求明细。"""
    with get_session() as session:
        graded_names = DemandGradeRepository(session).get_existing_demand_names(biz_dt)
        return DemandGradePlanRepository(session).materialize_pending_groups(
            biz_dt,
            graded_names=graded_names,
        )


def _group_success(item_summary: dict[str, int], processed_batches: int) -> bool:
    if item_summary.get("pending", 0) > 0:
        return False
    total = sum(item_summary.values())
    if total == 0:
        return True
    if processed_batches > 0:
        return True
    return item_summary.get("failed", 0) == 0 and (
        item_summary.get("finished", 0) > 0 or item_summary.get("skipped", 0) > 0
    )


def _run_group(biz_dt: str, group_id: int, *, max_demands_per_batch: int) -> int:
    """领取并执行一个固定任务，从组内需求明细表按批读取。"""
    with get_session() as session:
        group = DemandGradePlanRepository(session).claim_group(biz_dt, group_id)
    if group is None:
        logger.warning("分级任务已被其他 worker 领取或状态已变化: biz_dt=%s group_id=%s", biz_dt, group_id)
        return 0

    gid = int(group["id"])
    processed_batches = 0
    batch_errors: list[str] = []
    try:
        with get_session() as session:
            items = DemandGradePlanRepository(session).list_pending_group_items(gid)

        for batch_items in split_even_batches(items, max_per_batch=max_demands_per_batch):
            demands = [
                {"pool_id": item["pool_id"], "demand_name": item["demand_name"]}
                for item in batch_items
            ]
            item_ids = [int(item["item_id"]) for item in batch_items]
            try:
                grade_demand_words(demands, biz_dt=biz_dt)
                with get_session() as session:
                    DemandGradePlanRepository(session).mark_group_items_status(
                        item_ids,
                        status="finished",
                    )
                processed_batches += 1
            except Exception as exc:
                logger.exception(
                    "分级子批次失败，跳过并继续本组其余需求: biz_dt=%s group=%s count=%s",
                    biz_dt,
                    group["group_key"],
                    len(demands),
                )
                batch_errors.append(str(exc))
                with get_session() as session:
                    DemandGradePlanRepository(session).mark_group_items_status(
                        item_ids,
                        status="failed",
                        error_message=str(exc),
                    )

        with get_session() as session:
            repo = DemandGradePlanRepository(session)
            item_summary = repo.summarize_group_items(gid)
            repo.finish_group(
                gid,
                success=_group_success(item_summary, processed_batches),
                error_message="; ".join(batch_errors) if batch_errors else None,
            )
    except Exception as exc:
        logger.exception(
            "分级计划任务失败: biz_dt=%s group=%s",
            biz_dt,
            group["group_key"],
        )
        try:
            with get_session() as session:
                DemandGradePlanRepository(session).finish_group(
                    gid,
                    success=False,
                    error_message=str(exc),
                )
        except Exception:
            logger.exception(
                "记录分级计划任务失败状态时发生错误: biz_dt=%s group=%s",
                biz_dt,
                group["group_key"],
            )
        return 0
    return processed_batches


def _execute_plan_tasks(
    biz_dt: str,
    *,
    workers: int,
    max_demands_per_batch: int,
) -> dict[str, Any]:
    """并发执行当天全部 pending 计划任务，仅执行一轮。"""
    with get_session() as session:
        group_ids = DemandGradePlanRepository(session).list_pending_group_ids(biz_dt)
    if not group_ids:
        with get_session() as session:
            final_snapshot = DemandGradePlanRepository(session).get_execution_snapshot(biz_dt)
        return {
            "attempted_groups": 0,
            "groups_run": 0,
            "workers": 0,
            "final_snapshot": final_snapshot,
            "execution_complete": final_snapshot["execution_complete"],
        }

    worker_count = max(1, min(int(workers), len(group_ids)))
    groups_run = 0
    with ThreadPoolExecutor(max_workers=worker_count) as executor:
        futures = [
            executor.submit(_run_group, biz_dt, group_id, max_demands_per_batch=max_demands_per_batch)
            for group_id in group_ids
        ]
        for future in as_completed(futures):
            try:
                groups_run += future.result()
            except Exception:
                logger.exception("分级任务 worker 出现未捕获错误: biz_dt=%s", biz_dt)

    with get_session() as session:
        final_snapshot = DemandGradePlanRepository(session).get_execution_snapshot(biz_dt)
    return {
        "attempted_groups": len(group_ids),
        "groups_run": groups_run,
        "workers": worker_count,
        "final_snapshot": final_snapshot,
        "execution_complete": final_snapshot["execution_complete"],
    }


def execute_plan_tasks_until_complete(
    biz_dt: str,
    *,
    workers: int,
    max_demands_per_batch: int = MAX_DEMANDS_PER_BATCH,
    max_rounds: int = 0,
) -> dict[str, Any]:
    """循环执行 pending 计划任务，直到全部完成或达到 max_rounds。"""
    rounds: list[dict[str, Any]] = []
    groups_run = 0
    round_no = 0
    while True:
        if max_rounds > 0 and round_no >= max_rounds:
            break
        round_no += 1
        result = _execute_plan_tasks(
            biz_dt,
            workers=workers,
            max_demands_per_batch=max_demands_per_batch,
        )
        rounds.append(result)
        groups_run += int(result.get("groups_run") or 0)
        if int(result.get("attempted_groups") or 0) == 0:
            break

    final_snapshot = rounds[-1]["final_snapshot"] if rounds else None
    if final_snapshot is None:
        with get_session() as session:
            final_snapshot = DemandGradePlanRepository(session).get_execution_snapshot(biz_dt)

    return {
        "rounds": round_no,
        "groups_run": groups_run,
        "workers": max(1, int(workers)),
        "round_details": rounds,
        "final_snapshot": final_snapshot,
        "execution_complete": bool(final_snapshot["execution_complete"]),
    }


def _grade_demand_pool_impl(
    resolved_biz_dt: str,
    *,
    workers: int,
    max_demands_per_batch: int = MAX_DEMANDS_PER_BATCH,
    with_orchestrate: bool = True,
    max_rounds: int = 0,
) -> dict[str, Any]:
    with get_session() as session:
        total = MultiDemandPoolDiRepository(session).count_distinct_demand_names(resolved_biz_dt)
        graded_before = DemandGradeRepository(session).count_by_biz_dt(resolved_biz_dt)

    if with_orchestrate:
        try:
            auto_assign_daily_grade_plan(
                biz_dt=resolved_biz_dt,
                max_demands_per_group=max_demands_per_batch,
            )
        except Exception:
            logger.exception("自动分配计划组失败，继续处理数据库中已有任务: biz_dt=%s", resolved_biz_dt)

    materialized = _materialize_pending_group_items(resolved_biz_dt)
    logger.info("物化计划组需求明细: biz_dt=%s items=%s", resolved_biz_dt, materialized)

    plan_execution = execute_plan_tasks_until_complete(
        resolved_biz_dt,
        workers=max(1, int(workers)),
        max_demands_per_batch=max(1, min(int(max_demands_per_batch), MAX_DEMANDS_PER_BATCH)),
        max_rounds=max_rounds,
    )

    with get_session() as session:
        graded_after = DemandGradeRepository(session).count_by_biz_dt(resolved_biz_dt)
    final_snapshot = plan_execution["final_snapshot"]
    result = {
        "success": bool(plan_execution["execution_complete"]),
        "biz_dt": resolved_biz_dt,
        "total": total,
        "graded_before": graded_before,
        "graded_after": graded_after,
        "materialized_items": materialized,
        "planned_category_count": len(final_snapshot["assigned_category_ids"]),
        "planned_groups": final_snapshot["planned_groups"],
        "group_status": final_snapshot["group_status"],
        "plan_execution": plan_execution,
        "workers": plan_execution["workers"],
        "groups_run": plan_execution["groups_run"],
        "run_at": datetime.now().isoformat(),
    }
    logger.info("Grade demand pool completed: %s", result)
    return result


def retry_failed_plan_group_items(
    biz_dt: str | None = None,
    *,
    workers: int = _DEFAULT_WORKERS,
    max_demands_per_batch: int = MAX_DEMANDS_PER_BATCH,
    group_ids: list[int] | None = None,
    dry_run: bool = False,
) -> dict[str, Any]:
    """将 demand_grade_plan_group_item 中 failed 记录重置后重新执行分级。"""
    resolved_biz_dt = _resolve_biz_dt(biz_dt)
    try:
        with get_session() as session:
            failed_items = DemandGradePlanRepository(session).list_failed_group_items(
                resolved_biz_dt,
                group_ids=group_ids,
            )

        if not failed_items:
            with get_session() as session:
                snapshot = DemandGradePlanRepository(session).get_execution_snapshot(resolved_biz_dt)
            return {
                "success": True,
                "biz_dt": resolved_biz_dt,
                "dry_run": dry_run,
                "failed_items": 0,
                "reset": {"reset_items": 0, "reset_groups": 0, "group_ids": [], "items": []},
                "execution": None,
                "final_snapshot": snapshot,
                "run_at": datetime.now().isoformat(),
            }

        if dry_run:
            affected_group_ids = sorted({int(item["group_id"]) for item in failed_items})
            return {
                "success": True,
                "biz_dt": resolved_biz_dt,
                "dry_run": True,
                "failed_items": len(failed_items),
                "reset": {
                    "reset_items": len(failed_items),
                    "reset_groups": len(affected_group_ids),
                    "group_ids": affected_group_ids,
                    "items": failed_items,
                },
                "execution": None,
                "run_at": datetime.now().isoformat(),
            }

        with get_session() as session:
            reset_result = DemandGradePlanRepository(session).reset_failed_items_to_pending(
                resolved_biz_dt,
                group_ids=group_ids,
            )

        with get_session() as session:
            graded_before = DemandGradeRepository(session).count_by_biz_dt(resolved_biz_dt)

        plan_execution = execute_plan_tasks_until_complete(
            resolved_biz_dt,
            workers=max(1, int(workers)),
            max_demands_per_batch=max(1, min(int(max_demands_per_batch), MAX_DEMANDS_PER_BATCH)),
        )

        with get_session() as session:
            graded_after = DemandGradeRepository(session).count_by_biz_dt(resolved_biz_dt)
            remaining_failed = DemandGradePlanRepository(session).list_failed_group_items(
                resolved_biz_dt,
                group_ids=group_ids,
            )

        final_snapshot = plan_execution["final_snapshot"]
        return {
            "success": len(remaining_failed) == 0,
            "biz_dt": resolved_biz_dt,
            "dry_run": False,
            "failed_items": len(failed_items),
            "reset": reset_result,
            "graded_before": graded_before,
            "graded_after": graded_after,
            "remaining_failed": len(remaining_failed),
            "execution": plan_execution,
            "final_snapshot": final_snapshot,
            "group_status": final_snapshot["group_status"],
            "run_at": datetime.now().isoformat(),
        }
    except Exception as exc:
        logger.exception(
            "重试 failed plan group items 发生未捕获错误: biz_dt=%s",
            resolved_biz_dt,
        )
        return {
            "success": False,
            "biz_dt": resolved_biz_dt,
            "error": str(exc),
            "run_at": datetime.now().isoformat(),
        }


def grade_demand_pool(
    biz_dt: str | None = None,
    *,
    workers: int = _DEFAULT_WORKERS,
    max_demands_per_batch: int = MAX_DEMANDS_PER_BATCH,
    with_orchestrate: bool = True,
    max_rounds: int = 0,
) -> dict[str, Any]:
    """执行完整分级闭环；任何错误只记录日志并返回，不向上中断定时任务。"""
    resolved_biz_dt = str(biz_dt or "")
    try:
        resolved_biz_dt = _resolve_biz_dt(biz_dt)
        return _grade_demand_pool_impl(
            resolved_biz_dt,
            workers=workers,
            max_demands_per_batch=max_demands_per_batch,
            with_orchestrate=with_orchestrate,
            max_rounds=max_rounds,
        )
    except Exception as exc:
        logger.exception("需求分级任务发生未捕获错误，已阻止异常中断定时任务: biz_dt=%s", resolved_biz_dt)
        return {
            "success": False,
            "biz_dt": resolved_biz_dt,
            "error": str(exc),
            "run_at": datetime.now().isoformat(),
        }


if __name__ == "__main__":
    import sys

    from supply_infra.scheduler.cli_result import run_cli

    _argv = [a for a in sys.argv[1:] if a != "--retry-failed"]
    _retry_failed = "--retry-failed" in sys.argv[1:]
    _biz_dt = _argv[0] if len(_argv) > 0 else None
    _workers = int(_argv[1]) if len(_argv) > 1 else 5

    def _main() -> dict[str, Any]:
        if _retry_failed:
            return retry_failed_plan_group_items(_biz_dt, workers=_workers)
        return grade_demand_pool(
            _biz_dt,
            workers=_workers,
            with_orchestrate=True,
        )

    run_cli(_main, label="grade_demand_pool")
