from __future__ import annotations

from collections.abc import Awaitable, Callable
from typing import Any

from fastapi import HTTPException
from sqlalchemy.ext.asyncio import AsyncSession

from app.enums.common import ModuleEventTypeEnum, ModuleProjectStatusEnum, ModuleStepStatusEnum
from app.models.module_generation_project import ModuleGenerationProject
from app.models.module_generation_step import ModuleGenerationStep
from app.models.user import User
from app.services.module_generation_flow_base_service import (
    create_module_step,
    get_current_step_by_code,
    get_project_for_user,
    get_step_for_user,
    soft_delete_steps_from_index,
)
from app.enums.module_generation_flow import ModuleGenerationFlowConfig
from app.services.video_prompt_schema_config_service import fallback_runtime_schema_snapshot
from app.services.module_generation_step_common_service import (
    build_step_input,
    build_step_output,
    force_set_json,
    merge_dict,
    step_payload,
    step_usage,
    unwrap_step_output,
    utc_now,
)

LogModuleEventCallable = Callable[..., Awaitable[None]]
PatchVideoPromptSchemaCallable = Callable[..., dict[str, Any]]
BuildFinalVideoPromptCallable = Callable[[dict[str, Any]], str]


async def update_module_step(
    db: AsyncSession,
    *,
    current_user: User,
    project_id: str,
    step_id: str,
    req: Any,
    config: ModuleGenerationFlowConfig,
    log_module_event: LogModuleEventCallable,
) -> tuple[ModuleGenerationProject, ModuleGenerationStep]:
    project = await get_project_for_user(db, project_id=project_id, user=current_user, config=config, for_update=True)
    step = await get_step_for_user(db, project_id=project_id, step_id=step_id, user=current_user, config=config, for_update=True)
    if step.status == ModuleStepStatusEnum.PROCESSING.value:
        raise HTTPException(status_code=400, detail="当前子任务正在处理中，暂不能修改")

    input_data = step_payload(step.input_json, schema_version=config.step_io_schema_version)
    output_data = unwrap_step_output(step.output_json, schema_version=config.step_io_schema_version)

    if step.step_code == config.material_step_code:
        input_data = merge_dict(
            input_data,
            {
                "material_video_url": getattr(req, "material_video_url", None),
                "material_image_url": getattr(req, "material_image_url", None),
                "source_project_name": getattr(req, "source_project_name", None),
                "target_project_name": getattr(req, "target_project_name", None),
                "core_content_point": getattr(req, "core_content_point", None),
            },
        )
        target_project_name = getattr(req, "target_project_name", None)
        if target_project_name:
            project.title = target_project_name
    elif step.step_code == config.image_prompt_step_code:
        prompt = getattr(req, "prompt", None)
        if prompt is not None:
            output_data["optimized_prompt"] = prompt
            output_data["prompt"] = prompt
    elif step.step_code == config.video_prompt_step_code:
        prompt_schema = getattr(req, "prompt_schema", None)
        prompt = getattr(req, "prompt", None)
        if prompt_schema is not None:
            output_data["prompt_schema"] = prompt_schema
        if prompt is not None:
            output_data["final_prompt"] = prompt
    else:
        if getattr(req, "input_json", None):
            input_data = merge_dict(input_data, req.input_json)
        if getattr(req, "output_json", None):
            output_data = merge_dict(output_data, req.output_json)

    if getattr(req, "input_json", None):
        input_data = merge_dict(input_data, req.input_json)
    if getattr(req, "output_json", None):
        output_data = merge_dict(output_data, req.output_json)

    force_set_json(
        step,
        "input_json",
        build_step_input(
            step_code=step.step_code,
            payload=input_data,
            source_step_id=step.source_step_id,
            parent_step_id=step.parent_step_id,
            schema_version=config.step_io_schema_version,
        ),
    )
    force_set_json(
        step,
        "output_json",
        build_step_output(step_code=step.step_code, status=ModuleStepStatusEnum.COMPLETED.value, payload=output_data, schema_version=config.step_io_schema_version),
    )
    step.status = ModuleStepStatusEnum.COMPLETED.value
    step.error_message = None
    step.completed_at = utc_now()
    project.status = ModuleProjectStatusEnum.WAITING_USER.value
    project.current_step_code = step.step_code
    project.error_message = None

    await soft_delete_steps_from_index(
        db,
        project=project,
        start_index=step.step_index + 1,
        config=config,
        log_module_event=log_module_event,
    )
    await log_module_event(db, project=project, step=step, event_type=ModuleEventTypeEnum.STEP_UPDATED.value, message="用户修改子任务内容")
    await db.flush()
    return project, step


