from __future__ import annotations

from typing import Annotated

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import func, or_, select
from sqlalchemy.orm import Session, selectinload

from app.api.deps import (
    CurrentUserContext,
    get_current_user,
    get_db,
    is_budget_scope_limited_user,
    require_budget_editor_user,
    require_budget_viewer_user,
)
from app.api.pagination import PageNumber, PageSize, page_payload, wants_page
from app.models.budget import BudgetAllocation
from app.models.employee import Employee
from app.schemas.budget import (
    BudgetAllocationCreate,
    BudgetAllocationRead,
    BudgetCheckRead,
    BudgetCheckRequest,
    BudgetOperationRead,
    BudgetOperationRequest,
    BudgetSummaryRead,
    BudgetTransactionRead,
)
from app.schemas.common import ErrorResponse, PaginatedResponse
from app.services.budget import BudgetControlError, BudgetService

router = APIRouter(prefix="/budgets")
DbSession = Annotated[Session, Depends(get_db)]
CurrentUser = Annotated[CurrentUserContext, Depends(get_current_user)]
BudgetViewer = Annotated[CurrentUserContext, Depends(require_budget_viewer_user)]
BudgetEditor = Annotated[CurrentUserContext, Depends(require_budget_editor_user)]


@router.get(
    "/summary",
    response_model=BudgetSummaryRead,
    summary="读取预算中心汇总",
)
def get_budget_summary(
    db: DbSession,
    current_user: BudgetViewer,
    fiscal_year: Annotated[int | None, Query(alias="year")] = None,
    period_key: Annotated[str | None, Query(alias="period")] = None,
    department_id: str | None = None,
    department_name: str | None = None,
    cost_center: str | None = None,
) -> BudgetSummaryRead:
    scope = _resolve_budget_query_scope(
        db,
        current_user,
        department_id=department_id,
        department_name=department_name,
        cost_center=cost_center,
    )
    return BudgetService(db).get_summary(
        fiscal_year=fiscal_year,
        period_key=period_key,
        **scope,
    )


@router.get(
    "/allocations",
    response_model=list[BudgetAllocationRead] | PaginatedResponse[BudgetAllocationRead],
    summary="查询预算额度列表",
)
def list_budget_allocations(
    db: DbSession,
    current_user: BudgetViewer,
    fiscal_year: Annotated[int | None, Query(alias="year")] = None,
    period_key: Annotated[str | None, Query(alias="period")] = None,
    department_id: str | None = None,
    department_name: str | None = None,
    cost_center: str | None = None,
    page: PageNumber = None,
    page_size: PageSize = None,
) -> list[BudgetAllocationRead] | PaginatedResponse[BudgetAllocationRead]:
    scope = _resolve_budget_query_scope(
        db,
        current_user,
        department_id=department_id,
        department_name=department_name,
        cost_center=cost_center,
    )
    service = BudgetService(db)
    if wants_page(page, page_size):
        return page_payload(
            service.list_allocations_page(
                fiscal_year=fiscal_year,
                period_key=period_key,
                **scope,
                page=page,
                page_size=page_size,
            )
        )
    return service.list_allocations(
        fiscal_year=fiscal_year,
        period_key=period_key,
        **scope,
    )


@router.post(
    "/allocations",
    response_model=BudgetAllocationRead,
    status_code=status.HTTP_201_CREATED,
    summary="创建或更新预算额度",
    responses={status.HTTP_400_BAD_REQUEST: {"model": ErrorResponse}},
)
def create_budget_allocation(
    payload: BudgetAllocationCreate,
    db: DbSession,
    current_user: BudgetEditor,
) -> BudgetAllocationRead:
    try:
        allocation = BudgetService(db).create_or_update_allocation(
            payload,
            operator=current_user.name or current_user.username,
        )
        db.commit()
        return allocation
    except ValueError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error


@router.get(
    "/allocations/{allocation_id}/transactions",
    response_model=list[BudgetTransactionRead] | PaginatedResponse[BudgetTransactionRead],
    summary="读取预算交易台账",
)
def list_budget_transactions(
    allocation_id: str,
    db: DbSession,
    current_user: BudgetViewer,
    page: PageNumber = None,
    page_size: PageSize = None,
) -> list[BudgetTransactionRead] | PaginatedResponse[BudgetTransactionRead]:
    service = BudgetService(db)
    allocation = service.get_allocation_row(allocation_id)
    if allocation is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="预算额度不存在。")
    if not _allocation_visible_to_user(db, current_user, allocation):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="不能查看其他部门预算流水。",
        )
    if wants_page(page, page_size):
        return page_payload(
            service.list_transactions_page(allocation_id, page=page, page_size=page_size)
        )
    return service.list_transactions(allocation_id)


@router.post(
    "/check",
    response_model=BudgetCheckRead,
    summary="校验预算可用余额",
)
def check_budget(payload: BudgetCheckRequest, db: DbSession, current_user: BudgetViewer) -> BudgetCheckRead:
    scope = _resolve_budget_query_scope(
        db,
        current_user,
        department_id=payload.department_id,
        department_name=payload.department_name,
        cost_center=payload.cost_center,
    )
    scoped_payload = payload.model_copy(update=scope)
    return BudgetService(db).check(scoped_payload)


