"""Treasury API — Phase 5.4."""
from __future__ import annotations

from datetime import date
from decimal import Decimal
from uuid import UUID

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

from app.api.deps import get_db, require_tenant
from app.core.security import get_current_user
from app.models.treasury import Bank, BankAccount, CashBox
from app.repositories.base import TenantBaseRepository
from app.services.treasury_service import TreasuryService
from shared.security import CurrentUser

router = APIRouter()


class CashBoxCreate(BaseModel):
    code: str
    name: str
    account_id: UUID | None = None
    opening_balance: Decimal = Decimal("0")


class CashBoxRead(BaseModel):
    id: UUID
    tenant_id: UUID
    code: str
    name: str
    current_balance: Decimal
    is_active: bool

    model_config = {"from_attributes": True}


class CashReceiptRequest(BaseModel):
    cash_box_id: UUID
    amount: Decimal
    debit_account_id: UUID
    credit_account_id: UUID
    description: str | None = None
    transaction_date: date | None = None


class BankCreate(BaseModel):
    code: str
    name: str


class BankAccountCreate(BaseModel):
    bank_id: UUID
    account_number: str
    iban: str | None = None


class CashBoxRepo(TenantBaseRepository[CashBox]):
    model = CashBox


class BankRepo(TenantBaseRepository[Bank]):
    model = Bank


class BankAccountRepo(TenantBaseRepository[BankAccount]):
    model = BankAccount