async def update_module_material_input(
    db: AsyncSession,
    *,
    current_user: User,
    project_id: str,
    req: Any,
    config: ModuleGenerationFlowConfig,
    log_module_event: LogModuleEventCallable,
) -> tuple[str, str]:
    project = await get_project_for_user(db, project_id=project_id, user=current_user, config=config, for_update=True)
    old_material_step = await get_current_step_by_code(db, project_id=project.id, step_code=config.material_step_code, config=config)
    old_material = step_payload(old_material_step.input_json if old_material_step else None, schema_version=config.step_io_schema_version)

    material_video_url = (
        getattr(req, "material_video_url", None)
        if config.material_video_url_editable and getattr(req, "material_video_url", None) is not None
        else old_material.get("material_video_url")
    )
    material = {
        "material_video_url": material_video_url,
        "material_image_url": getattr(req, "material_image_url", None) if getattr(req, "material_image_url", None) is not None else old_material.get("material_image_url"),
        "source_project_name": getattr(req, "source_project_name", None) if getattr(req, "source_project_name", None) is not None else old_material.get("source_project_name"),
        "target_project_name": getattr(req, "target_project_name", None) if getattr(req, "target_project_name", None) is not None else old_material.get("target_project_name"),
        "core_content_point": getattr(req, "core_content_point", None) if getattr(req, "core_content_point", None) is not None else old_material.get("core_content_point"),
    }

    missing_fields = [key for key, value in material.items() if value is None or str(value).strip() == ""]
    if missing_fields:
        raise HTTPException(status_code=400, detail=f"素材输入缺少必要字段: {', '.join(missing_fields)}")

    await soft_delete_steps_from_index(
        db,
        project=project,
        start_index=config.step_index_map[config.material_step_code],
        config=config,
        log_module_event=log_module_event,
    )

    project.title = str(material["target_project_name"])
    project.status = ModuleProjectStatusEnum.WAITING_USER.value
    project.current_step_code = config.material_step_code
    project.final_image_url = None
    project.final_video_url = None
    project.final_video_cover_url = None
    project.error_message = None
    project.completed_at = None

    new_step = await create_module_step(
        db,
        project=project,
        step_code=config.material_step_code,
        config=config,
        log_module_event=log_module_event,
        status=ModuleStepStatusEnum.COMPLETED.value,
        input_data=material,
        output_data={"message": "素材输入已修改，旧步骤已软删除。下一步请重新生成图片AI提词。"},
    )
    await log_module_event(
        db,
        project=project,
        step=new_step,
        event_type=ModuleEventTypeEnum.STEP_UPDATED.value,
        message="用户修改素材输入并重建第1步新版本",
        detail={
            "old_material_step_id": old_material_step.id if old_material_step else None,
            "new_material_step_id": new_step.id,
            "version": new_step.version,
        },
    )
    return project.id, new_step.id


async def update_module_image_prompt(
    db: AsyncSession,
    *,
    current_user: User,
    project_id: str,
    step_id: str,
    req: Any,
    config: ModuleGenerationFlowConfig,
    log_module_event: LogModuleEventCallable,
) -> tuple[ModuleGenerationProject, ModuleGenerationStep]:
    project = await get_project_for_user(db, project_id=project_id, user=current_user, config=config, for_update=True)
    step = await get_step_for_user(db, project_id=project_id, step_id=step_id, user=current_user, config=config, for_update=True)
    if step.step_code != config.image_prompt_step_code:
        raise HTTPException(status_code=400, detail="只能修改第2步图片 AI 提词子任务")
    if step.status != ModuleStepStatusEnum.COMPLETED.value:
        raise HTTPException(status_code=400, detail="图片 AI 提词未完成，不能直接修改")

    output_data = step_payload(step.output_json, schema_version=config.step_io_schema_version)
    usage = step_usage(step.output_json, schema_version=config.step_io_schema_version)
    new_prompt = req.prompt.strip()
    output_data["optimized_prompt"] = new_prompt
    output_data["prompt"] = new_prompt
    output_data["manual_edited"] = True
    output_data["manual_edited_at"] = utc_now().isoformat()

    force_set_json(
        step,
        "output_json",
        build_step_output(
            step_code=config.image_prompt_step_code,
            status=ModuleStepStatusEnum.COMPLETED.value,
            payload=output_data,
            usage=usage,
            schema_version=config.step_io_schema_version,
        ),
    )
    step.status = ModuleStepStatusEnum.COMPLETED.value
    step.error_message = None
    step.completed_at = utc_now()

    await soft_delete_steps_from_index(
        db,
        project=project,
        start_index=config.step_index_map[config.image_generate_step_code],
        config=config,
        log_module_event=log_module_event,
    )

    project.status = ModuleProjectStatusEnum.WAITING_USER.value
    project.current_step_code = config.image_prompt_step_code
    project.final_image_url = None
    project.final_video_url = None
    project.final_video_cover_url = None
    project.completed_at = None
    project.error_message = None

    await log_module_event(
        db,
        project=project,
        step=step,
        event_type=ModuleEventTypeEnum.STEP_UPDATED.value,
        message="用户直接修改图片 AI 优化提词，已软删除后续步骤",
        detail={"start_deleted_step_index": config.step_index_map[config.image_generate_step_code]},
    )
    await db.flush()
    return project, step


