from __future__ import annotations

from typing import Annotated, NoReturn

from fastapi import APIRouter, BackgroundTasks, Depends, Header, HTTPException, Query, status
from sqlalchemy.orm import Session

from app.api.deps import (
    CurrentUserContext,
    get_current_user,
    get_db,
    require_platform_admin_user,
    require_rule_editor_user,
    require_rule_reviewer_user,
)
from app.db.session import get_session_factory
from app.schemas.agent_asset import (
    AgentAssetRead,
    AgentAssetRiskRuleDraftUpdate,
    AgentAssetRiskRuleEnabledUpdate,
    AgentAssetRiskRuleFeedbackCreate,
    AgentAssetRiskRuleFeedbackRead,
    AgentAssetRiskRuleGenerateRequest,
    AgentAssetRiskRuleLatestTestSummary,
    AgentAssetRiskRuleLevelUpdate,
    AgentAssetRiskRuleRegenerateRequest,
    AgentAssetRiskRuleReportRequest,
    AgentAssetRiskRuleReturnRequest,
    AgentAssetRiskRuleRevisionCreate,
    AgentAssetRiskRuleSampleTestRequest,
    AgentAssetRiskRuleScenarioTestRequest,
    AgentAssetRiskRuleSimulationRead,
    AgentAssetRiskRuleSimulationRequest,
    AgentAssetRiskRuleTemplateGroupRead,
    AgentAssetRiskRuleTestRunRead,
    AgentAssetRuleJsonRead,
    AgentAssetRuleJsonWrite,
)
from app.services.agent_asset_access import stable_user_principal
from app.services.agent_asset_risk_rule_regeneration import AgentAssetRiskRuleRegenerationService
from app.services.agent_asset_risk_rule_revision import AgentAssetRiskRuleRevisionService
from app.services.agent_assets import AgentAssetService
from app.services.risk_rule_generation_jobs import RiskRuleGenerationJobService
from app.services.risk_rule_template_catalog import list_risk_rule_template_groups

router = APIRouter(prefix="/agent-assets")
DbSession = Annotated[Session, Depends(get_db)]
ActorHeader = Annotated[
    str | None,
    Header(description="兼容旧客户端；审计主体始终以登录会话中的稳定身份为准。"),
]
RequestIdHeader = Annotated[
    str | None,
    Header(description="外部请求 ID，用于串联审计日志和上游调用链。"),
]
RuleEditorUser = Annotated[CurrentUserContext, Depends(require_rule_editor_user)]
RuleReviewerUser = Annotated[CurrentUserContext, Depends(require_rule_reviewer_user)]
PlatformAdminUser = Annotated[CurrentUserContext, Depends(require_platform_admin_user)]
CurrentUser = Annotated[CurrentUserContext, Depends(get_current_user)]


def _handle_asset_error(exc: Exception) -> NoReturn:
    if isinstance(exc, (LookupError, FileNotFoundError)):
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
    if isinstance(exc, (PermissionError, ValueError)):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
    raise exc


def _actor_name(current_user: CurrentUserContext, x_actor: str | None) -> str:
    del x_actor
    return stable_user_principal(current_user)


def _read_asset(
    db: Session,
    asset_id: str,
    current_user: CurrentUserContext,
) -> AgentAssetRead:
    asset = AgentAssetService(db, current_user=current_user).get_asset(asset_id)
    if asset is None:
        raise LookupError("Asset not found")
    return asset


def _complete_risk_rule_generation_task(
    asset_id: str,
    payload: dict,
    actor: str,
    request_id: str | None,
    tenant_id: str,
) -> None:
    db = get_session_factory()()
    try:
        body = AgentAssetRiskRuleGenerateRequest.model_validate(payload)
        RiskRuleGenerationJobService(db).complete_rule_asset_generation(
            asset_id,
            body,
            tenant_id=tenant_id,
            actor=actor,
            request_id=request_id,
        )
    finally:
        db.close()


