"""Operational documents + inventory masters API."""
from __future__ import annotations

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

from fastapi import APIRouter, Depends, Query, status
from pydantic import BaseModel, Field
from sqlalchemy import select
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.operational import BusinessDocument, InventoryItem, Warehouse
from app.repositories.base import TenantBaseRepository
from shared.exceptions import NotFoundError
from shared.security import CurrentUser

router = APIRouter()


class BizDocCreate(BaseModel):
    module: str = Field(max_length=40)
    doc_type: str = Field(max_length=50)
    number: str | None = Field(default=None, max_length=50)
    doc_date: date
    party_name: str | None = None
    party_id: UUID | None = None
    amount: Decimal = Decimal("0")
    status: str = "draft"
    description: str | None = None
    meta_json: str | None = None


class BizDocUpdate(BaseModel):
    party_name: str | None = None
    party_id: UUID | None = None
    amount: Decimal | None = None
    status: str | None = None
    description: str | None = None
    doc_date: date | None = None
    meta_json: str | None = None


class BizDocRead(BaseModel):
    id: UUID
    tenant_id: UUID
    module: str
    doc_type: str
    number: str
    doc_date: date
    party_name: str | None
    party_id: UUID | None = None
    amount: Decimal
    status: str
    description: str | None
    meta_json: str | None = None

    model_config = {"from_attributes": True}


class ItemCreate(BaseModel):
    code: str
    name: str
    item_type: str = "goods"
    unit: str = "عدد"
    quantity_on_hand: Decimal = Decimal("0")
    unit_cost: Decimal = Decimal("0")


class ItemRead(BaseModel):
    id: UUID
    code: str
    name: str
    item_type: str
    unit: str
    quantity_on_hand: Decimal
    unit_cost: Decimal
    is_active: bool

    model_config = {"from_attributes": True}


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


class WarehouseRead(BaseModel):
    id: UUID
    code: str
    name: str
    is_active: bool

    model_config = {"from_attributes": True}


class BizDocRepo(TenantBaseRepository[BusinessDocument]):
    model = BusinessDocument


class ItemRepo(TenantBaseRepository[InventoryItem]):
    model = InventoryItem


class WarehouseRepo(TenantBaseRepository[Warehouse]):
    model = Warehouse


