from __future__ import annotations

import uuid
from typing import Annotated

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

from app.api.deps import CurrentUserContext, get_current_user, get_db
from app.schemas.common import ErrorResponse
from app.schemas.risk_disposition import (
    RiskDispositionActionCreate,
    RiskDispositionMutationRead,
    RiskDispositionRead,
)
from app.schemas.risk_observation import (
    RiskObservationDashboardRead,
    RiskObservationFeedbackCreate,
    RiskObservationFeedbackRead,
    RiskObservationListRead,
    RiskObservationRead,
)
from app.services.risk_disposition_read_projection import RiskDispositionReadProjection
from app.services.risk_dispositions import (
    RiskDispositionConflictError,
    RiskDispositionPermissionError,
    RiskDispositionService,
    RiskDispositionVersionConflictError,
    RiskWaiverDecisionPermissionError,
)
from app.services.risk_observation_access_policy import RiskObservationAccessPolicy
from app.services.risk_observations import RiskObservationService

router = APIRouter(prefix="/risk-observations")
DbSession = Annotated[Session, Depends(get_db)]
CurrentUser = Annotated[CurrentUserContext, Depends(get_current_user)]


@router.get(
    "",
    response_model=RiskObservationListRead,
    summary="查询风险观察列表",
    description="按单据、风险等级、风险信号、状态和来源筛选统一风险观察池。",
)
def list_risk_observations(
    db: DbSession,
    current_user: CurrentUser,
    claim_id: Annotated[str | None, Query(max_length=80)] = None,
    run_id: Annotated[str | None, Query(max_length=80)] = None,
    execution_log_id: Annotated[str | None, Query(max_length=80)] = None,
    risk_level: Annotated[str | None, Query(max_length=20)] = None,
    risk_signal: Annotated[str | None, Query(max_length=100)] = None,
    status_value: Annotated[
        str | None,
        Query(alias="status", max_length=30),
    ] = None,
    source: Annotated[str | None, Query(max_length=60)] = None,
    limit: Annotated[int, Query(ge=1, le=200)] = 50,
    offset: Annotated[int, Query(ge=0)] = 0,
) -> RiskObservationListRead:
    _require_pool_access(db, current_user)
    items, total = RiskObservationService(db).list_observations(
        tenant_id=current_user.tenant_id,
        claim_id=claim_id,
        run_id=run_id,
        execution_log_id=execution_log_id,
        risk_level=risk_level,
        risk_signal=risk_signal,
        status=status_value,
        source=source,
        limit=limit,
        offset=offset,
    )
    projected_items = RiskDispositionReadProjection(db).observation_list_read(
        items,
        current_user,
    )
    return RiskObservationListRead(
        items=projected_items,
        total=total,
        limit=limit,
        offset=offset,
    )


@router.get(
    "/dashboard",
    response_model=RiskObservationDashboardRead,
    summary="查询风险看板聚合",
    description="返回风险观察池的总量、分布、算法效果和近期高风险记录。",
)
def summarize_risk_observations(
    db: DbSession,
    current_user: CurrentUser,
    window_days: Annotated[int, Query(ge=1, le=365)] = 30,
    limit: Annotated[int, Query(ge=1, le=2000)] = 500,
) -> RiskObservationDashboardRead:
    _require_pool_access(db, current_user)
    return RiskObservationService(db).summarize_dashboard(
        tenant_id=current_user.tenant_id,
        window_days=window_days,
        limit=limit,
    )


@router.get(
    "/claim/{claim_id}",
    response_model=list[RiskObservationRead],
    summary="查询单据风险观察",
    description="按报销单 ID 返回该单据关联的风险观察，供单据详情证据链使用。",
)
def list_claim_risk_observations(
    claim_id: str,
    db: DbSession,
    current_user: CurrentUser,
) -> list[RiskObservationRead]:
    if not RiskObservationAccessPolicy(db).can_read_claim_risks(claim_id, current_user):
        raise _not_found()
    observations = RiskObservationService(db).list_claim_observations(
        claim_id,
        tenant_id=current_user.tenant_id,
    )
    return RiskDispositionReadProjection(db).observation_list_read(
        observations,
        current_user,
    )


@router.get(
    "/execution-log/{execution_log_id}",
    response_model=list[RiskObservationRead],
    summary="查询数字员工工作记录风险观察",
    description="按数字员工执行日志 ID 返回本次任务生成的风险观察。",
)
def list_execution_log_risk_observations(
    execution_log_id: str,
    db: DbSession,
    current_user: CurrentUser,
) -> list[RiskObservationRead]:
    _require_pool_access(db, current_user)
    observations = RiskObservationService(db).list_execution_log_observations(
        execution_log_id,
        tenant_id=current_user.tenant_id,
    )
    return RiskDispositionReadProjection(db).observation_list_read(
        observations,
        current_user,
    )


@router.get(
    "/{observation_key_or_id}",
    response_model=RiskObservationRead,
    summary="读取风险观察详情",
    description="按观察 key 或 ID 返回风险评分、证据链、图谱节点、制度引用和决策追踪。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "风险观察不存在。",
        }
    },
)
def get_risk_observation(
    observation_key_or_id: str,
    db: DbSession,
    current_user: CurrentUser,
) -> RiskObservationRead:
    _require_pool_access(db, current_user)
    observation = RiskObservationService(db).get_observation(
        observation_key_or_id,
        tenant_id=current_user.tenant_id,
    )
    if observation is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Risk observation not found",
        )
    return RiskDispositionReadProjection(db).observation_read(
        observation,
        current_user,
    )