@router.get(
    "/risk-rules/templates",
    response_model=list[AgentAssetRiskRuleTemplateGroupRead],
    summary="查询常见费控风险规则模板",
    description=(
        "返回模板分组、默认自然语言、字段清单和 DSL 样例；模板只用于预填，不绕过通用生成链路。"
    ),
)
def list_risk_rule_templates(_: CurrentUser) -> list[AgentAssetRiskRuleTemplateGroupRead]:
    return list_risk_rule_template_groups()


@router.patch(
    "/{asset_id}/risk-rules/draft",
    response_model=AgentAssetRead,
    summary="编辑未上线风险规则草稿",
    description="仅允许编辑从未上线的自然语言风险规则草稿或生成失败规则，不直接覆盖已上线版本。",
)
def update_risk_rule_draft(
    asset_id: str,
    payload: AgentAssetRiskRuleDraftUpdate,
    current_user: RuleEditorUser,
    db: DbSession,
    x_actor: ActorHeader = None,
    x_request_id: RequestIdHeader = None,
) -> AgentAssetRead:
    try:
        AgentAssetRiskRuleRevisionService(db, current_user=current_user).update_unpublished_draft(
            asset_id,
            payload,
            actor=_actor_name(current_user, x_actor),
            request_id=x_request_id,
        )
        return _read_asset(db, asset_id, current_user)
    except Exception as exc:
        _handle_asset_error(exc)


@router.post(
    "/{asset_id}/risk-rules/revisions",
    response_model=AgentAssetRead,
    status_code=status.HTTP_201_CREATED,
    summary="创建已上线风险规则修订草稿",
    description="为已上线或已下线的自然语言风险规则创建修订草稿，保留当前生效版本不变。",
)
def create_risk_rule_revision(
    asset_id: str,
    payload: AgentAssetRiskRuleRevisionCreate,
    current_user: RuleEditorUser,
    db: DbSession,
    x_actor: ActorHeader = None,
    x_request_id: RequestIdHeader = None,
) -> AgentAssetRead:
    try:
        AgentAssetRiskRuleRevisionService(db, current_user=current_user).create_revision_draft(
            asset_id,
            payload,
            actor=_actor_name(current_user, x_actor),
            request_id=x_request_id,
        )
        return _read_asset(db, asset_id, current_user)
    except Exception as exc:
        _handle_asset_error(exc)


@router.post(
    "/{asset_id}/risk-rules/regenerate",
    response_model=AgentAssetRead,
    summary="重新生成风险规则执行模板",
    description="把未上线草稿或已上线规则的修订草稿重新解释为 DSL、流程图、风险评分和业务说明。",
)
def regenerate_risk_rule(
    asset_id: str,
    payload: AgentAssetRiskRuleRegenerateRequest,
    current_user: RuleEditorUser,
    db: DbSession,
    x_actor: ActorHeader = None,
    x_request_id: RequestIdHeader = None,
) -> AgentAssetRead:
    try:
        AgentAssetRiskRuleRegenerationService(
            db, current_user=current_user
        ).regenerate(
            asset_id,
            payload,
            tenant_id=current_user.tenant_id,
            actor=_actor_name(current_user, x_actor),
            request_id=x_request_id,
        )
        return _read_asset(db, asset_id, current_user)
    except Exception as exc:
        _handle_asset_error(exc)


@router.post(
    "/{asset_id}/risk-rules/feedback",
    response_model=AgentAssetRiskRuleFeedbackRead,
    status_code=status.HTTP_201_CREATED,
    summary="提交风险规则误判或漏判反馈",
    description="普通用户可提交规则误判、漏判或改进反馈；该接口只记录反馈，不直接修改规则。",
)
def create_risk_rule_feedback(
    asset_id: str,
    payload: AgentAssetRiskRuleFeedbackCreate,
    current_user: CurrentUser,
    db: DbSession,
    x_actor: ActorHeader = None,
    x_request_id: RequestIdHeader = None,
) -> AgentAssetRiskRuleFeedbackRead:
    try:
        return AgentAssetService(db, current_user=current_user).create_risk_rule_feedback(
            asset_id,
            payload,
            actor=_actor_name(current_user, x_actor),
            request_id=x_request_id,
        )
    except Exception as exc:
        _handle_asset_error(exc)