@router.get("/documents", response_model=list[BizDocRead])
async def list_documents(
    module: str | None = Query(default=None),
    doc_type: str | None = Query(default=None),
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    stmt = select(BusinessDocument).where(BusinessDocument.tenant_id == tenant_id)
    if module:
        stmt = stmt.where(BusinessDocument.module == module)
    if doc_type:
        stmt = stmt.where(BusinessDocument.doc_type == doc_type)
    stmt = stmt.order_by(BusinessDocument.doc_date.desc()).limit(200)
    result = await db.execute(stmt)
    return list(result.scalars().all())


@router.post("/documents", response_model=BizDocRead, status_code=status.HTTP_201_CREATED)
async def create_document(
    body: BizDocCreate,
    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, ops_document_type

    data = body.model_dump()
    svc = DocumentNumberService(db)
    data["number"] = await svc.allocate_if_blank(
        tenant_id, ops_document_type(body.module, body.doc_type), body.number
    )
    entity = BusinessDocument(tenant_id=tenant_id, **data)
    await BizDocRepo(db).add(entity)
    await db.commit()
    await db.refresh(entity)
    return entity


@router.patch("/documents/{doc_id}", response_model=BizDocRead)
async def update_document(
    doc_id: UUID,
    body: BizDocUpdate,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    repo = BizDocRepo(db)
    entity = await repo.get(tenant_id, doc_id)
    if entity is None:
        raise NotFoundError("سند یافت نشد", error_code="document_not_found")
    for k, v in body.model_dump(exclude_unset=True).items():
        setattr(entity, k, v)
    await db.commit()
    await db.refresh(entity)
    return entity


@router.post("/documents/{doc_id}/confirm", response_model=BizDocRead)
async def confirm_document(
    doc_id: UUID,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    user: CurrentUser = Depends(get_current_user),
):
    """Confirm ops document; when posting-policy auto_post is on, also post to GL."""
    from decimal import Decimal

    from app.services.posting_policy_service import PostingPolicyService
    from app.services.purchase_inventory_service import PurchaseInventoryAccountingService
    from app.services.sales_accounting_service import SalesAccountingService
    from app.models.types import SalesDocumentType
    from shared.exceptions import AppError

    entity = await BizDocRepo(db).get(tenant_id, doc_id)
    if entity is None:
        raise NotFoundError("سند یافت نشد", error_code="document_not_found")

    policy_svc = PostingPolicyService(db)
    policy = await policy_svc.get_policy(tenant_id)
    should_auto = policy_svc.is_auto_post_enabled(policy, entity.module, entity.doc_type)

    voucher_info: dict | None = None
    if should_auto:
        amount = Decimal(str(entity.amount or 0))
        if amount <= 0:
            raise AppError(
                "برای ثبت خودکار حسابداری مبلغ سند باید بزرگ‌تر از صفر باشد",
                status_code=422,
                error_code="auto_post_amount_required",
            )
        try:
            if entity.module == "sales" and entity.doc_type in ("invoice", "return"):
                doc_type = (
                    SalesDocumentType.RETURN
                    if entity.doc_type == "return"
                    else SalesDocumentType.INVOICE
                )
                voucher = await SalesAccountingService(db).post_sales_invoice(
                    tenant_id,
                    document_type=doc_type,
                    source_document_id=str(entity.id),
                    total_amount=amount,
                    actor_user_id=user.user_id,
                )
                voucher_info = {
                    "voucher_id": str(voucher.id),
                    "voucher_number": voucher.voucher_number,
                }
            elif entity.module == "purchase" and entity.doc_type in (
                "goods_receipt",
                "goods-receipts",
            ):
                voucher = await PurchaseInventoryAccountingService(db).post_goods_receipt(
                    tenant_id,
                    source_document_id=str(entity.id),
                    amount=amount,
                    actor_user_id=user.user_id,
                )
                voucher_info = {
                    "voucher_id": str(voucher.id),
                    "voucher_number": voucher.voucher_number,
                }
            elif entity.module == "purchase" and entity.doc_type == "invoice":
                voucher = await PurchaseInventoryAccountingService(db).post_purchase_invoice(
                    tenant_id,
                    source_document_id=str(entity.id),
                    amount=amount,
                    actor_user_id=user.user_id,
                    party_id=entity.party_id,
                    party_name=entity.party_name,
                )
                voucher_info = {
                    "voucher_id": str(voucher.id),
                    "voucher_number": voucher.voucher_number,
                }
            elif entity.module == "purchase" and entity.doc_type == "return":
                import json as _json

                meta: dict = {}
                if entity.meta_json:
                    try:
                        meta = _json.loads(entity.meta_json)
                    except Exception:
                        meta = {}
                lines = meta.get("lines") or []
                lines_summary = None
                if isinstance(lines, list) and lines:
                    bits = []
                    for ln in lines[:5]:
                        if not isinstance(ln, dict):
                            continue
                        title = str(ln.get("title") or "")
                        qty = str(ln.get("qty") or "")
                        if title:
                            bits.append(f"{title}×{qty}" if qty else title)
                    if bits:
                        lines_summary = "، ".join(bits)
                voucher = await PurchaseInventoryAccountingService(db).post_purchase_return(
                    tenant_id,
                    source_document_id=str(entity.id),
                    amount=amount,
                    actor_user_id=user.user_id,
                    source_invoice_id=str(meta.get("source_invoice_id") or "") or None,
                    source_invoice_number=str(meta.get("source_invoice_number") or "") or None,
                    party_id=entity.party_id,
                    party_name=entity.party_name or (str(meta.get("party_name") or "") or None),
                    lines_summary=lines_summary,
                )
                voucher_info = {
                    "voucher_id": str(voucher.id),
                    "voucher_number": voucher.voucher_number,
                }
            else:
                # Policy on but no engine mapping — leave as confirmed only
                voucher_info = None
        except AppError:
            raise
        except Exception as exc:  # noqa: BLE001
            raise AppError(
                f"ثبت خودکار حسابداری ناموفق بود: {exc}",
                status_code=422,
                error_code="auto_post_failed",
            ) from exc

    entity.status = "posted" if voucher_info else "confirmed"
    if voucher_info and entity.meta_json:
        import json

        try:
            meta = json.loads(entity.meta_json)
        except Exception:
            meta = {}
        meta["accounting_voucher"] = voucher_info
        entity.meta_json = json.dumps(meta, ensure_ascii=False)
    elif voucher_info:
        import json

        entity.meta_json = json.dumps({"accounting_voucher": voucher_info}, ensure_ascii=False)

    await db.commit()
    await db.refresh(entity)
    return entity


@router.get("/items", response_model=list[ItemRead])
async def list_items(
    q: str | None = Query(default=None, description="Search code/name"),
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    rows = await ItemRepo(db).list_by_tenant(tenant_id, limit=200)
    if q:
        needle = q.strip().lower()
        rows = [r for r in rows if needle in (r.code or "").lower() or needle in (r.name or "").lower()]
    return rows


@router.post("/items", response_model=ItemRead, status_code=status.HTTP_201_CREATED)
async def create_item(
    body: ItemCreate,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    entity = InventoryItem(tenant_id=tenant_id, **body.model_dump())
    await ItemRepo(db).add(entity)
    await db.commit()
    await db.refresh(entity)
    return entity


@router.get("/warehouses", response_model=list[WarehouseRead])
async def list_warehouses(
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    return await WarehouseRepo(db).list_by_tenant(tenant_id, limit=100)


@router.post("/warehouses", response_model=WarehouseRead, status_code=status.HTTP_201_CREATED)
async def create_warehouse(
    body: WarehouseCreate,
    tenant_id: UUID = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
    _user: CurrentUser = Depends(get_current_user),
):
    entity = Warehouse(tenant_id=tenant_id, **body.model_dump())
    await WarehouseRepo(db).add(entity)
    await db.commit()
    await db.refresh(entity)
    return entity