@router.post(
    "/reserve",
    response_model=BudgetOperationRead,
    summary="记录预算预占台账",
    responses={status.HTTP_400_BAD_REQUEST: {"model": ErrorResponse}},
)
def reserve_budget(
    payload: BudgetOperationRequest,
    db: DbSession,
    current_user: BudgetEditor,
) -> BudgetOperationRead:
    return _execute_budget_operation(
        payload,
        db=db,
        current_user=current_user,
        transaction_type="reserve",
    )


@router.post(
    "/release",
    response_model=BudgetOperationRead,
    summary="记录预算释放台账",
    responses={status.HTTP_400_BAD_REQUEST: {"model": ErrorResponse}},
)
def release_budget(
    payload: BudgetOperationRequest,
    db: DbSession,
    current_user: BudgetEditor,
) -> BudgetOperationRead:
    return _execute_budget_operation(
        payload,
        db=db,
        current_user=current_user,
        transaction_type="release",
    )


@router.post(
    "/consume",
    response_model=BudgetOperationRead,
    summary="记录预算核销台账",
    responses={status.HTTP_400_BAD_REQUEST: {"model": ErrorResponse}},
)
def consume_budget(
    payload: BudgetOperationRequest,
    db: DbSession,
    current_user: BudgetEditor,
) -> BudgetOperationRead:
    return _execute_budget_operation(
        payload,
        db=db,
        current_user=current_user,
        transaction_type="consume",
    )


@router.post(
    "/rollback",
    response_model=BudgetOperationRead,
    summary="记录预算核销回滚台账",
    responses={status.HTTP_400_BAD_REQUEST: {"model": ErrorResponse}},
)
def rollback_budget(
    payload: BudgetOperationRequest,
    db: DbSession,
    current_user: BudgetEditor,
) -> BudgetOperationRead:
    return _execute_budget_operation(
        payload,
        db=db,
        current_user=current_user,
        transaction_type="rollback",
    )


def _execute_budget_operation(
    payload: BudgetOperationRequest,
    *,
    db: Session,
    current_user: CurrentUserContext,
    transaction_type: str,
) -> BudgetOperationRead:
    try:
        result = BudgetService(db).execute_operation(
            payload,
            transaction_type=transaction_type,
            operator=current_user.name or current_user.username,
        )
        db.commit()
        return result
    except BudgetControlError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error
    except ValueError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error


def _resolve_budget_query_scope(
    db: Session,
    current_user: CurrentUserContext,
    *,
    department_id: str | None = None,
    department_name: str | None = None,
    cost_center: str | None = None,
) -> dict[str, str | None]:
    if not is_budget_scope_limited_user(current_user):
        return {
            "department_id": _blank_to_none(department_id),
            "department_name": _blank_to_none(department_name),
            "cost_center": _blank_to_none(cost_center),
        }

    employee = _resolve_current_employee(db, current_user)
    scoped_cost_center = (
        _blank_to_none(current_user.cost_center)
        or _blank_to_none(getattr(employee, "cost_center", None))
        or _blank_to_none(getattr(getattr(employee, "organization_unit", None), "cost_center", None))
    )
    scoped_department_name = (
        _blank_to_none(current_user.department_name)
        or _blank_to_none(getattr(getattr(employee, "organization_unit", None), "name", None))
    )

    if not scoped_cost_center and not scoped_department_name:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="预算监控员缺少部门归属，不能查看预算中心。",
        )

    return {
        "department_id": None,
        "department_name": None if scoped_cost_center else scoped_department_name,
        "cost_center": scoped_cost_center,
    }


def _allocation_visible_to_user(
    db: Session,
    current_user: CurrentUserContext,
    allocation: BudgetAllocation,
) -> bool:
    if not is_budget_scope_limited_user(current_user):
        return True

    scope = _resolve_budget_query_scope(db, current_user)
    scoped_cost_center = scope.get("cost_center")
    scoped_department_name = scope.get("department_name")
    if scoped_cost_center:
        return str(allocation.cost_center or "").strip() == scoped_cost_center
    if scoped_department_name:
        return str(allocation.department_name or "").strip() == scoped_department_name
    return False


def _resolve_current_employee(db: Session, current_user: CurrentUserContext) -> Employee | None:
    identities = [
        str(current_user.username or "").strip(),
        str(current_user.name or "").strip(),
    ]
    identities = [item for item in dict.fromkeys(identities) if item]
    if not identities:
        return None

    lowered = [item.lower() for item in identities]
    stmt = (
        select(Employee)
        .options(selectinload(Employee.organization_unit))
        .where(
            or_(
                func.lower(Employee.email).in_(lowered),
                Employee.employee_no.in_(identities),
                Employee.name.in_(identities),
            )
        )
        .limit(1)
    )
    return db.scalars(stmt).first()


def _blank_to_none(value: str | None) -> str | None:
    text = str(value or "").strip()
    return text or None