@router.get(
    "/{asset_id}/risk-rules/feedback",
    response_model=list[AgentAssetRiskRuleFeedbackRead],
    summary="查询风险规则反馈记录",
    description="高级财务人员或 admin 管理员查看指定风险规则的误判、漏判和改进反馈记录。",
)
def list_risk_rule_feedback(
    asset_id: str,
    current_user: RuleReviewerUser,
    db: DbSession,
    version: Annotated[str | None, Query(max_length=30)] = None,
    status_value: Annotated[str | None, Query(alias="status", max_length=30)] = None,
    limit: Annotated[int, Query(ge=1, le=200)] = 50,
) -> list[AgentAssetRiskRuleFeedbackRead]:
    try:
        return AgentAssetService(db, current_user=current_user).list_risk_rule_feedback(
            asset_id,
            version=version,
            status=status_value,
            limit=limit,
        )
    except Exception as exc:
        _handle_asset_error(exc)


@router.get(
    "/{asset_id}/rule-json",
    response_model=AgentAssetRuleJsonRead,
    summary="读取风险规则 JSON",
    description="读取 JSON 风险规则资产绑定的规则文件内容。",
)
def get_agent_asset_rule_json(
    asset_id: str,
    current_user: CurrentUser,
    db: DbSession,
) -> AgentAssetRuleJsonRead:
    try:
        return AgentAssetService(db, current_user=current_user).read_rule_json(asset_id)
    except Exception as exc:
        _handle_asset_error(exc)


@router.put(
    "/{asset_id}/rule-json",
    response_model=AgentAssetRuleJsonRead,
    summary="保存风险规则 JSON",
    description="保存 JSON 风险规则资产绑定的规则文件内容，并写入审计日志。",
)
def save_agent_asset_rule_json(
    asset_id: str,
    payload: AgentAssetRuleJsonWrite,
    current_user: RuleEditorUser,
    db: DbSession,
    x_actor: ActorHeader = None,
    x_request_id: RequestIdHeader = None,
) -> AgentAssetRuleJsonRead:
    try:
        return AgentAssetService(db, current_user=current_user).write_rule_json(
            asset_id,
            body=payload,
            actor=_actor_name(current_user, x_actor),
            request_id=x_request_id,
        )
    except Exception as exc:
        _handle_asset_error(exc)


@router.post(
    "/risk-rules/generate",
    response_model=AgentAssetRead,
    status_code=status.HTTP_201_CREATED,
    summary="根据自然语言新建风险规则草稿",
    description=(
        "根据业务域、自然语言描述和风险评分模型生成 JSON 风险规则，并保存为待上线草稿资产。"
    ),
)
def generate_agent_asset_risk_rule(
    payload: AgentAssetRiskRuleGenerateRequest,
    background_tasks: BackgroundTasks,
    current_user: RuleReviewerUser,
    db: DbSession,
    x_actor: ActorHeader = None,
    x_request_id: RequestIdHeader = None,
) -> AgentAssetRead:
    try:
        actor = _actor_name(current_user, x_actor)
        asset_id = RiskRuleGenerationJobService(db).enqueue_rule_asset_generation(
            payload,
            tenant_id=current_user.tenant_id,
            actor=actor,
            request_id=x_request_id,
        )
        background_tasks.add_task(
            _complete_risk_rule_generation_task,
            asset_id,
            payload.model_dump(mode="json"),
            actor,
            x_request_id,
            current_user.tenant_id,
        )
        return _read_asset(db, asset_id, current_user)
    except Exception as exc:
        _handle_asset_error(exc)


