"""Posting Engine API — Phase 5.2."""
from __future__ import annotations

from uuid import UUID

from fastapi import APIRouter, Depends, status
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_db, get_pagination, require_tenant
from app.core.security import get_current_user
from app.models.posting import Voucher, VoucherLine
from app.models.types import VoucherStatus
from app.repositories.posting import AuditLogRepository, VoucherRepository
from app.schemas.posting import (
    AuditLogRead,
    PostRequest,
    ReverseRequest,
    VoucherCreate,
    VoucherRead,
    VoucherUpdate,
)
from app.services.posting_engine import PostingEngine
from shared.exceptions import AppError, NotFoundError
from shared.pagination import PaginationParams
from shared.security import CurrentUser

router = APIRouter()


def _assert_lines_balanced(lines: list) -> None:
    from decimal import Decimal

    if len(lines) < 2:
        raise AppError(
            "حداقل دو ردیف برای سند لازم است",
            status_code=422,
            error_code="voucher_lines_min",
        )
    total_debit = Decimal("0")
    total_credit = Decimal("0")
    for i, line in enumerate(lines, start=1):
        data = line.model_dump() if hasattr(line, "model_dump") else dict(line)
        debit = Decimal(str(data.get("debit") or 0))
        credit = Decimal(str(data.get("credit") or 0))
        if debit > 0 and credit > 0:
            raise AppError(
                f"ردیف {i}: نمی‌توان همزمان بدهکار و بستانکار داشت",
                status_code=422,
                error_code="voucher_line_both_sides",
            )
        if debit == 0 and credit == 0:
            raise AppError(
                f"ردیف {i}: یکی از بدهکار یا بستانکار باید مقدار داشته باشد",
                status_code=422,
                error_code="voucher_line_empty",
            )
        total_debit += debit
        total_credit += credit
    if total_debit != total_credit:
        diff = abs(total_debit - total_credit)
        raise AppError(
            f"سند نامتوازن است — جمع بدهکار {total_debit} و بستانکار {total_credit} "
            f"(اختلاف {diff}). ثبت مجاز نیست.",
            status_code=422,
            error_code="voucher_unbalanced",
        )


@router.get("/vouchers", response_model=list[VoucherRead])
async def list_vouchers(
    tenant_id: UUID = Depends(require_tenant),
    pagination: PaginationParams = Depends(get_pagination),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
    status: str | None = None,
    source: str | None = None,
):
    """List all vouchers including automatic/system-posted (sales/purchase/treasury/...)."""
    from sqlalchemy import or_, select
    from sqlalchemy.orm import selectinload

    stmt = (
        select(Voucher)
        .options(selectinload(Voucher.lines))
        .where(Voucher.tenant_id == tenant_id)
    )
    if status:
        try:
            stmt = stmt.where(Voucher.status == VoucherStatus(status))
        except ValueError:
            pass
    if source == "manual":
        stmt = stmt.where(
            or_(Voucher.source_module.is_(None), Voucher.source_module.in_(["", "manual"]))
        )
    elif source == "auto":
        stmt = stmt.where(
            Voucher.source_module.is_not(None),
            Voucher.source_module.notin_(["", "manual"]),
        )
    elif source:
        stmt = stmt.where(Voucher.source_module == source)

    stmt = (
        stmt.order_by(Voucher.created_at.desc())
        .offset(pagination.offset)
        .limit(pagination.page_size)
    )
    result = await db.execute(stmt)
    return list(result.scalars().all())


@router.post("/vouchers", response_model=VoucherRead, status_code=status.HTTP_201_CREATED)
async def create_voucher(
    body: VoucherCreate,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    user: CurrentUser = Depends(get_current_user),
):
    from app.services.document_number_service import DocumentNumberService

    _assert_lines_balanced(body.lines)
    repo = VoucherRepository(db)
    number = await DocumentNumberService(db).allocate_if_blank(
        tenant_id, "voucher", body.voucher_number
    )
    voucher = Voucher(
        tenant_id=tenant_id,
        fiscal_period_id=body.fiscal_period_id,
        voucher_number=number,
        voucher_date=body.voucher_date,
        status=VoucherStatus.DRAFT,
        description=body.description,
        reference_number=body.reference_number,
        source_module=body.source_module,
        created_by=user.user_id,
    )
    await repo.add(voucher)
    for i, line in enumerate(body.lines, start=1):
        db.add(VoucherLine(
            tenant_id=tenant_id,
            voucher_id=voucher.id,
            line_number=i,
            **line.model_dump(),
        ))
    await db.commit()
    result = await repo.get_with_lines(tenant_id, voucher.id)
    return result