@router.post("/cash-boxes", response_model=CashBoxRead, status_code=status.HTTP_201_CREATED)
async def create_cash_box(
    body: CashBoxCreate,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    repo = CashBoxRepo(db)
    entity = CashBox(
        tenant_id=tenant_id,
        code=body.code,
        name=body.name,
        account_id=body.account_id,
        opening_balance=body.opening_balance,
        current_balance=body.opening_balance,
    )
    await repo.add(entity)
    await db.commit()
    await db.refresh(entity)
    return entity


@router.get("/cash-boxes", response_model=list[CashBoxRead])
async def list_cash_boxes(
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    repo = CashBoxRepo(db)
    return await repo.list_by_tenant(tenant_id, limit=100)


@router.post("/cash-receipts")
async def record_cash_receipt(
    body: CashReceiptRequest,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    user: CurrentUser = Depends(get_current_user),
):
    svc = TreasuryService(db)
    tx = await svc.record_cash_receipt(
        tenant_id, body.cash_box_id, body.amount,
        debit_account_id=body.debit_account_id,
        credit_account_id=body.credit_account_id,
        actor_user_id=user.user_id,
        description=body.description,
        transaction_date=body.transaction_date,
    )
    await db.commit()
    return {"id": str(tx.id), "voucher_id": str(tx.voucher_id)}


@router.post("/banks", status_code=status.HTTP_201_CREATED)
async def create_bank(
    body: BankCreate,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    repo = BankRepo(db)
    entity = Bank(tenant_id=tenant_id, code=body.code, name=body.name)
    await repo.add(entity)
    await db.commit()
    return {"id": str(entity.id)}


@router.post("/bank-accounts", status_code=status.HTTP_201_CREATED)
async def create_bank_account(
    body: BankAccountCreate,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    repo = BankAccountRepo(db)
    entity = BankAccount(
        tenant_id=tenant_id,
        bank_id=body.bank_id,
        account_number=body.account_number,
        iban=body.iban,
    )
    await repo.add(entity)
    await db.commit()
    return {"id": str(entity.id)}


@router.get("/banks")
async def list_banks(
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    banks = await BankRepo(db).list_by_tenant(tenant_id, limit=100)
    return [{"id": str(b.id), "code": b.code, "name": b.name, "is_active": b.is_active} for b in banks]


@router.get("/bank-accounts")
async def list_bank_accounts(
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    accounts = await BankAccountRepo(db).list_by_tenant(tenant_id, limit=100)
    return [
        {
            "id": str(a.id),
            "bank_id": str(a.bank_id),
            "account_number": a.account_number,
            "iban": a.iban,
            "is_active": a.is_active,
        }
        for a in accounts
    ]


@router.patch("/cash-boxes/{cash_box_id}")
async def update_cash_box(
    cash_box_id: UUID,
    body: dict,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    from shared.exceptions import NotFoundError

    repo = CashBoxRepo(db)
    entity = await repo.get(tenant_id, cash_box_id)
    if entity is None:
        raise NotFoundError("صندوق یافت نشد", error_code="cash_box_not_found")
    if "name" in body and body["name"] is not None:
        entity.name = body["name"]
    if "is_active" in body and body["is_active"] is not None:
        entity.is_active = bool(body["is_active"])
    await db.commit()
    await db.refresh(entity)
    return CashBoxRead.model_validate(entity)


# ── Cheques / transfers / receipts / payments / reconciliation ───────────────

from app.models.treasury import Cheque, PaymentVoucher, ReceiptVoucher, Reconciliation, Transfer
from app.models.types import ChequeStatus, ReconciliationStatus


class ChequeRepo(TenantBaseRepository[Cheque]):
    model = Cheque


class TransferRepo(TenantBaseRepository[Transfer]):
    model = Transfer


class ReceiptRepo(TenantBaseRepository[ReceiptVoucher]):
    model = ReceiptVoucher


class PaymentRepo(TenantBaseRepository[PaymentVoucher]):
    model = PaymentVoucher


class ReconciliationRepo(TenantBaseRepository[Reconciliation]):
    model = Reconciliation


class ChequeCreate(BaseModel):
    cheque_number: str
    amount: Decimal
    issue_date: date
    due_date: date | None = None
    is_incoming: bool = True
    payee: str | None = None
    bank_account_id: UUID | None = None
    status: ChequeStatus = ChequeStatus.RECEIVED


class TransferCreate(BaseModel):
    transfer_date: date
    amount: Decimal
    from_type: str
    from_id: UUID
    to_type: str
    to_id: UUID
    description: str | None = None


class ReceiptCreate(BaseModel):
    receipt_number: str | None = None
    receipt_date: date
    amount: Decimal
    payment_method: str = "cash"
    cash_box_id: UUID | None = None
    bank_account_id: UUID | None = None
    counter_account_id: UUID | None = None
    description: str | None = None


class PaymentCreate(BaseModel):
    payment_number: str | None = None
    payment_date: date
    amount: Decimal
    payment_method: str = "cash"
    cash_box_id: UUID | None = None
    bank_account_id: UUID | None = None
    counter_account_id: UUID | None = None
    description: str | None = None


class ReconciliationCreate(BaseModel):
    bank_account_id: UUID
    statement_date: date
    statement_balance: Decimal
    book_balance: Decimal


@router.get("/cheques")
async def list_cheques(
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    rows = await ChequeRepo(db).list_by_tenant(tenant_id, limit=200)
    return [
        {
            "id": str(c.id),
            "cheque_number": c.cheque_number,
            "amount": str(c.amount),
            "issue_date": c.issue_date.isoformat(),
            "due_date": c.due_date.isoformat() if c.due_date else None,
            "status": c.status.value,
            "is_incoming": c.is_incoming,
            "payee": c.payee,
        }
        for c in rows
    ]


@router.post("/cheques", status_code=status.HTTP_201_CREATED)
async def create_cheque(
    body: ChequeCreate,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    entity = Cheque(tenant_id=tenant_id, **body.model_dump())
    await ChequeRepo(db).add(entity)
    await db.commit()
    return {"id": str(entity.id), "cheque_number": entity.cheque_number}


@router.get("/transfers")
async def list_transfers(
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    rows = await TransferRepo(db).list_by_tenant(tenant_id, limit=200)
    return [
        {
            "id": str(t.id),
            "transfer_date": t.transfer_date.isoformat(),
            "amount": str(t.amount),
            "from_type": t.from_type,
            "from_id": str(t.from_id),
            "to_type": t.to_type,
            "to_id": str(t.to_id),
            "description": t.description,
            "voucher_id": str(t.voucher_id) if t.voucher_id else None,
        }
        for t in rows
    ]


async def _resolve_transfer_gl_account(
    db: AsyncSession,
    tenant_id: UUID,
    side_type: str,
    side_id: UUID,
) -> UUID:
    from shared.exceptions import AppError

    if side_type == "account":
        return side_id
    if side_type == "cash":
        box = await CashBoxRepo(db).get(tenant_id, side_id)
        if box is None or box.account_id is None:
            raise AppError(
                "صندوق باید به حساب دفتر کل متصل باشد",
                status_code=422,
                error_code="cash_box_account_missing",
            )
        return box.account_id
    if side_type == "bank":
        bank = await BankAccountRepo(db).get(tenant_id, side_id)
        if bank is None or bank.account_id is None:
            raise AppError(
                "حساب بانکی باید به حساب دفتر کل متصل باشد",
                status_code=422,
                error_code="bank_account_gl_missing",
            )
        return bank.account_id
    raise AppError(
        f"نوع مبدأ/مقصد نامعتبر است: {side_type} (account|cash|bank)",
        status_code=422,
        error_code="transfer_type_invalid",
    )


@router.post("/transfers", status_code=status.HTTP_201_CREATED)
async def create_transfer(
    body: TransferCreate,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    user: CurrentUser = Depends(get_current_user),
):
    """Transfer between cash/bank/any GL account — auto-posts Dr to / Cr from."""
    from app.services.posting_policy_service import PostingPolicyService
    from app.services.source_posting_guard import SourcePostingGuard

    entity = Transfer(tenant_id=tenant_id, **body.model_dump())
    await TransferRepo(db).add(entity)

    from_gl = await _resolve_transfer_gl_account(db, tenant_id, body.from_type, body.from_id)
    to_gl = await _resolve_transfer_gl_account(db, tenant_id, body.to_type, body.to_id)

    policy = await PostingPolicyService(db).get_policy(tenant_id)
    voucher_id = None
    voucher_number = None
    # Transfers always need a GL document when accounts resolve — honor policy flag if present.
    auto = bool((policy.get("auto_post") or {}).get("treasury_transfer", True))
    if auto:
        guard = SourcePostingGuard(db)
        await guard.assert_can_post(
            tenant_id,
            source_module="treasury",
            source_document_type="transfer",
            source_document_id=str(entity.id),
        )
        svc = TreasuryService(db)
        voucher = await svc.post_gl_transfer(
            tenant_id,
            amount=body.amount,
            from_account_id=from_gl,
            to_account_id=to_gl,
            actor_user_id=user.user_id,
            description=body.description or "انتقال بین حساب‌ها",
            transaction_date=body.transfer_date,
            reference=str(entity.id),
        )
        entity.voucher_id = voucher.id
        voucher_id = str(voucher.id)
        voucher_number = voucher.voucher_number
        await guard.mark_posted(
            tenant_id,
            voucher_id=voucher.id,
            source_module="treasury",
            source_document_type="transfer",
            source_document_id=str(entity.id),
        )
        # Update cash balances when sides are cash boxes
        if body.from_type == "cash":
            box = await CashBoxRepo(db).get(tenant_id, body.from_id)
            if box is not None:
                box.current_balance -= body.amount
        if body.to_type == "cash":
            box = await CashBoxRepo(db).get(tenant_id, body.to_id)
            if box is not None:
                box.current_balance += body.amount

    await db.commit()
    return {
        "id": str(entity.id),
        "voucher_id": voucher_id,
        "voucher_number": voucher_number,
    }


@router.get("/receipts")
async def list_receipts(
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    rows = await ReceiptRepo(db).list_by_tenant(tenant_id, limit=200)
    return [
        {
            "id": str(r.id),
            "receipt_number": r.receipt_number,
            "receipt_date": r.receipt_date.isoformat(),
            "amount": str(r.amount),
            "payment_method": r.payment_method,
            "description": r.description,
            "voucher_id": str(r.voucher_id) if r.voucher_id else None,
        }
        for r in rows
    ]


@router.post("/receipts", status_code=status.HTTP_201_CREATED)
async def create_receipt(
    body: ReceiptCreate,
    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
    from app.services.posting_policy_service import PostingPolicyService
    from app.services.source_posting_guard import SourcePostingGuard
    from shared.exceptions import AppError

    data = body.model_dump()
    counter_account_id = data.pop("counter_account_id", None)
    data["receipt_number"] = await DocumentNumberService(db).allocate_if_blank(
        tenant_id, "receipt", body.receipt_number
    )
    entity = ReceiptVoucher(tenant_id=tenant_id, **data)
    await ReceiptRepo(db).add(entity)

    policy = await PostingPolicyService(db).get_policy(tenant_id)
    voucher_id = None
    voucher_number = None
    if policy.get("auto_post", {}).get("treasury_receipt", True):
        if counter_account_id is None:
            raise AppError(
                "برای ثبت خودکار سند خزانه، حساب مقابل (طرف بستانکار) الزامی است",
                status_code=422,
                error_code="counter_account_required",
            )
        treasury_account_id = await _resolve_treasury_gl_account(
            db, tenant_id, body.payment_method, body.cash_box_id, body.bank_account_id
        )
        guard = SourcePostingGuard(db)
        await guard.assert_can_post(
            tenant_id,
            source_module="treasury",
            source_document_type="receipt",
            source_document_id=str(entity.id),
        )
        svc = TreasuryService(db)
        voucher = await svc.post_receipt_payment_voucher(
            tenant_id,
            amount=body.amount,
            treasury_account_id=treasury_account_id,
            counter_account_id=counter_account_id,
            is_receipt=True,
            actor_user_id=user.user_id,
            description=body.description or f"دریافت {entity.receipt_number}",
            transaction_date=body.receipt_date,
            reference=entity.receipt_number,
            cash_box_id=body.cash_box_id,
        )
        entity.voucher_id = voucher.id
        voucher_id = str(voucher.id)
        voucher_number = voucher.voucher_number
        await guard.mark_posted(
            tenant_id,
            voucher_id=voucher.id,
            source_module="treasury",
            source_document_type="receipt",
            source_document_id=str(entity.id),
        )

    await db.commit()
    return {
        "id": str(entity.id),
        "receipt_number": entity.receipt_number,
        "voucher_id": voucher_id,
        "voucher_number": voucher_number,
    }


@router.get("/payments")
async def list_payments(
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    rows = await PaymentRepo(db).list_by_tenant(tenant_id, limit=200)
    return [
        {
            "id": str(p.id),
            "payment_number": p.payment_number,
            "payment_date": p.payment_date.isoformat(),
            "amount": str(p.amount),
            "payment_method": p.payment_method,
            "description": p.description,
            "voucher_id": str(p.voucher_id) if p.voucher_id else None,
        }
        for p in rows
    ]


@router.post("/payments", status_code=status.HTTP_201_CREATED)
async def create_payment(
    body: PaymentCreate,
    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
    from app.services.posting_policy_service import PostingPolicyService
    from app.services.source_posting_guard import SourcePostingGuard
    from shared.exceptions import AppError

    data = body.model_dump()
    counter_account_id = data.pop("counter_account_id", None)
    data["payment_number"] = await DocumentNumberService(db).allocate_if_blank(
        tenant_id, "payment", body.payment_number
    )
    entity = PaymentVoucher(tenant_id=tenant_id, **data)
    await PaymentRepo(db).add(entity)

    policy = await PostingPolicyService(db).get_policy(tenant_id)
    voucher_id = None
    voucher_number = None
    if policy.get("auto_post", {}).get("treasury_payment", True):
        if counter_account_id is None:
            raise AppError(
                "برای ثبت خودکار سند خزانه، حساب مقابل (طرف بدهکار) الزامی است",
                status_code=422,
                error_code="counter_account_required",
            )
        treasury_account_id = await _resolve_treasury_gl_account(
            db, tenant_id, body.payment_method, body.cash_box_id, body.bank_account_id
        )
        guard = SourcePostingGuard(db)
        await guard.assert_can_post(
            tenant_id,
            source_module="treasury",
            source_document_type="payment",
            source_document_id=str(entity.id),
        )
        svc = TreasuryService(db)
        voucher = await svc.post_receipt_payment_voucher(
            tenant_id,
            amount=body.amount,
            treasury_account_id=treasury_account_id,
            counter_account_id=counter_account_id,
            is_receipt=False,
            actor_user_id=user.user_id,
            description=body.description or f"پرداخت {entity.payment_number}",
            transaction_date=body.payment_date,
            reference=entity.payment_number,
            cash_box_id=body.cash_box_id,
        )
        entity.voucher_id = voucher.id
        voucher_id = str(voucher.id)
        voucher_number = voucher.voucher_number
        await guard.mark_posted(
            tenant_id,
            voucher_id=voucher.id,
            source_module="treasury",
            source_document_type="payment",
            source_document_id=str(entity.id),
        )

    await db.commit()
    return {
        "id": str(entity.id),
        "payment_number": entity.payment_number,
        "voucher_id": voucher_id,
        "voucher_number": voucher_number,
    }


async def _resolve_treasury_gl_account(
    db: AsyncSession,
    tenant_id: UUID,
    payment_method: str,
    cash_box_id: UUID | None,
    bank_account_id: UUID | None,
) -> UUID:
    from shared.exceptions import AppError

    if payment_method == "cash" or cash_box_id:
        if cash_box_id is None:
            raise AppError(
                "برای دریافت/پرداخت نقدی انتخاب صندوق الزامی است",
                status_code=422,
                error_code="cash_box_required",
            )
        box = await CashBoxRepo(db).get(tenant_id, cash_box_id)
        if box is None or box.account_id is None:
            raise AppError(
                "صندوق باید به حساب دفتر کل متصل باشد",
                status_code=422,
                error_code="cash_box_account_missing",
            )
        return box.account_id

    if bank_account_id is None:
        raise AppError(
            "برای حواله بانکی انتخاب حساب بانکی الزامی است",
            status_code=422,
            error_code="bank_account_required",
        )
    bank = await BankAccountRepo(db).get(tenant_id, bank_account_id)
    if bank is None or bank.account_id is None:
        raise AppError(
            "حساب بانکی باید به حساب دفتر کل متصل باشد",
            status_code=422,
            error_code="bank_account_gl_missing",
        )
    return bank.account_id


@router.get("/reconciliations")
async def list_reconciliations(
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    rows = await ReconciliationRepo(db).list_by_tenant(tenant_id, limit=100)
    return [
        {
            "id": str(r.id),
            "bank_account_id": str(r.bank_account_id),
            "statement_date": r.statement_date.isoformat(),
            "statement_balance": str(r.statement_balance),
            "book_balance": str(r.book_balance),
            "difference": str(r.difference),
            "status": r.status.value,
        }
        for r in rows
    ]


@router.post("/reconciliations", status_code=status.HTTP_201_CREATED)
async def create_reconciliation(
    body: ReconciliationCreate,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    diff = body.statement_balance - body.book_balance
    entity = Reconciliation(
        tenant_id=tenant_id,
        bank_account_id=body.bank_account_id,
        statement_date=body.statement_date,
        statement_balance=body.statement_balance,
        book_balance=body.book_balance,
        difference=diff,
        status=ReconciliationStatus.PENDING,
    )
    await ReconciliationRepo(db).add(entity)
    await db.commit()
    return {"id": str(entity.id), "difference": str(diff)}