@router.get(
    "/{asset_id}/risk-rule-tests/latest",
    response_model=AgentAssetRiskRuleLatestTestSummary,
    summary="读取风险规则最近测试摘要",
    description="返回当前风险规则工作版本最近一次样例测试、场景试运行和测试报告。",
)
def get_agent_asset_risk_rule_latest_test(
    asset_id: str,
    current_user: CurrentUser,
    db: DbSession,
) -> AgentAssetRiskRuleLatestTestSummary:
    try:
        return AgentAssetService(
            db, current_user=current_user
        ).get_latest_risk_rule_test_summary(asset_id)
    except Exception as exc:
        _handle_asset_error(exc)


@router.post(
    "/{asset_id}/risk-rule-tests/simulate",
    response_model=AgentAssetRiskRuleSimulationRead,
    summary="执行风险规则对话仿真",
    description="基于临时对话输入和附件元信息执行风险识别，不创建业务单据，不写入测试记录。",
)
def simulate_agent_asset_risk_rule_test(
    asset_id: str,
    payload: AgentAssetRiskRuleSimulationRequest,
    current_user: PlatformAdminUser,
    db: DbSession,
) -> AgentAssetRiskRuleSimulationRead:
    try:
        return AgentAssetService(
            db, current_user=current_user
        ).simulate_risk_rule_message(asset_id, payload)
    except Exception as exc:
        _handle_asset_error(exc)


@router.post(
    "/{asset_id}/risk-rule-tests/sample",
    response_model=AgentAssetRiskRuleTestRunRead,
    summary="执行风险规则快速样例测试",
    description="使用人工样例或系统默认样例执行当前 JSON 风险规则，不依赖大模型判断结果。",
)
def run_agent_asset_risk_rule_sample_test(
    asset_id: str,
    payload: AgentAssetRiskRuleSampleTestRequest,
    current_user: PlatformAdminUser,
    db: DbSession,
    x_actor: ActorHeader = None,
    x_request_id: RequestIdHeader = None,
) -> AgentAssetRiskRuleTestRunRead:
    try:
        return AgentAssetService(db, current_user=current_user).run_risk_rule_sample_test(
            asset_id,
            payload,
            actor=_actor_name(current_user, x_actor),
            request_id=x_request_id,
        )
    except Exception as exc:
        _handle_asset_error(exc)


@router.post(
    "/{asset_id}/risk-rule-tests/scenario",
    response_model=AgentAssetRiskRuleTestRunRead,
    summary="执行风险规则真实场景试运行",
    description="按测试意图读取真实业务样本并沙盒执行风险规则，不写回业务单据。",
)
def run_agent_asset_risk_rule_scenario_test(
    asset_id: str,
    payload: AgentAssetRiskRuleScenarioTestRequest,
    current_user: PlatformAdminUser,
    db: DbSession,
    x_actor: ActorHeader = None,
    x_request_id: RequestIdHeader = None,
) -> AgentAssetRiskRuleTestRunRead:
    try:
        return AgentAssetService(
            db, current_user=current_user
        ).run_risk_rule_scenario_test(
            asset_id,
            payload,
            actor=_actor_name(current_user, x_actor),
            request_id=x_request_id,
        )
    except Exception as exc:
        _handle_asset_error(exc)


@router.post(
    "/{asset_id}/risk-rule-tests/report",
    response_model=AgentAssetRiskRuleTestRunRead,
    summary="确认风险规则测试报告",
    description="在样例测试和真实场景试运行通过后，保存当前版本测试通过记录。",
)
def confirm_agent_asset_risk_rule_test_report(
    asset_id: str,
    payload: AgentAssetRiskRuleReportRequest,
    current_user: PlatformAdminUser,
    db: DbSession,
    x_actor: ActorHeader = None,
    x_request_id: RequestIdHeader = None,
) -> AgentAssetRiskRuleTestRunRead:
    try:
        return AgentAssetService(
            db, current_user=current_user
        ).confirm_risk_rule_test_report(
            asset_id,
            payload,
            actor=_actor_name(current_user, x_actor),
            request_id=x_request_id,
        )
    except Exception as exc:
        _handle_asset_error(exc)