@router.get(
    "/{observation_key_or_id}/disposition",
    response_model=RiskDispositionRead,
    summary="读取风险观察处置状态",
    description="返回裁决结论、处置生命周期、负责人、截止时间和只追加事件。",
)
def get_risk_observation_disposition(
    observation_key_or_id: str,
    db: DbSession,
    current_user: CurrentUser,
) -> RiskDispositionRead:
    _require_pool_access(db, current_user)
    observation = _get_observation_or_404(db, current_user, observation_key_or_id)
    disposition = RiskDispositionService(db).get_disposition(
        observation.id,
        tenant_id=current_user.tenant_id,
    )
    if disposition is None:
        raise _not_found("Risk disposition not found")
    return RiskDispositionReadProjection(db).disposition_read(
        disposition,
        observation,
        current_user,
    )


@router.post(
    "/{observation_key_or_id}/disposition/actions",
    response_model=RiskDispositionMutationRead,
    summary="执行类型化风险处置动作",
    description="使用乐观锁和请求幂等键追加风险处置事件。",
)
def execute_risk_disposition_action(
    observation_key_or_id: str,
    payload: RiskDispositionActionCreate,
    db: DbSession,
    current_user: CurrentUser,
) -> RiskDispositionMutationRead:
    mutation = _execute_action(
        db,
        current_user,
        observation_key_or_id,
        payload,
    )
    observation = _get_observation_or_404(
        db,
        current_user,
        observation_key_or_id,
    )
    projected_disposition = RiskDispositionReadProjection(db).disposition_snapshot_read(
        mutation.response.disposition,
        observation,
        current_user,
    )
    return mutation.response.model_copy(
        update={"disposition": projected_disposition},
    )


@router.post(
    "/{observation_key_or_id}/feedback",
    response_model=RiskObservationFeedbackRead,
    summary="写入风险观察反馈",
    description="记录人工确认、误报、忽略、已处理或备注反馈，并同步更新观察状态。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "风险观察不存在。",
        }
    },
)
def create_risk_observation_feedback(
    observation_key_or_id: str,
    payload: RiskObservationFeedbackCreate,
    db: DbSession,
    current_user: CurrentUser,
) -> RiskObservationFeedbackRead:
    observation = _get_observation_or_404(db, current_user, observation_key_or_id)
    if (
        payload.feedback_type not in {"confirm", "false_positive"}
        or payload.action is not None
        or bool(payload.payload_json)
    ):
        raise HTTPException(
            status_code=status.HTTP_410_GONE,
            detail=("自由格式反馈入口已停用；请使用 disposition/actions 类型化处置接口。"),
        )

    service = RiskDispositionService(db)
    mutation = _execute_action(
        db,
        current_user,
        observation_key_or_id,
        RiskDispositionActionCreate(
            action=payload.feedback_type,
            expected_version=service.get_current_version(
                observation.id,
                tenant_id=current_user.tenant_id,
            ),
            request_id=f"legacy:{uuid.uuid4()}",
            comment=payload.comment,
        ),
    )
    if mutation.legacy_feedback is None:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Legacy feedback projection is unavailable.",
        )
    return mutation.legacy_feedback


def _require_pool_access(db: Session, current_user: CurrentUserContext) -> None:
    if RiskObservationAccessPolicy(db).can_read_tenant_pool(current_user):
        return
    raise HTTPException(
        status_code=status.HTTP_403_FORBIDDEN,
        detail="当前用户无权访问企业风险观察池。",
    )


def _get_observation_or_404(
    db: Session,
    current_user: CurrentUserContext,
    observation_key_or_id: str,
):
    observation = RiskObservationService(db).get_observation(
        observation_key_or_id,
        tenant_id=current_user.tenant_id,
    )
    if observation is None:
        raise _not_found()
    return observation


def _execute_action(
    db: Session,
    current_user: CurrentUserContext,
    observation_key_or_id: str,
    payload: RiskDispositionActionCreate,
):
    try:
        return RiskDispositionService(db).execute_action(
            observation_key_or_id,
            payload,
            tenant_id=current_user.tenant_id,
            actor_id=current_user.employee_id or current_user.username,
            actor_name=current_user.name or current_user.username,
            current_user=current_user,
        )
    except LookupError:
        raise _not_found() from None
    except RiskDispositionVersionConflictError as error:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail={
                "code": "RISK_DISPOSITION_VERSION_CONFLICT",
                "current_version": error.current_version,
                "message": "风险处置状态已更新，请刷新证据链后重试。",
            },
        ) from None
    except RiskWaiverDecisionPermissionError as error:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail={
                "code": "RISK_WAIVER_DECISION_FORBIDDEN",
                "message": str(error),
            },
        ) from None
    except RiskDispositionPermissionError as error:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail=str(error),
        ) from None
    except RiskDispositionConflictError as error:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=str(error),
        ) from None


def _not_found(detail: str = "Risk observation not found") -> HTTPException:
    return HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail=detail,
    )