async def update_module_video_prompt_schema(
    db: AsyncSession,
    *,
    current_user: User,
    project_id: str,
    step_id: str,
    req: Any,
    config: ModuleGenerationFlowConfig,
    log_module_event: LogModuleEventCallable,
    patch_video_prompt_schema_from_client: PatchVideoPromptSchemaCallable,
    build_final_video_prompt: BuildFinalVideoPromptCallable,
) -> tuple[ModuleGenerationProject, ModuleGenerationStep]:
    project = await get_project_for_user(db, project_id=project_id, user=current_user, config=config, for_update=True)
    step = await get_step_for_user(db, project_id=project_id, step_id=step_id, user=current_user, config=config, for_update=True)
    if step.step_code != config.video_prompt_step_code:
        raise HTTPException(status_code=400, detail="只能修改第4步视频 AI 提词 JSON schema 子任务")
    if step.status != ModuleStepStatusEnum.COMPLETED.value:
        raise HTTPException(status_code=400, detail="视频 AI 提词未完成，不能直接修改")

    output_data = step_payload(step.output_json, schema_version=config.step_io_schema_version)
    usage = step_usage(step.output_json, schema_version=config.step_io_schema_version)
    input_data = step_payload(step.input_json, schema_version=config.step_io_schema_version)
    server_schema = output_data.get("prompt_schema") if isinstance(output_data.get("prompt_schema"), dict) else {}
    video_config = output_data.get("params_used_for_prompt") or input_data.get("video_config") or {}
    if not isinstance(video_config, dict) or not video_config.get("duration") or not video_config.get("aspect_ratio") or not video_config.get("resolution"):
        raise HTTPException(status_code=400, detail="缺少第4步视频参数快照，不能安全修改视频 schema")

    schema_config_snapshot = fallback_runtime_schema_snapshot(output_data.get("schema_config_snapshot"))
    try:
        patched_schema = patch_video_prompt_schema_from_client(
            server_schema=server_schema,
            client_schema=req.prompt_schema,
            video_config=video_config,
            schema_config_snapshot=schema_config_snapshot,
        )
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    final_prompt = build_final_video_prompt(patched_schema)

    output_data["prompt_schema"] = patched_schema
    output_data["final_prompt"] = final_prompt
    output_data["params_used_for_prompt"] = video_config
    output_data["manual_edited"] = True
    output_data["manual_edited_at"] = utc_now().isoformat()

    force_set_json(
        step,
        "output_json",
        build_step_output(
            step_code=config.video_prompt_step_code,
            status=ModuleStepStatusEnum.COMPLETED.value,
            payload=output_data,
            usage=usage,
            schema_version=config.step_io_schema_version,
        ),
    )
    step.status = ModuleStepStatusEnum.COMPLETED.value
    step.error_message = None
    step.completed_at = utc_now()

    await soft_delete_steps_from_index(
        db,
        project=project,
        start_index=config.step_index_map[config.video_generate_step_code],
        config=config,
        log_module_event=log_module_event,
    )

    project.status = ModuleProjectStatusEnum.WAITING_USER.value
    project.current_step_code = config.video_prompt_step_code
    project.final_video_url = None
    project.final_video_cover_url = None
    project.completed_at = None
    project.error_message = None

    await log_module_event(
        db,
        project=project,
        step=step,
        event_type=ModuleEventTypeEnum.STEP_UPDATED.value,
        message="用户修改视频 AI 提词 schema，已软删除视频生成步骤",
        detail={
            "start_deleted_step_index": config.step_index_map[config.video_generate_step_code],
            "locked_fields": [
                "schema_version",
                "schema_usage",
                "画面属性.视频时长",
                "画面属性.视频比例",
                "画面属性.清晰度",
                "画面属性.帧率",
                "画面属性.推荐分辨率",
                "动作流程[*].时间段",
                "镜头流程[*].时间段",
                "动态时间规划",
                "输出规格限制",
                "质量控制",
                "合规控制",
            ],
        },
    )
    await db.flush()
    return project, step