@router.post(
    "/{asset_id}/risk-rule-enabled",
    response_model=AgentAssetRead,
    summary="设置风险规则启用状态",
    description=(
        "高级财务人员可独立启用或停用 JSON 风险规则；停用后即使已上线也不会进入真实业务扫描。"
    ),
)
def set_agent_asset_risk_rule_enabled(
    asset_id: str,
    payload: AgentAssetRiskRuleEnabledUpdate,
    current_user: RuleReviewerUser,
    db: DbSession,
    x_actor: ActorHeader = None,
    x_request_id: RequestIdHeader = None,
) -> AgentAssetRead:
    try:
        asset = AgentAssetService(db, current_user=current_user).set_risk_rule_enabled(
            asset_id,
            enabled=payload.enabled,
            actor=_actor_name(current_user, x_actor),
            request_id=x_request_id,
        )
        return _read_asset(db, asset.id, current_user)
    except Exception as exc:
        _handle_asset_error(exc)


@router.post(
    "/{asset_id}/risk-rule-level",
    response_model=AgentAssetRead,
    summary="风险规则风险等级已由评分模型接管",
    description="风险规则等级和分数由自然语言规则评分模型生成，不再允许人工调整。",
)
def set_agent_asset_risk_rule_level(
    asset_id: str,
    payload: AgentAssetRiskRuleLevelUpdate,
    current_user: RuleEditorUser,
    db: DbSession,
    x_actor: ActorHeader = None,
    x_request_id: RequestIdHeader = None,
) -> AgentAssetRead:
    try:
        del asset_id, payload, current_user, db, x_actor, x_request_id
        raise ValueError("风险等级和分数由评分模型自动计算，不能手动修改。")
    except Exception as exc:
        _handle_asset_error(exc)


@router.post(
    "/{asset_id}/return",
    response_model=AgentAssetRiskRuleLatestTestSummary,
    summary="回退待审核风险规则",
    description="高级财务人员将待审核风险规则回退到草稿，并记录回退原因。",
)
def return_agent_asset_risk_rule(
    asset_id: str,
    payload: AgentAssetRiskRuleReturnRequest,
    current_user: RuleReviewerUser,
    db: DbSession,
    x_actor: ActorHeader = None,
    x_request_id: RequestIdHeader = None,
) -> AgentAssetRiskRuleLatestTestSummary:
    try:
        return AgentAssetService(db, current_user=current_user).return_risk_rule(
            asset_id,
            note=payload.note,
            actor=_actor_name(current_user, x_actor),
            request_id=x_request_id,
        )
    except Exception as exc:
        _handle_asset_error(exc)


@router.post(
    "/{asset_id}/publish",
    response_model=AgentAssetRead,
    summary="审核并启动风险规则影子发布",
    description=(
        "高级财务人员确认测试与 Golden 门禁通过后，将候选规则送入 shadow；"
        "该入口不会直接 active，后续必须通过 Canary 质量门禁。"
    ),
)
def publish_agent_asset_risk_rule(
    asset_id: str,
    current_user: RuleReviewerUser,
    db: DbSession,
    x_actor: ActorHeader = None,
    x_request_id: RequestIdHeader = None,
) -> AgentAssetRead:
    try:
        asset = AgentAssetService(db, current_user=current_user).publish_risk_rule(
            asset_id,
            actor=_actor_name(current_user, x_actor),
            tenant_id=current_user.tenant_id,
            allow_global_management=current_user.is_admin,
            request_id=x_request_id,
        )
        return _read_asset(db, asset.id, current_user)
    except Exception as exc:
        _handle_asset_error(exc)