@router.get("/vouchers/{voucher_id}", response_model=VoucherRead)
async def get_voucher(
    voucher_id: UUID,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    repo = VoucherRepository(db)
    voucher = await repo.get_with_lines(tenant_id, voucher_id)
    if voucher is None:
        raise NotFoundError("سند یافت نشد", error_code="voucher_not_found")
    return voucher


@router.patch("/vouchers/{voucher_id}", response_model=VoucherRead)
async def update_draft_voucher(
    voucher_id: UUID,
    body: VoucherUpdate,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    repo = VoucherRepository(db)
    voucher = await repo.get_with_lines(tenant_id, voucher_id)
    if voucher is None:
        raise NotFoundError("سند یافت نشد", error_code="voucher_not_found")
    if voucher.status != VoucherStatus.DRAFT:
        raise AppError(
            "فقط اسناد پیش‌نویس قابل ویرایش هستند",
            status_code=422,
            error_code="voucher_not_draft",
        )
    data = body.model_dump(exclude_unset=True)
    lines_data = data.pop("lines", None)
    if lines_data is not None:
        _assert_lines_balanced(lines_data)
    for key, value in data.items():
        setattr(voucher, key, value)
    if lines_data is not None:
        for line in list(voucher.lines):
            await db.delete(line)
        await db.flush()
        for i, line in enumerate(lines_data, start=1):
            db.add(
                VoucherLine(
                    tenant_id=tenant_id,
                    voucher_id=voucher.id,
                    line_number=i,
                    **line,
                )
            )
    await db.commit()
    return await repo.get_with_lines(tenant_id, voucher_id)


@router.post("/vouchers/{voucher_id}/validate", response_model=VoucherRead)
async def validate_voucher(
    voucher_id: UUID,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    engine = PostingEngine(db)
    voucher = await engine.validate_voucher(tenant_id, voucher_id)
    await db.commit()
    return await VoucherRepository(db).get_with_lines(tenant_id, voucher.id)


@router.post("/vouchers/{voucher_id}/post", response_model=VoucherRead)
async def post_voucher(
    voucher_id: UUID,
    body: PostRequest,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    user: CurrentUser = Depends(get_current_user),
):
    engine = PostingEngine(db)
    voucher = await engine.post_voucher(
        tenant_id, voucher_id, actor_user_id=user.user_id, source_module=body.source_module
    )
    await db.commit()
    return await VoucherRepository(db).get_with_lines(tenant_id, voucher.id)


@router.post("/vouchers/{voucher_id}/reverse", response_model=VoucherRead)
async def reverse_voucher(
    voucher_id: UUID,
    body: ReverseRequest,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    user: CurrentUser = Depends(get_current_user),
):
    engine = PostingEngine(db)
    voucher = await engine.reverse_voucher(
        tenant_id, voucher_id, actor_user_id=user.user_id, reversal_date=body.reversal_date
    )
    await db.commit()
    return await VoucherRepository(db).get_with_lines(tenant_id, voucher.id)


@router.post("/vouchers/{voucher_id}/reverse-and-edit", response_model=VoucherRead)
async def reverse_and_edit_voucher(
    voucher_id: UUID,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    user: CurrentUser = Depends(get_current_user),
):
    """برگشت سند قطعی + ایجاد پیش‌نویس قابل ویرایش از همان محتوا."""
    engine = PostingEngine(db)
    draft = await engine.reverse_and_reopen_for_edit(
        tenant_id, voucher_id, actor_user_id=user.user_id
    )
    await db.commit()
    return await VoucherRepository(db).get_with_lines(tenant_id, draft.id)


@router.post("/vouchers/{voucher_id}/cancel", response_model=VoucherRead)
async def cancel_voucher(
    voucher_id: UUID,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    user: CurrentUser = Depends(get_current_user),
):
    engine = PostingEngine(db)
    voucher = await engine.cancel_voucher(tenant_id, voucher_id, actor_user_id=user.user_id)
    await db.commit()
    return voucher


@router.get("/audit-logs", response_model=list[AuditLogRead])
async def list_audit_logs(
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    repo = AuditLogRepository(db)
    return await repo.list_by_tenant(tenant_id, limit=100)
