from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session, joinedload
from sqlalchemy import desc, and_, or_, func
from sqlalchemy.exc import IntegrityError
from typing import List, Optional, Dict
from decimal import Decimal
from datetime import datetime, date

from app.core.database import get_db
from app.core.config import settings
from app.core.response_wrapper import UnifiedResponse
from app.models.purchases import Supplier, Purchase, PurchaseItem, PurchaseOrder, PurchaseOrderItem
from app.models.inventory import Product
from app.models.accounting import AccountingCode, JournalEntry, AccountingEntry
from app.models.accounting_dimensions import AccountingDimensionValue
from app.schemas.purchases import (
    SupplierResponse, SupplierCreate, SupplierUpdate,
    PurchaseResponse, PurchaseCreate, PurchaseUpdate,
    PurchaseItemUpdate, PurchaseItemResponse
)
from app.services.asset_management_service import AssetManagementService
from app.services.landed_cost_service import LandedCostService
from app.services.ifrs_accounting_service import IFRSAccountingService
from app.services.purchase_service import PurchaseService
from app.core.dependencies import get_current_user, get_current_company_id
from app.models.user import User
from app.models.branch import Branch

router = APIRouter()


def _format_user_display(user: Optional[User]) -> Optional[str]:
    """Return a readable user name with sensible fallbacks."""
    if not user:
        return None

    parts = [p for p in [user.first_name, user.last_name] if p]
    if parts:
        return " ".join(parts)

    for fallback in (user.username, user.email):
        if fallback:
            return fallback

    return None


def _serialize_supplier(s: Supplier) -> dict:
    base = {
        "id": s.id,
        "name": s.name,
        "email": s.email,
        "telephone": s.telephone,
        "address": s.address,
        "accounting_code_id": s.accounting_code_id,
        "vat_reg_number": s.vat_reg_number,
        "branch_id": s.branch_id,
        "supplier_type": s.supplier_type,
        "contact_person": s.contact_person,
        "payment_terms": s.payment_terms,
        "credit_limit": float(s.credit_limit or 0),
        "current_balance": float(s.current_balance or 0),
        "tax_exempt": s.tax_exempt,
        "active": s.active,
        "notes": s.notes,
        "created_at": s.created_at.isoformat() if s.created_at else None,
        "updated_at": s.updated_at.isoformat() if s.updated_at else None,
    }
    if s.accounting_code:
        base["accounting_code"] = {
            "id": s.accounting_code.id,
            "code": s.accounting_code.code,
            "name": s.accounting_code.name,
            "account_type": s.accounting_code.account_type,
        }
    return base


def _approval_policy():
    mode = (settings.purchase_approval_mode or "single_operator").lower().strip()
    self_max = Decimal(str(settings.purchase_self_approve_max or 0))
    return mode, self_max


def normalize_asset_category(category_input: str) -> str:
    """Convert user-friendly asset category names to proper enum values"""
    if not category_input:
        return 'EQUIPMENT'

    # Mapping from user-friendly names to enum values
    category_mapping = {
        'vehicle': 'VEHICLE',
        'vehicles': 'VEHICLE',
        'car': 'VEHICLE',
        'truck': 'VEHICLE',
        'equipment': 'EQUIPMENT',
        'machinery': 'MACHINERY',
        'furniture': 'FURNITURE',
        'computer': 'COMPUTER',
        'computers': 'COMPUTER',
        'office_equipment': 'OFFICE_EQUIPMENT',
        'office': 'OFFICE_EQUIPMENT',
        'building': 'BUILDING',
        'buildings': 'BUILDING',
        'land': 'LAND',
        'software': 'SOFTWARE',
        'intangible': 'INTANGIBLE',
        'inventory': 'INVENTORY',
        'other': 'OTHER'
    }

    # Normalize input and look up mapping
    normalized_input = category_input.lower().strip()
    return category_mapping.get(normalized_input, 'EQUIPMENT')


@router.post("/{purchase_id}/receive")
async def receive_purchase_items(
    purchase_id: str,
    payload: dict,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    Confirm physical receipt of goods for a purchase.

    Stock levels are already updated when the purchase is created (immediate
    posting mode).  This endpoint therefore does NOT re-add to product.quantity —
    doing so would cause double-counting.

    What this endpoint DOES:
    - Creates a GRN InventoryTransaction for the audit trail (type='grn').
    - For serialised products:
        · If serials were already registered at purchase-creation time, links
          them to this GRN transaction (inventory_transaction_id) and updates
          batch/branch data.
        · If new serials are supplied here (e.g., physical scan on arrival),
          creates the SerialNumber records now.
    - Captures batch numbers and expiry dates on the GRN transaction note.
    - Marks the purchase status as 'received'.
    - Returns a summary: serials linked, serials newly created, duplicates skipped.
    """
    try:
        purchase = db.query(Purchase).filter(Purchase.id == purchase_id).first()
        if not purchase:
            raise HTTPException(status_code=404, detail="Purchase not found")

        items = payload.get('items', [])
        received_by = payload.get('received_by') or getattr(current_user, 'username', None)

        from app.models.inventory import InventoryTransaction, SerialNumber
        from datetime import date
        from decimal import Decimal, InvalidOperation

        # Fallback: derive basic receipt lines from purchase items if none supplied
        if not items:
            items = [
                {'product_id': it.product_id, 'quantity': it.quantity,
                 'purchase_item_id': it.id}
                for it in (purchase.purchase_items or [])
            ]

        # Build a lookup: product_id → purchase_item_id (for serial linking)
        pi_by_product: dict = {}
        for pi in (purchase.purchase_items or []):
            if pi.product_id and pi.product_id not in pi_by_product:
                pi_by_product[pi.product_id] = pi

        total_serials_received = 0
        skipped_duplicates: list = []
        items_received: list = []

        for item in items:
            product_id = item.get('product_id')
            qty_raw = item.get('quantity')
            try:
                quantity = Decimal(str(qty_raw)) if qty_raw is not None else Decimal('0')
            except (InvalidOperation, ValueError, TypeError):
                quantity = Decimal('0')
            if quantity <= Decimal('0') or not product_id:
                continue

            product = db.query(Product).filter(Product.id == product_id).first()
            is_serialized = bool(product and getattr(product, 'is_serialized', False))

            # Resolve the purchase item for this line (for purchase_item_id FK)
            po_item = pi_by_product.get(product_id)
            purchase_item_id = item.get('purchase_item_id') or (po_item.id if po_item else None)

            # Batch / expiry
            item_batch = item.get('batch_number') or (po_item.batch_number if po_item else None)
            expiry_date = item.get('expiry_date') or ''

            # ── GRN audit transaction (NOT a stock increment — stock was already
            #    added when the purchase was created in immediate-posting mode).
            inv_tx = InventoryTransaction(
                product_id=product_id,
                transaction_type='grn',          # goods receipt note — confirmation only
                quantity=quantity,
                unit_cost=po_item.cost if po_item else None,
                date=date.today(),
                reference=f"GRN-{purchase_id[:8].upper()}",
                branch_id=purchase.branch_id,
                related_purchase_id=purchase_id,
                note=f"Batch:{item_batch or ''}|Expiry:{expiry_date}|ReceivedBy:{received_by or ''}",
                created_by=current_user.id if hasattr(current_user, 'id') else None,
            )
            db.add(inv_tx)
            db.flush()  # get inv_tx.id for serial linking

            # NOTE: product.quantity is NOT incremented here.
            # It was already incremented by create_purchase (immediate posting mode).
            # Incrementing here again would cause double-counting.

            # ── Serial numbers ─────────────────────────────────────────────
            # Two cases:
            #   A) Serial was registered at purchase-creation time → update it with
            #      the GRN transaction ID and confirm batch/branch.
            #   B) Serial is new (scanned on physical arrival) → create it now.
            serials_raw = item.get('serial_numbers') or []
            serials_clean = [str(s).strip() for s in serials_raw if str(s).strip()]

            item_serial_count = 0
            serials_linked = 0
            for s in serials_clean:
                existing = db.query(SerialNumber).filter(SerialNumber.serial == s).first()

                if existing:
                    # Case A: already in DB (created at purchase time)
                    if existing.purchase_id == purchase.id:
                        # Own serial — link GRN transaction and refresh batch/branch
                        if not existing.inventory_transaction_id:
                            existing.inventory_transaction_id = inv_tx.id
                        if item_batch and not existing.batch_number:
                            existing.batch_number = item_batch
                        serials_linked += 1
                        item_serial_count += 1
                    else:
                        # Belongs to a DIFFERENT purchase — genuine duplicate
                        skipped_duplicates.append({
                            "serial": s,
                            "reason": f"Belongs to a different purchase (status: {existing.status})",
                            "existing_purchase_id": existing.purchase_id,
                        })
                else:
                    # Case B: new serial scanned on arrival
                    sn = SerialNumber(
                        serial=s,
                        product_id=product_id,
                        inventory_transaction_id=inv_tx.id,
                        purchase_item_id=purchase_item_id,
                        status='available',
                        purchase_date=purchase.purchase_date or date.today(),
                        purchase_id=purchase.id,
                        supplier_id=purchase.supplier_id,
                        batch_number=item_batch,
                        branch_id=purchase.branch_id,
                        active=True,
                    )
                    db.add(sn)
                    item_serial_count += 1

            total_serials_received += item_serial_count
            items_received.append({
                "product_id": product_id,
                "product_name": product.name if product else None,
                "quantity": float(quantity),
                "is_serialized": is_serialized,
                "serials_confirmed": serials_linked,
                "serials_new": item_serial_count - serials_linked,
                "serials_skipped": len(serials_clean) - item_serial_count,
            })

        # Mark purchase as received
        purchase.status = 'received'
        if hasattr(purchase, 'received_at'):
            purchase.received_at = date.today()

        db.commit()

        return {
            "success": True,
            "message": f"Stock received. {total_serials_received} serial(s) recorded.",
            "items_received": items_received,
            "total_serials_received": total_serials_received,
            "skipped_duplicates": skipped_duplicates,
            "has_duplicates": len(skipped_duplicates) > 0,
        }
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=f"Error receiving items: {str(e)}")


@router.get("/suppliers")
async def get_suppliers(
    skip: int = 0,
    limit: int = 100,
    supplier_type: str = None,
    active: bool = None,
    search: str = None,
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """Get all suppliers with optional filtering"""
    try:
        query = db.query(Supplier).options(joinedload(Supplier.accounting_code))
        if company_id:
            branch_ids_sq = db.query(Branch.id).filter(Branch.company_id == company_id)
            query = query.filter(
                or_(Supplier.branch_id.in_(branch_ids_sq), Supplier.branch_id.is_(None))
            )

        # Apply filters
        if supplier_type:
            query = query.filter(Supplier.supplier_type == supplier_type)

        if active is not None:
            # Assuming there's an active field, if not we'll need to add it
            # For now, we'll return all suppliers
            pass

        if search:
            query = query.filter(
                Supplier.name.ilike(f"%{search}%") |
                Supplier.email.ilike(f"%{search}%") |
                Supplier.contact_person.ilike(f"%{search}%")
            )

        suppliers = query.offset(skip).limit(limit).all()

        serialized = [_serialize_supplier(s) for s in suppliers]

        return UnifiedResponse.success(
            data=serialized,
            message=f"Retrieved {len(serialized)} suppliers",
            meta={
                "total": query.count(),
                "skip": skip,
                "limit": limit,
                "filters": {
                    "supplier_type": supplier_type,
                    "active": active,
                    "search": search
                }
            }
        )
    except Exception as e:
        return UnifiedResponse.error(f"Error fetching suppliers: {str(e)}")


@router.get("/suppliers/{supplier_id}")
async def get_supplier(supplier_id: str, db: Session = Depends(get_db)):
    """Get supplier by ID (with accounting code summary)"""
    try:
        supplier = (
            db.query(Supplier)
            .options(joinedload(Supplier.accounting_code))
            .filter(Supplier.id == supplier_id)
            .first()
        )
        if not supplier:
            return UnifiedResponse.error("Supplier not found", status_code=404)

        data = {
            "id": supplier.id,
            "name": supplier.name,
            "email": supplier.email,
            "telephone": supplier.telephone,
            "address": supplier.address,
            "accounting_code_id": supplier.accounting_code_id,
            "vat_reg_number": supplier.vat_reg_number,
            "branch_id": supplier.branch_id,
            "supplier_type": supplier.supplier_type,
            "contact_person": supplier.contact_person,
            "payment_terms": supplier.payment_terms,
            "credit_limit": float(supplier.credit_limit or 0),
            "current_balance": float(supplier.current_balance or 0),
            "tax_exempt": supplier.tax_exempt,
            "active": supplier.active,
            "notes": supplier.notes,
            "created_at": supplier.created_at,
            "updated_at": supplier.updated_at,
        }
        if supplier.accounting_code:
            data["accounting_code"] = {
                "id": supplier.accounting_code.id,
                "code": supplier.accounting_code.code,
                "name": supplier.accounting_code.name,
                "account_type": supplier.accounting_code.account_type,
            }

        return UnifiedResponse.success(
            data=data,
            message=f"Retrieved supplier {supplier.name}"
        )
    except Exception as e:
        return UnifiedResponse.error(f"Error fetching supplier: {str(e)}")


@router.post("/suppliers")
async def create_supplier(
    supplier: SupplierCreate,
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
    current_user: User = Depends(get_current_user),
):
    """Create a new supplier"""
    try:
        data = supplier.model_dump()
        if not data.get("branch_id"):
            branch_id = getattr(current_user, "branch_id", None)
            if branch_id:
                data["branch_id"] = branch_id
            elif company_id:
                branch = (
                    db.query(Branch)
                    .filter(Branch.company_id == company_id, Branch.active == True)
                    .first()
                )
                if branch:
                    data["branch_id"] = branch.id

        db_supplier = Supplier(**data)
        db.add(db_supplier)
        db.commit()
        db.refresh(db_supplier)
        db_supplier = (
            db.query(Supplier)
            .options(joinedload(Supplier.accounting_code))
            .filter(Supplier.id == db_supplier.id)
            .first()
        )
        return UnifiedResponse.success(
            data=_serialize_supplier(db_supplier),
            message=f"Supplier '{db_supplier.name}' created successfully"
        )
    except IntegrityError as e:
        db.rollback()
        error_msg = str(e.orig)
        if 'duplicate key' in error_msg.lower() or 'unique constraint' in error_msg.lower():
            if 'email' in error_msg:
                return UnifiedResponse.error("A supplier with this email already exists")
            elif 'vat_reg_number' in error_msg:
                return UnifiedResponse.error("A supplier with this VAT registration number already exists")
            else:
                return UnifiedResponse.error("A supplier with this value already exists. Please ensure unique fields are different.")
        return UnifiedResponse.error(f"Database constraint violation: {error_msg}")
    except Exception as e:
        db.rollback()
        return UnifiedResponse.error(f"Error creating supplier: {str(e)}")


@router.put("/suppliers/{supplier_id}")
async def update_supplier(supplier_id: str, supplier: SupplierUpdate, db: Session = Depends(get_db)):
    """Update a supplier"""
    try:
        db_supplier = db.query(Supplier).filter(Supplier.id == supplier_id).first()
        if not db_supplier:
            return UnifiedResponse.error("Supplier not found", status_code=404)

        update_data = supplier.dict(exclude_unset=True)
        for field, value in update_data.items():
            setattr(db_supplier, field, value)

        db.commit()
        db.refresh(db_supplier)
        return UnifiedResponse.success(
            data=db_supplier,
            message=f"Supplier '{db_supplier.name}' updated successfully"
        )
    except IntegrityError as e:
        db.rollback()
        error_msg = str(e.orig)
        if 'duplicate key' in error_msg.lower() or 'unique constraint' in error_msg.lower():
            if 'email' in error_msg:
                return UnifiedResponse.error("A supplier with this email already exists")
            elif 'vat_reg_number' in error_msg:
                return UnifiedResponse.error("A supplier with this VAT registration number already exists")
            else:
                return UnifiedResponse.error("A supplier with this value already exists. Please ensure unique fields are different.")
        return UnifiedResponse.error(f"Database constraint violation: {error_msg}")
    except Exception as e:
        db.rollback()
        return UnifiedResponse.error(f"Error updating supplier: {str(e)}")


@router.delete("/suppliers/{supplier_id}")
async def delete_supplier(supplier_id: str, db: Session = Depends(get_db)):
    """Delete a supplier if it has no dependent records"""
    supplier = db.query(Supplier).filter(Supplier.id == supplier_id).first()
    if not supplier:
        raise UnifiedResponse.error("Supplier not found", status_code=404)

    # Check for dependent records that would block deletion
    dependency_checks = {
        "purchases": db.query(func.count(Purchase.id)).filter(Purchase.supplier_id == supplier_id).scalar(),
        "purchase_orders": db.query(func.count(PurchaseOrder.id)).filter(PurchaseOrder.supplier_id == supplier_id).scalar(),
        "products": db.query(func.count(Product.id)).filter(Product.supplier_id == supplier_id).scalar(),
    }

    blocking_dependencies = {k: v for k, v in dependency_checks.items() if v}
    if blocking_dependencies:
        detail_message = ", ".join(
            f"{count} {name.replace('_', ' ')}" for name, count in blocking_dependencies.items()
        )
        raise UnifiedResponse.error(
            message=(
                "Cannot delete supplier because there are related records that depend on it. "
                f"Remove the associated {detail_message} first."
            ),
            code="SUPPLIER_DELETE_BLOCKED",
            details={"dependencies": blocking_dependencies}
        )

    supplier_name = supplier.name
    try:
        db.delete(supplier)
        db.commit()
    except IntegrityError as exc:
        db.rollback()
        raise UnifiedResponse.error(
            message="Failed to delete supplier due to database constraints",
            code="SUPPLIER_DELETE_INTEGRITY_ERROR",
            details=str(exc.orig) if getattr(exc, "orig", None) else str(exc)
        )
    except Exception as exc:
        db.rollback()
        raise UnifiedResponse.error(
            message="Error deleting supplier",
            code="SUPPLIER_DELETE_ERROR",
            details=str(exc)
        )

    return UnifiedResponse.success(
        data={"id": supplier_id, "name": supplier_name},
        message=f"Supplier '{supplier_name}' deleted successfully"
    )


@router.get("", response_model=List[PurchaseResponse])
@router.get("/", response_model=List[PurchaseResponse])
async def get_purchases(
    skip: int = 0,
    limit: int = 100,
    supplier_id: Optional[str] = None,
    branch_id: Optional[str] = None,
    status: Optional[str] = None,
    from_date: Optional[date] = None,
    to_date: Optional[date] = None,
    start_date: Optional[date] = Query(None, alias="start_date"),
    end_date: Optional[date] = Query(None, alias="end_date"),
    search: Optional[str] = None,
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """Get all purchases with optional filtering"""
    try:
        query = (
            db.query(Purchase)
            .options(
                joinedload(Purchase.supplier),
                joinedload(Purchase.purchase_items),
                joinedload(Purchase.branch),
                joinedload(Purchase.created_by_user),
                joinedload(Purchase.posted_by_user),
                joinedload(Purchase.approved_by_user),
                joinedload(Purchase.created_from_branch),
                joinedload(Purchase.approved_from_branch),
                joinedload(Purchase.posted_from_branch)
            )
            .join(Supplier)
        )

        # Apply filters
        if company_id:
            _branch_ids_sq = db.query(Branch.id).filter(Branch.company_id == company_id)
            query = query.filter(
                or_(Purchase.branch_id.in_(_branch_ids_sq), Purchase.branch_id.is_(None))
            )

        if supplier_id:
            query = query.filter(Purchase.supplier_id == supplier_id)

        if branch_id:
            query = query.filter(Purchase.branch_id == branch_id)

        if status:
            query = query.filter(Purchase.status == status)

        date_from = from_date or start_date
        date_to = to_date or end_date

        if date_from:
            query = query.filter(Purchase.purchase_date >= date_from)

        if date_to:
            query = query.filter(Purchase.purchase_date <= date_to)

        if search:
            query = query.filter(
                or_(
                    Supplier.name.ilike(f"%{search}%"),
                    Purchase.reference.ilike(f"%{search}%"),
                    Purchase.notes.ilike(f"%{search}%")
                )
            )

        purchases = (
            query
            .order_by(desc(Purchase.created_at), desc(Purchase.purchase_date))
            .offset(skip)
            .limit(limit)
            .all()
        )

        def serialize_item(item: PurchaseItem) -> Dict:
            return {
                "id": item.id,
                "purchase_id": item.purchase_id,
                "product_id": item.product_id,
                "quantity": item.quantity,
                "cost": item.cost,
                "total_cost": item.total_cost,
                "vat_amount": item.vat_amount,
                "vat_rate": item.vat_rate,
                "description": item.description,
                "batch_number": getattr(item, "batch_number", None),
                "notes": item.notes,
                "is_inventory": item.is_inventory,
                "is_asset": item.is_asset,
                "created_at": getattr(item, "created_at", None),
                "updated_at": getattr(item, "updated_at", None),
            }

        results = []
        for purchase in purchases:
            results.append({
                "id": purchase.id,
                "supplier_id": purchase.supplier_id,
                "purchase_date": purchase.purchase_date,
                "total_amount": purchase.total_amount,
                "total_vat_amount": purchase.total_vat_amount,
                "total_amount_ex_vat": purchase.total_amount_ex_vat,
                "status": purchase.status,
                "reference": purchase.reference,
                "supplier_invoice_number": purchase.supplier_invoice_number,
                "notes": purchase.notes,
                "discount_amount": purchase.discount_amount,
                "discount_percentage": purchase.discount_percentage,
                "shipping_cost": purchase.shipping_cost,
                "handling_cost": purchase.handling_cost,
                "due_date": purchase.due_date,
                "branch_id": purchase.branch_id,
                "branch_name": purchase.branch.name if purchase.branch else None,
                "bank_account_id": purchase.bank_account_id,
                "amount_paid": purchase.amount_paid,
                "created_at": purchase.created_at,
                "updated_at": purchase.updated_at,
                # Full audit trail
                "created_by": purchase.created_by,
                "created_by_name": _format_user_display(purchase.created_by_user),
                "created_from_branch_id": purchase.created_from_branch_id,
                "created_from_branch_name": purchase.created_from_branch.name if purchase.created_from_branch else None,
                "approved_by": purchase.approved_by,
                "approved_by_name": _format_user_display(purchase.approved_by_user),
                "approved_at": purchase.approved_at,
                "approved_from_branch_id": purchase.approved_from_branch_id,
                "approved_from_branch_name": purchase.approved_from_branch.name if purchase.approved_from_branch else None,
                "posted_by": purchase.posted_by,
                "posted_by_name": _format_user_display(purchase.posted_by_user),
                "last_posted_date": purchase.last_posted_date,
                "posted_from_branch_id": purchase.posted_from_branch_id,
                "posted_from_branch_name": purchase.posted_from_branch.name if purchase.posted_from_branch else None,
                "purchase_items": [serialize_item(it) for it in (purchase.purchase_items or [])],
                "supplier": {
                    "id": purchase.supplier.id,
                    "name": purchase.supplier.name,
                    "email": purchase.supplier.email,
                    "telephone": purchase.supplier.telephone,
                    "address": purchase.supplier.address,
                    "accounting_code_id": purchase.supplier.accounting_code_id,
                    "vat_reg_number": purchase.supplier.vat_reg_number,
                    "branch_id": purchase.supplier.branch_id,
                    "supplier_type": purchase.supplier.supplier_type,
                    "contact_person": purchase.supplier.contact_person,
                    "payment_terms": purchase.supplier.payment_terms,
                    "credit_limit": purchase.supplier.credit_limit,
                    "current_balance": purchase.supplier.current_balance,
                    "tax_exempt": purchase.supplier.tax_exempt,
                    "active": purchase.supplier.active,
                    "notes": purchase.supplier.notes,
                    "created_at": purchase.supplier.created_at,
                    "updated_at": purchase.supplier.updated_at,
                } if purchase.supplier else None,
            })

        return results
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error fetching purchases: {str(e)}")


@router.get("/by-id/{purchase_id}")
async def get_purchase_by_id(
    purchase_id: str,
    db: Session = Depends(get_db)
):
    """Get a single purchase by ID with all details and audit trail"""
    print(f"[DEBUG purchases.py v2026.01.17.10.05] Getting purchase {purchase_id}")
    try:
        from app.models.user import User
        from app.models.purchase_payments import PurchasePayment
        from app.models.banking import BankAccount

        purchase = (
            db.query(Purchase)
            .options(
                joinedload(Purchase.supplier),
                joinedload(Purchase.purchase_items).joinedload(PurchaseItem.product),
                joinedload(Purchase.branch),
                joinedload(Purchase.created_from_branch),
                joinedload(Purchase.approved_from_branch),
                joinedload(Purchase.posted_from_branch),
                joinedload(Purchase.payments).joinedload(PurchasePayment.branch),
                joinedload(Purchase.bank_account)
            )
            .filter(Purchase.id == purchase_id)
            .first()
        )

        if not purchase:
            raise HTTPException(status_code=404, detail="Purchase not found")

        # Fetch all SerialNumber records linked to this purchase, keyed by purchase_item_id
        # (falls back to product_id for legacy rows that pre-date the purchase_item_id column)
        from app.models.inventory import SerialNumber as SNModel
        sn_rows = db.query(SNModel).filter(SNModel.purchase_id == purchase_id).all()
        sn_by_item: dict = {}
        sn_by_product_legacy: dict = {}
        for sn in sn_rows:
            if sn.purchase_item_id:
                sn_by_item.setdefault(sn.purchase_item_id, []).append(sn.serial)
            else:
                sn_by_product_legacy.setdefault(sn.product_id, []).append(sn.serial)

        # Collect user IDs to resolve names in one query
        user_ids = set(filter(None, [purchase.created_by, purchase.approved_by, purchase.posted_by]))
        for pay in purchase.payments or []:
            if pay.recorded_by:
                user_ids.add(pay.recorded_by)

        users_map = {}
        if user_ids:
            users = db.query(User).filter(User.id.in_(user_ids)).all()
            for u in users:
                parts = [p for p in [u.first_name, u.last_name] if p]
                users_map[u.id] = " ".join(parts) if parts else u.username

        # Build response dict manually to include computed fields and audit info
        from app.models.branch import Branch

        branch_name = purchase.branch.name if purchase.branch else None
        branch_code = purchase.branch.code if purchase.branch else None

        created_by_name = users_map.get(purchase.created_by)
        created_from_branch_name = purchase.created_from_branch.name if purchase.created_from_branch else None

        approved_by_name = users_map.get(purchase.approved_by)
        approved_from_branch_name = purchase.approved_from_branch.name if purchase.approved_from_branch else None

        posted_by_name = users_map.get(purchase.posted_by)
        posted_from_branch_name = purchase.posted_from_branch.name if purchase.posted_from_branch else None

        bank_account_info = None
        bank_account_label = None
        if purchase.bank_account:
            bank_account_info = {
                "id": purchase.bank_account.id,
                "name": purchase.bank_account.name,
                "institution": purchase.bank_account.institution,
                "account_number": purchase.bank_account.account_number,
                "currency": purchase.bank_account.currency,
                "branch_id": purchase.bank_account.branch_id,
            }
            acct_name = purchase.bank_account.name or "Bank Account"
            acct_num = purchase.bank_account.account_number
            bank_account_label = f"{acct_name} ({acct_num})" if acct_num else acct_name

        payments_payload = []
        payment_references = []
        for pay in purchase.payments or []:
            if pay.reference:
                payment_references.append(pay.reference)
            payments_payload.append({
                "id": pay.id,
                "amount": float(pay.amount or 0),
                "payment_date": pay.payment_date.isoformat() if pay.payment_date else None,
                "payment_method": pay.payment_method,
                "reference": pay.reference,
                "notes": pay.notes,
                "recorded_by": pay.recorded_by,
                "recorded_by_name": users_map.get(pay.recorded_by),
                "recorded_at": pay.recorded_at.isoformat() if pay.recorded_at else None,
                "branch_id": pay.branch_id,
                "branch_name": pay.branch.name if pay.branch else None
            })

        mode, self_max = _approval_policy()

        result = {
            "id": purchase.id,
            "supplier_id": purchase.supplier_id,
            "reference": purchase.reference,
            "purchase_date": purchase.purchase_date.isoformat() if purchase.purchase_date else None,
            "due_date": purchase.due_date.isoformat() if purchase.due_date else None,
            "status": purchase.status,
            "total_amount": float(purchase.total_amount or 0),
            "total_vat_amount": float(purchase.total_vat_amount or 0),
            "total_amount_ex_vat": float(purchase.total_amount_ex_vat or 0),
            "amount_paid": float(purchase.amount_paid or 0),
            "notes": purchase.notes,
            "branch_id": purchase.branch_id,
            "branch_name": branch_name,
            "branch_code": branch_code,
            "bank_account_id": purchase.bank_account_id,
            "bank_account": bank_account_info,
            "bank_account_label": bank_account_label,
            "bank_reference": payment_references[0] if payment_references else None,
            "payment_references": payment_references,
            "approval_mode": mode,
            "self_approve_max": float(self_max),
            "created_at": purchase.created_at.isoformat() if purchase.created_at else None,
            "updated_at": purchase.updated_at.isoformat() if purchase.updated_at else None,
            "created_by": purchase.created_by,
            "created_by_name": created_by_name,
            "created_from_branch_id": purchase.created_from_branch_id,
            "created_from_branch_name": created_from_branch_name,
            "approved_by": purchase.approved_by,
            "approved_by_name": approved_by_name,
            "approved_at": purchase.approved_at.isoformat() if purchase.approved_at else None,
            "approved_from_branch_id": purchase.approved_from_branch_id,
            "approved_from_branch_name": approved_from_branch_name,
            "posted_by": purchase.posted_by,
            "posted_by_name": posted_by_name,
            "last_posted_date": purchase.last_posted_date.isoformat() if purchase.last_posted_date else None,
            "posted_from_branch_id": purchase.posted_from_branch_id,
            "posted_from_branch_name": posted_from_branch_name,
            "supplier": {
                "id": purchase.supplier.id,
                "name": purchase.supplier.name,
                "email": purchase.supplier.email,
                # FIXED: Using telephone field (not phone) - v2025.10.19.22.35
                "phone": purchase.supplier.telephone,
                "address": purchase.supplier.address,
            } if purchase.supplier else None,
            "purchase_items": [
                {
                    "id": item.id,
                    "product_id": item.product_id,
                    "product_name": item.product.name if item.product else None,
                    "product_sku": item.product.sku if item.product else None,
                    "description": item.description,
                    "quantity": float(item.quantity or 0),
                    "cost": float(item.cost or 0),
                    "unit_cost": float(item.cost or 0),
                    "total_cost": float(item.total_cost or 0),
                    "vat_rate": float(item.vat_rate or 0),
                    "vat_amount": float(item.vat_amount or 0),
                    "batch_number": item.batch_number,
                    "is_asset": item.is_asset or False,
                    "is_inventory": item.is_inventory or False,
                    # serial_numbers: prefer item-keyed records; fall back to legacy product-keyed rows
                    "serial_numbers": list(dict.fromkeys(
                        sn_by_item.get(item.id) or
                        sn_by_product_legacy.get(item.product_id) or
                        (list(item.serial_numbers) if item.serial_numbers else [])
                    )),
                } for item in (purchase.purchase_items or [])
            ],
            "payments": payments_payload
        }

        return result
    except HTTPException:
        raise
    except Exception as e:
        import traceback
        error_log = f"Error fetching purchase {purchase_id}: {e}\n{traceback.format_exc()}"
        print(error_log)
        # Also write to file for debugging
        with open("purchase_error.log", "a") as f:
            import datetime
            f.write(f"\n\n=== {datetime.datetime.now()} ===\n{error_log}\n")
        raise HTTPException(status_code=500, detail=f"Error fetching purchase: {str(e)}")


# NOTE: The GET "/{purchase_id}" route is declared later in this file, after static routes like
# "/products" and "/dashboard-stats" to avoid route shadowing issues.


@router.post("", response_model=PurchaseResponse)
@router.post("/", response_model=PurchaseResponse)
async def create_purchase(
    purchase: PurchaseCreate,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """Create a new purchase with automatic accounting entries and optional landed costs"""
    try:
        print(f"==== PURCHASE CREATION DEBUG ====")
        print(f"Received purchase data: {purchase.model_dump()}")
        print(f"Created by user: {current_user.username} ({current_user.id})")

        # Basic validation
        if not purchase.supplier_id or not purchase.purchase_date or not purchase.items:
            raise HTTPException(status_code=400, detail="Supplier, purchase date, and at least one item are required.")

        print(f"Basic validation passed")

        # Pop items and landed costs to handle them separately
        items_data = purchase.items
        processed_item_payloads = []
        landed_costs_data = purchase.landed_costs

        # Calculate totals from items with proper VAT handling
        total_amount_ex_vat = sum(item.quantity * item.cost for item in items_data)
        total_vat = 0

        # Calculate VAT based on the VAT calculation mode and individual item rates
        for item in items_data:
            line_total = item.quantity * item.cost
            vat_rate = item.vat_rate / 100 if item.vat_rate else 0

            # Calculate VAT amount per line item
            if vat_rate > 0:
                # Assuming exclusive VAT calculation by default (VAT is added to the cost)
                line_vat = Decimal(str(line_total * vat_rate))
                total_vat += line_vat

        # Add landed costs to totals (landed costs are typically VAT-free)
        if landed_costs_data:
            total_amount_ex_vat += Decimal(str(sum(lc.amount for lc in landed_costs_data)))

        # Calculate grand total
        grand_total = total_amount_ex_vat + total_vat

        # Create Purchase object from schema, excluding items, landed costs, and payment fields
        purchase_dict = purchase.model_dump(exclude={
            'items', 'landed_costs', 'payment_method', 'payment_source_id',
            'payment_source_type', 'payment_reference', 'expense_account_id', 'vat_account_id'
        })
        purchase_dict['total_amount'] = grand_total
        purchase_dict['total_vat_amount'] = total_vat
        purchase_dict['total_amount_ex_vat'] = total_amount_ex_vat

        # Set default branch_id if not provided
        if 'branch_id' not in purchase_dict or not purchase_dict['branch_id']:
            _branch_q = db.query(Branch).filter(Branch.active == True)
            if company_id:
                _branch_q = _branch_q.filter(Branch.company_id == company_id)
            default_branch = _branch_q.first()
            if default_branch:
                purchase_dict['branch_id'] = default_branch.id
                print(f"Using default branch: {default_branch.name} ({default_branch.id})")

        # Capture the current user as the creator with audit trail
        from datetime import datetime
        purchase_dict['created_by'] = current_user.id
        purchase_dict['created_at'] = datetime.utcnow()
        purchase_dict['created_from_branch_id'] = purchase_dict.get('branch_id')
        print(f"Set created_by to: {current_user.id} from branch {purchase_dict.get('branch_id')}")

        db_purchase = Purchase(**purchase_dict)
        db.add(db_purchase)
        db.flush()  # Flush to get the purchase ID

        # Process each purchase item
        for item_data in items_data:
            item_dict = item_data.model_dump()
            # Track normalized payload for follow-up processing (e.g., asset creation)
            normalized_payload = item_dict.copy()

            is_asset = bool(item_dict.get('is_asset'))
            if is_asset:
                # Assets should never be treated as inventory lines
                item_dict['is_inventory'] = False
                normalized_payload['is_inventory'] = False
                # Normalize asset category to enumeration-friendly uppercase value
                normalized_payload['asset_category'] = normalize_asset_category(item_dict.get('asset_category'))
                item_dict['asset_category'] = normalized_payload['asset_category']
                # Ensure depreciation method is always lower-case for service consumption
                if normalized_payload.get('asset_depreciation_method'):
                    method = str(normalized_payload['asset_depreciation_method']).lower()
                    normalized_payload['asset_depreciation_method'] = method
                    item_dict['asset_depreciation_method'] = method
            else:
                # Default to inventory when flag not supplied
                if 'is_inventory' not in item_dict:
                    item_dict['is_inventory'] = True
                normalized_payload['is_inventory'] = item_dict['is_inventory']

            item_dict['purchase_id'] = db_purchase.id

            # Extract serial_numbers before creating PurchaseItem (handled separately below)
            item_serials = item_dict.pop('serial_numbers', None) or []

            # Derive total_cost if missing (quantity * cost)
            if not item_dict.get('total_cost') and item_dict.get('quantity') is not None and item_dict.get('cost') is not None:
                try:
                    item_dict['total_cost'] = item_dict['quantity'] * item_dict['cost']
                    normalized_payload['total_cost'] = item_dict['total_cost']
                except Exception:
                    # Fallback: ignore if calculation fails
                    pass

            db_item = PurchaseItem(**item_dict)
            db_item.serial_numbers = list(item_serials)  # store on item for reference
            db.add(db_item)
            db.flush()  # ensure the purchase item has an ID for downstream processing
            normalized_payload['purchase_item_id'] = db_item.id

            # Create individual SerialNumber records for each serial provided
            if item_serials and item_dict.get('product_id'):
                from app.models.inventory import SerialNumber
                for sn_value in item_serials:
                    sn_value = str(sn_value).strip()
                    if not sn_value:
                        continue
                    sn_record = SerialNumber(
                        serial=sn_value,
                        product_id=item_dict['product_id'],
                        status='available',
                        purchase_date=db_purchase.purchase_date,
                        purchase_id=db_purchase.id,
                        purchase_item_id=db_item.id,
                        supplier_id=db_purchase.supplier_id,
                        batch_number=item_dict.get('batch_number'),
                        branch_id=db_purchase.branch_id,
                    )
                    db.add(sn_record)

            processed_item_payloads.append(normalized_payload)

        # Handle landed costs if present
        if landed_costs_data:
            landed_cost_service = LandedCostService(db)
            landed_cost_payload = {
                "purchase_id": db_purchase.id,
                "reference": f"LC for Purchase {db_purchase.reference or db_purchase.id}",
                "supplier_id": db_purchase.supplier_id,
                "date": db_purchase.purchase_date,
                "items": [lc.model_dump() for lc in landed_costs_data]
            }
            try:
                landed_cost_service.create_landed_cost(landed_cost_payload)
            except Exception as lc_error:
                print(f"Warning: Failed to create landed cost document for purchase {db_purchase.id}: {lc_error}")

        # Commit the purchase and its items
        db.commit()
        db.refresh(db_purchase)

        # --- Post-creation processing: Accounting and Inventory ---

        # 1. Create journal entries for the purchase itself (Dr. Inventory/Expense, Cr. A/P)
        try:
            print(f"Creating journal entries for purchase {db_purchase.id}")
            ifrs_service = IFRSAccountingService(db)
            journal_entries = ifrs_service.create_purchase_journal_entries(db_purchase, created_by_user_id=current_user.id)
            print(f"Successfully created {len(journal_entries)} main journal entries for purchase {db_purchase.id}")

            # Mark posting status and audit
            db_purchase.posting_status = "posted"
            db_purchase.posted_by = current_user.id
            db_purchase.last_posted_date = datetime.utcnow()
            db.commit()
            db.refresh(db_purchase)
        except Exception as accounting_error:
            db.rollback()  # clear any dirty posting_status/posted_by attrs set before a failed commit
            print(f"Warning: Failed to create main accounting entries for purchase {db_purchase.id}: {accounting_error}")
            import traceback
            print(f"Full traceback: {traceback.format_exc()}")

        # 2. If an initial payment was made, create payment journal entries and update balances
        if purchase.amount_paid and purchase.amount_paid > 0:
            try:
                ifrs_service = IFRSAccountingService(db)

                # Determine payment account based on payment source
                payment_account_id = None
                if purchase.payment_source_type == 'bank' and purchase.payment_source_id:
                    # For bank accounts, use the bank_account_id in the payment details
                    payment_account_id = purchase.payment_source_id
                elif purchase.payment_source_type == 'cash' and purchase.payment_source_id:
                    # For cash accounts, the payment_source_id is the accounting code ID
                    payment_account_id = purchase.payment_source_id
                elif purchase.bank_account_id:
                    # Legacy support
                    payment_account_id = purchase.bank_account_id

                payment_details = {
                    "purchase_id": db_purchase.id,
                    "amount": purchase.amount_paid,
                    "payment_date": db_purchase.purchase_date,
                    "payment_account_id": payment_account_id,
                    "payment_source_type": purchase.payment_source_type or 'bank',
                    "payment_reference": purchase.payment_reference,
                    "branch_id": db_purchase.branch_id,
                    "supplier_id": db_purchase.supplier_id,
                }

                # Create journal entries for payment
                payment_journal_entries = ifrs_service.create_purchase_payment_journal_entries(
                    purchase=db_purchase,
                    payment_amount=purchase.amount_paid,
                    payment_date=db_purchase.purchase_date,
                    bank_account_id=payment_account_id
                )
                print(f"Successfully created {len(payment_journal_entries)} payment journal entries for purchase {db_purchase.id}")

                # Create bank transaction record for bank payments
                if purchase.payment_source_type == 'bank' and purchase.payment_source_id:
                    from app.models.banking import BankTransaction, BankAccount
                    # Resolve the real BankAccount — frontend may send either BankAccount.id
                    # (new: from /banking/accounts) or accounting_code.id (legacy fallback)
                    resolved_bank_acct = db.query(BankAccount).filter(
                        BankAccount.id == purchase.payment_source_id
                    ).first()
                    if not resolved_bank_acct:
                        resolved_bank_acct = db.query(BankAccount).filter(
                            BankAccount.accounting_code_id == purchase.payment_source_id
                        ).first()

                    if resolved_bank_acct:
                        bank_transaction = BankTransaction(
                            bank_account_id=resolved_bank_acct.id,   # always a real bank_accounts.id
                            date=db_purchase.purchase_date,
                            amount=-purchase.amount_paid,  # Negative for outflow
                            description=f"Purchase payment - {purchase.payment_reference or db_purchase.reference or db_purchase.id}",
                            transaction_type='purchase_payment',
                            reference=purchase.payment_reference or db_purchase.reference,
                            reconciled=False
                        )
                        db.add(bank_transaction)
                    else:
                        print(f"Warning: could not resolve BankAccount for payment_source_id={purchase.payment_source_id}; BankTransaction not created")

                # Update account balances
                if purchase.payment_source_type == 'bank' and purchase.payment_source_id:
                    # resolved_bank_acct was set in the BankTransaction block above
                    if resolved_bank_acct:
                        resolved_bank_acct.balance = (resolved_bank_acct.balance or 0) - purchase.amount_paid
                        print(f"Updated bank account {resolved_bank_acct.name} balance: {resolved_bank_acct.balance}")

                elif purchase.payment_source_type == 'cash' and purchase.payment_source_id:
                    # Update cash account balance in accounting codes
                    from app.models.accounting import AccountingCode
                    cash_account = db.query(AccountingCode).filter(AccountingCode.id == purchase.payment_source_id).first()
                    if cash_account:
                        cash_account.total_credits = (cash_account.total_credits or 0) + purchase.amount_paid
                        cash_account.update_balance()
                        print(f"Updated cash account {cash_account.name} balance: {cash_account.balance}")

                # Commit balance updates
                db.commit()
                print(f"Successfully created journal entries and updated balances for payment of purchase {db_purchase.id}")

            except Exception as payment_je_error:
                print(f"Warning: Failed to create journal entries for initial payment of purchase {db_purchase.id}: {payment_je_error}")
                db.rollback()

        # 3. Create immediate inventory adjustments for all purchases
        # This ensures inventory is updated when purchase is created, not just when received
        try:
            from app.services.inventory_service import InventoryService
            inventory_service = InventoryService(db)
            asset_service = AssetManagementService(db)

            # Prepare quick lookup for processed payloads keyed by purchase item ID
            processed_lookup = {
                payload['purchase_item_id']: payload for payload in processed_item_payloads if payload.get('purchase_item_id')
            }

            # Ensure a PPE account exists so assets can be tagged correctly
            from app.models.accounting import AccountingCode
            ppe_account = db.query(AccountingCode).filter(AccountingCode.reporting_tag == 'A2.1').first()
            if not ppe_account:
                try:
                    ifrs_helper = IFRSAccountingService(db)
                    ppe_account = (
                        ifrs_helper._get_accounting_code_by_ifrs_tag('A2.1') or
                        ifrs_helper._get_default_accounting_code('Property, Plant and Equipment', 'Asset')
                    )
                except Exception:
                    ppe_account = None

            for item in db_purchase.purchase_items:
                payload = processed_lookup.get(item.id, {})
                if getattr(item, 'is_asset', False):
                    # Ensure the asset remains mapped to a PPE account
                    if not item.asset_accounting_code_id and ppe_account:
                        item.asset_accounting_code_id = ppe_account.id

                    name = payload.get('asset_name') or item.asset_name or item.description or f"Asset from {db_purchase.id}"
                    category = payload.get('asset_category') or normalize_asset_category(item.asset_category or 'equipment')
                    depreciation_method = payload.get('asset_depreciation_method') or item.asset_depreciation_method or 'straight_line'
                    useful_life = payload.get('asset_useful_life_years') or item.asset_useful_life_years or 5
                    salvage_value = payload.get('asset_salvage_value') or item.asset_salvage_value or 0

                    quantity = payload.get('quantity') or item.quantity or 1
                    unit_cost = payload.get('cost') or item.cost or 0
                    total_cost = payload.get('total_cost') or item.total_cost or (quantity * unit_cost)

                    asset_payload = {
                        'name': name,
                        'category': category,
                        'purchase_date': db_purchase.purchase_date,
                        'purchase_cost': float(total_cost or 0),
                        'current_value': float(total_cost or 0),
                        'salvage_value': float(salvage_value or 0),
                        'depreciation_method': depreciation_method,
                        'useful_life_years': int(useful_life or 0) or 5,
                        'serial_number': payload.get('asset_serial_number') or item.asset_serial_number,
                        'vehicle_registration': payload.get('asset_vehicle_registration') or item.asset_vehicle_registration,
                        'engine_number': payload.get('asset_engine_number') or item.asset_engine_number,
                        'chassis_number': payload.get('asset_chassis_number') or item.asset_chassis_number,
                        'accounting_code_id': item.asset_accounting_code_id or (ppe_account.id if ppe_account else None),
                        'branch_id': db_purchase.branch_id,
                        'supplier_id': db_purchase.supplier_id,
                        'notes': payload.get('asset_notes') or item.asset_notes or f"Created from purchase {db_purchase.reference or db_purchase.id}",
                        'ifrs_category': 'PPE_IAS_16',
                        # Accounting entry already handled by the purchase journal
                        'skip_accounting_entry': True
                    }

                    # Normalize category via helper to align with enum expectations
                    asset_payload['category'] = normalize_asset_category(asset_payload.get('category'))

                    try:
                        asset_service.create_asset(asset_payload)
                    except Exception as asset_error:
                        print(f"Warning: Failed to create asset for purchase item {item.id}: {asset_error}")
                elif getattr(item, 'is_inventory', False) and item.product_id:
                    product_before = db.query(Product).filter(Product.id == item.product_id).first()
                    qty_before = product_before.quantity if product_before else 0
                    print(f" DEBUG: Product {item.product_id} quantity BEFORE: {qty_before}, adding: {item.quantity}")

                    # Update product quantity (this also creates the InventoryTransaction internally)
                    # REMOVED duplicate InventoryTransaction creation - the service method creates it
                    success, message = inventory_service.update_product_quantity(
                        product_id=item.product_id,
                        quantity_change=item.quantity,
                        transaction_type='goods_receipt',
                        reference=f"Purchase {db_purchase.reference or db_purchase.id}",
                        branch_id=db_purchase.branch_id,
                        related_purchase_id=db_purchase.id
                    )

                    # DEBUG: Get product quantity after update
                    product_after = db.query(Product).filter(Product.id == item.product_id).first()
                    qty_after = product_after.quantity if product_after else 0
                    expected = qty_before + item.quantity
                    print(f" DEBUG: Product {item.product_id} quantity AFTER: {qty_after}, expected: {expected}")

                    if success:
                        print(f" Updated inventory for {item.product.name if item.product else 'Product'}: +{item.quantity} units")
                    else:
                        print(f" Warning updating inventory for product {item.product_id}: {message}")

            db.commit()
            print(f" Inventory adjustments completed for purchase {db_purchase.id}")
        except Exception as inventory_error:
            print(f" Warning: Failed to create inventory adjustments for purchase {db_purchase.id}: {inventory_error}")
            # Don't rollback the entire purchase for inventory issues
            import traceback
            print(f"Full traceback: {traceback.format_exc()}")

        # 4. Auto-tag each purchased product to this supplier in ProductSupplierCatalog.
        #    Batch-checks existing pairs in a single IN query to avoid N+1 round-trips.
        try:
            from app.models.inventory import ProductSupplierCatalog
            supplier_id_str = str(db_purchase.supplier_id)
            product_ids = [item.product_id for item in (db_purchase.purchase_items or []) if item.product_id]
            if product_ids:
                existing_product_ids = {
                    row.product_id
                    for row in db.query(ProductSupplierCatalog.product_id)
                    .filter(
                        ProductSupplierCatalog.product_id.in_(product_ids),
                        ProductSupplierCatalog.supplier_id == supplier_id_str,
                    )
                    .all()
                }
                currency = getattr(db_purchase, "currency", "BWP") or "BWP"
                for item in db_purchase.purchase_items or []:
                    if item.product_id and item.product_id not in existing_product_ids:
                        db.add(ProductSupplierCatalog(
                            product_id=item.product_id,
                            supplier_id=supplier_id_str,
                            unit_cost=item.cost,
                            currency=currency,
                            active=True,
                        ))
            db.commit()
            print(f" Supplier-product catalog updated for purchase {db_purchase.id}")
        except Exception as catalog_error:
            print(f" Warning: Failed to auto-tag supplier catalog for purchase {db_purchase.id}: {catalog_error}")

        return db_purchase
    except HTTPException:
        raise
    except IntegrityError as e:
        db.rollback()
        error_msg = str(e.orig)
        if 'duplicate key' in error_msg.lower() or 'unique constraint' in error_msg.lower():
            if 'po_number' in error_msg or 'reference' in error_msg:
                raise HTTPException(status_code=400, detail="A purchase with this reference/PO number already exists")
            else:
                raise HTTPException(status_code=400, detail="A record with this value already exists. Please ensure unique fields are different.")
        raise HTTPException(status_code=400, detail=f"Database constraint violation: {error_msg}")
    except Exception as e:
        db.rollback()
        print(f"Error creating purchase: {e}")
        raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")


@router.put("/{purchase_id}", response_model=PurchaseResponse)
async def update_purchase(purchase_id: str, purchase: PurchaseUpdate, db: Session = Depends(get_db)):
    """Update a purchase"""
    try:
        db_purchase = db.query(Purchase).filter(Purchase.id == purchase_id).first()
        if not db_purchase:
            raise HTTPException(status_code=404, detail="Purchase not found")

        update_data = purchase.dict(exclude_unset=True)
        for field, value in update_data.items():
            setattr(db_purchase, field, value)

        db.commit()
        db.refresh(db_purchase)
        return db_purchase
    except HTTPException:
        raise
    except IntegrityError as e:
        db.rollback()
        error_msg = str(e.orig)
        if 'duplicate key' in error_msg.lower() or 'unique constraint' in error_msg.lower():
            if 'po_number' in error_msg or 'reference' in error_msg:
                raise HTTPException(status_code=400, detail="A purchase with this reference/PO number already exists")
            else:
                raise HTTPException(status_code=400, detail="A record with this value already exists. Please ensure unique fields are different.")
        raise HTTPException(status_code=400, detail=f"Database constraint violation: {error_msg}")
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=f"Error updating purchase: {str(e)}")


@router.post("/{purchase_id}/approve")
async def approve_purchase(
    purchase_id: str,
    payload: dict,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    """Approve a purchase with configurable maker-checker rules."""
    try:
        # Use current user as the approver
        approver_id = current_user.id
        print(f"Approving purchase {purchase_id} by user: {current_user.username} ({approver_id})")

        purchase = db.query(Purchase).filter(Purchase.id == purchase_id).first()
        if not purchase:
            raise HTTPException(status_code=404, detail="Purchase not found")

        if purchase.approved_by:
            raise HTTPException(status_code=400, detail="Purchase already approved")

        mode = (settings.purchase_approval_mode or "single_operator").lower()
        self_max = Decimal(str(settings.purchase_self_approve_max or 0))
        total_amount = Decimal(str(purchase.total_amount or 0))
        is_same_user = purchase.created_by and purchase.created_by == approver_id

        allow_self = False
        if mode == "single_operator":
            allow_self = True
        elif mode == "segregated":
            if is_same_user and total_amount <= self_max:
                allow_self = True
            elif not is_same_user:
                allow_self = True
        else:
            allow_self = True  # fallback

        if is_same_user and not allow_self:
            raise HTTPException(status_code=403, detail="Segregation of duties enforced; approver must differ from requester")

        # Capture approver audit trail with branch and timestamp
        from datetime import datetime
        purchase.approved_by = approver_id
        purchase.approved_at = datetime.utcnow()
        purchase.approved_from_branch_id = current_user.branch_id or purchase.branch_id
        if purchase.status in [None, "pending", "draft", "ordered"]:
            purchase.status = "approved"

        db.commit()
        db.refresh(purchase)

        return {
            "message": "Purchase approved",
            "purchase_id": purchase.id,
            "approved_by": approver_id,
            "approved_at": purchase.approved_at.isoformat() if purchase.approved_at else None,
            "approved_from_branch_id": purchase.approved_from_branch_id,
            "status": purchase.status,
            "mode": mode,
            "self_allowed": allow_self,
            "self_threshold": float(self_max)
        }
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=f"Error approving purchase: {str(e)}")


@router.post("/{purchase_id}/payment")
async def record_purchase_payment(
    purchase_id: str,
    payment_data: dict,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    """Record a payment against a purchase"""
    try:
        # Get the purchase
        purchase = db.query(Purchase).filter(Purchase.id == purchase_id).first()
        if not purchase:
            raise HTTPException(status_code=404, detail="Purchase not found")

        # Extract payment details
        payment_amount = Decimal(str(payment_data.get('amount', 0)))
        payment_method = payment_data.get('payment_method', 'bank_transfer')
        payment_date = payment_data.get('payment_date')
        reference = payment_data.get('reference', '')
        notes = payment_data.get('notes', '')
        # Use current user as the recorder
        recorded_by = current_user.id
        print(f"Recording payment for purchase {purchase_id} by user: {current_user.username} ({recorded_by})")

        mode, _self_max = _approval_policy()
        if mode == "segregated" and not purchase.approved_by:
            raise HTTPException(status_code=409, detail="Purchase must be approved before recording payments (segregated mode)")

        # Validate payment amount
        if payment_amount <= 0:
            raise HTTPException(status_code=400, detail="Payment amount must be greater than 0")

        # Calculate new amount_paid
        current_amount_paid = purchase.amount_paid or Decimal('0')
        new_amount_paid = current_amount_paid + payment_amount

        # Check if payment doesn't exceed total amount
        if new_amount_paid > purchase.total_amount:
            raise HTTPException(
                status_code=400,
                detail=f"Payment amount ({payment_amount}) would exceed remaining balance"
            )

        # Persist payment record and create journals
        from app.models.purchase_payments import PurchasePayment
        from app.services.ifrs_accounting_service import IFRSAccountingService
        from app.models.banking import BankTransaction, BankAccount
        from app.models.accounting import AccountingCode

        payment_record = PurchasePayment(
            purchase_id=purchase.id,
            amount=payment_amount,
            payment_date=payment_date or date.today(),
            payment_method=payment_method,
            reference=reference,
            notes=notes,
            branch_id=purchase.branch_id,
            recorded_by=recorded_by
        )
        db.add(payment_record)
        db.flush()

        # Update purchase aggregates
        purchase.amount_paid = new_amount_paid
        if new_amount_paid >= purchase.total_amount:
            purchase.status = 'paid'
        elif new_amount_paid > 0:
            purchase.status = 'partially_paid'

        # Commit core payment and purchase updates first
        db.commit()
        db.refresh(purchase)

        # Best-effort: create journals and update balances without failing the whole request
        journal_entries_response = []
        try:
            ifrs_service = IFRSAccountingService(db)
            # Accept account ID from any field name — IFRS service resolves
            # BankAccount.id → BankAccount.accounting_code_id → direct AccountingCode.id
            payment_account_id = (
                payment_data.get('bank_account_id') or
                payment_data.get('payment_source_id') or
                None
            )

            payment_journals = ifrs_service.create_purchase_payment_journal_entries(
                purchase=purchase,
                payment_amount=payment_amount,
                payment_date=payment_record.payment_date,
                bank_account_id=payment_account_id
            )

            # Create BankTransaction for tracked bank accounts;
            # also try lookup via accounting_code_id for cases where frontend sends the code id
            if payment_account_id:
                bank_acc = db.query(BankAccount).filter(BankAccount.id == payment_account_id).first()
                if not bank_acc:
                    bank_acc = db.query(BankAccount).filter(BankAccount.accounting_code_id == payment_account_id).first()
                if bank_acc:
                    bank_tx = BankTransaction(
                        bank_account_id=bank_acc.id,
                        date=payment_record.payment_date,
                        amount=-payment_amount,
                        description=f"Purchase payment - {reference or purchase.reference or purchase.id}",
                        transaction_type='purchase_payment',
                        reference=reference or purchase.reference,
                        reconciled=False
                    )
                    db.add(bank_tx)
                    bank_acc.balance = (bank_acc.balance or 0) - payment_amount

            db.commit()
            journal_entries_response = [
                {
                    'id': je.id,
                    'debit': float(je.debit_amount),
                    'credit': float(je.credit_amount),
                    'account': je.accounting_code.code if je.accounting_code else None,
                    'description': je.description
                } for je in payment_journals
            ]
        except Exception as je_err:
            # Log and move on
            print(f"Warning: Failed to create payment journals/updates for purchase {purchase.id}: {je_err}")
            db.rollback()

        return {
            "message": "Payment recorded successfully",
            "purchase_id": purchase_id,
            "payment_amount": float(payment_amount),
            "total_paid": float(new_amount_paid),
            "remaining_balance": float(purchase.total_amount - new_amount_paid),
            "status": purchase.status,
            "journal_entries": journal_entries_response
        }

    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=f"Error recording payment: {str(e)}")


@router.post("/bulk-payment")
async def bulk_record_payments(
    payment_data: dict,
    db: Session = Depends(get_db)
):
    """Record payments for multiple purchases"""
    try:
        purchase_payments = payment_data.get('payments', [])
        common_data = payment_data.get('common', {})

        if not purchase_payments:
            raise HTTPException(status_code=400, detail="No payments provided")

        results = []
        errors = []

        for payment in purchase_payments:
            try:
                purchase_id = payment.get('purchase_id')
                amount = Decimal(str(payment.get('amount', 0)))

                # Use common data if specific data not provided
                payment_date = payment.get('payment_date') or common_data.get('payment_date')
                payment_method = payment.get('payment_method') or common_data.get('payment_method', 'bank_transfer')
                reference = payment.get('reference') or common_data.get('reference', '')
                notes = payment.get('notes') or common_data.get('notes', '')

                # Get the purchase
                purchase = db.query(Purchase).filter(Purchase.id == purchase_id).first()
                if not purchase:
                    errors.append(f"Purchase {purchase_id} not found")
                    continue

                # Validate payment amount
                if amount <= 0:
                    errors.append(f"Invalid payment amount for purchase {purchase_id}")
                    continue

                # Calculate new amount_paid
                current_amount_paid = purchase.amount_paid or Decimal('0')
                new_amount_paid = current_amount_paid + amount

                # Check if payment doesn't exceed total amount
                if new_amount_paid > purchase.total_amount:
                    errors.append(f"Payment for purchase {purchase_id} would exceed remaining balance")
                    continue

                # Update purchase amount_paid
                purchase.amount_paid = new_amount_paid

                # Update status based on payment
                if new_amount_paid >= purchase.total_amount:
                    purchase.status = 'paid'
                elif new_amount_paid > 0:
                    purchase.status = 'partially_paid'

                results.append({
                    "purchase_id": purchase_id,
                    "payment_amount": float(amount),
                    "total_paid": float(new_amount_paid),
                    "remaining_balance": float(purchase.total_amount - new_amount_paid),
                    "status": purchase.status
                })

            except Exception as payment_error:
                errors.append(f"Error processing payment for purchase {payment.get('purchase_id', 'unknown')}: {str(payment_error)}")

        db.commit()

        # Best-effort: post payment journal entries for each successfully processed purchase
        bulk_payment_account_id = (
            common_data.get('bank_account_id') or
            common_data.get('payment_source_id') or
            None
        )
        if bulk_payment_account_id and results:
            from app.services.ifrs_accounting_service import IFRSAccountingService
            from app.models.banking import BankAccount as _BA
            from datetime import date as _date
            _ifrs = IFRSAccountingService(db)
            _pay_date_str = common_data.get('payment_date') or str(_date.today())
            try:
                _pay_date = _date.fromisoformat(_pay_date_str)
            except Exception:
                _pay_date = _date.today()

            for r in results:
                try:
                    pur = db.query(Purchase).filter(Purchase.id == r['purchase_id']).first()
                    if pur:
                        _ifrs.create_purchase_payment_journal_entries(
                            purchase=pur,
                            payment_amount=Decimal(str(r['payment_amount'])),
                            payment_date=_pay_date,
                            bank_account_id=bulk_payment_account_id
                        )
                except Exception as _je:
                    print(f"[bulk-payment] journals not created for {r.get('purchase_id')}: {_je}")

            # Update BankAccount.balance for the paying account
            try:
                _ba = db.query(_BA).filter(_BA.id == bulk_payment_account_id).first()
                if not _ba:
                    _ba = db.query(_BA).filter(_BA.accounting_code_id == bulk_payment_account_id).first()
                if _ba:
                    _total = sum(Decimal(str(r['payment_amount'])) for r in results)
                    _ba.balance = (_ba.balance or 0) - _total
                    db.commit()
            except Exception:
                pass

        return {
            "message": f"Processed {len(results)} payments successfully",
            "successful_payments": results,
            "errors": errors,
            "total_processed": len(results),
            "total_errors": len(errors)
        }

    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=f"Error processing bulk payments: {str(e)}")


@router.get("/{purchase_id}/serials")
async def get_purchase_serials(purchase_id: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
    """Return all serial numbers linked to a purchase, grouped by purchase item."""
    from app.models.inventory import SerialNumber as SNModel
    rows = db.query(SNModel).filter(SNModel.purchase_id == purchase_id).all()
    grouped: dict = {}
    for sn in rows:
        key = sn.purchase_item_id or sn.product_id  # legacy rows lack purchase_item_id
        grouped.setdefault(key, []).append({
            "serial": sn.serial,
            "status": sn.status,
            "batch_number": sn.batch_number,
            "branch_id": sn.branch_id,
            "purchase_item_id": sn.purchase_item_id,
            "product_id": sn.product_id,
        })
    return {"purchase_id": purchase_id, "serials_by_item": grouped, "total": len(rows)}


@router.get("/{purchase_id}/payment-history")
async def get_payment_history(purchase_id: str, db: Session = Depends(get_db)):
    """Get payment history for a purchase"""
    try:
        purchase = db.query(Purchase).filter(Purchase.id == purchase_id).first()
        if not purchase:
            raise HTTPException(status_code=404, detail="Purchase not found")

        # For now, return basic payment info
        # In a full implementation, you'd have a separate payments table
        return {
            "purchase_id": purchase_id,
            "total_amount": float(purchase.total_amount),
            "amount_paid": float(purchase.amount_paid or 0),
            "remaining_balance": float(purchase.total_amount - (purchase.amount_paid or 0)),
            "status": purchase.status,
            "payments": [
                # This would be populated from a payments table
                # For now, showing summary only
            ]
        }

    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error fetching payment history: {str(e)}")


@router.get("/purchase-items/{item_id}", response_model=PurchaseItemResponse)
async def get_purchase_item(item_id: str, db: Session = Depends(get_db)):
    """Fetch a single purchase item (includes extended asset metadata)."""
    db_item = db.query(PurchaseItem).filter(PurchaseItem.id == item_id).first()
    if not db_item:
        raise HTTPException(status_code=404, detail="Purchase item not found")
    return db_item


@router.put("/purchase-items/{item_id}", response_model=PurchaseItemResponse)
async def update_purchase_item(item_id: str, item_update: PurchaseItemUpdate, db: Session = Depends(get_db)):
    """Update a single purchase item including extended asset metadata and return full record."""
    try:
        db_item = db.query(PurchaseItem).filter(PurchaseItem.id == item_id).first()
        if not db_item:
            raise HTTPException(status_code=404, detail="Purchase item not found")

        update_data = item_update.dict(exclude_unset=True)
        for field, value in update_data.items():
            setattr(db_item, field, value)

        db.commit()
        db.refresh(db_item)
        return db_item
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=f"Error updating purchase item: {str(e)}")


@router.delete("/{purchase_id}")
async def delete_purchase(purchase_id: str, db: Session = Depends(get_db)):
    """Delete a purchase"""
    try:
        purchase = db.query(Purchase).filter(Purchase.id == purchase_id).first()
        if not purchase:
            raise HTTPException(status_code=404, detail="Purchase not found")

        # Business rule: prevent deleting purchases that have been received or have payments
        protected_statuses = {"received", "partially_paid", "paid"}
        if (purchase.status in protected_statuses) or (purchase.amount_paid and purchase.amount_paid > 0):
            raise HTTPException(
                status_code=409,
                detail="Cannot delete a purchase that is received or has payments. Reverse receipts/payments first."
            )

        print(f"Deleting purchase {purchase_id} and related records...")

        # Delete related purchase items first
        purchase_items = db.query(PurchaseItem).filter(PurchaseItem.purchase_id == purchase_id).all()
        print(f"Found {len(purchase_items)} purchase items to delete")
        for item in purchase_items:
            db.delete(item)

        # Delete related inventory transactions
        from app.models.inventory import InventoryTransaction
        inventory_transactions = db.query(InventoryTransaction).filter(
            InventoryTransaction.related_purchase_id == purchase_id
        ).all()
        print(f"Found {len(inventory_transactions)} inventory transactions to delete")
        for transaction in inventory_transactions:
            db.delete(transaction)

        # Delete related journal entries (if any reference this purchase)
        from app.models.accounting import JournalEntry, AccountingEntry
        journal_entries = db.query(JournalEntry).filter(
            (JournalEntry.purchase_id == purchase_id) | (JournalEntry.reference == purchase_id)
        ).all()
        print(f"Found {len(journal_entries)} journal entries to delete")
        for entry in journal_entries:
            db.delete(entry)

        # Also delete the accounting entry header if it exists and has no other JEs
        accounting_entries = db.query(AccountingEntry).filter(
            AccountingEntry.book == f"PURCHASE-{purchase_id}"
        ).all()
        print(f"Found {len(accounting_entries)} accounting entry headers to delete")
        for ae in accounting_entries:
            db.delete(ae)

        # Delete related payments if any lingering zero/empty records exist (defensive)
        try:
            from app.models.purchase_payments import PurchasePayment
            payments = db.query(PurchasePayment).filter(PurchasePayment.purchase_id == purchase_id).all()
            for p in payments:
                db.delete(p)
        except Exception:
            # Do not fail deletion if payments model/records are unavailable
            pass

        # Now delete the purchase
        db.delete(purchase)
        db.commit()
        print(f"Successfully deleted purchase {purchase_id}")
        return {"message": "Purchase deleted successfully"}
    except Exception as e:
        db.rollback()
        print(f"Error deleting purchase {purchase_id}: {str(e)}")
        raise HTTPException(status_code=500, detail=f"Error deleting purchase: {str(e)}")


@router.get("/orders", response_model=List[dict])
async def get_purchase_orders(
    skip: int = 0,
    limit: int = 100,
    supplier_id: Optional[str] = None,
    status: Optional[str] = None,
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """Get purchase orders with optional filtering"""
    try:
        query = db.query(PurchaseOrder)

        if company_id:
            branch_ids_sq = db.query(Branch.id).filter(Branch.company_id == company_id)
            query = query.filter(PurchaseOrder.branch_id.in_(branch_ids_sq))

        if supplier_id:
            query = query.filter(PurchaseOrder.supplier_id == supplier_id)

        if status:
            query = query.filter(PurchaseOrder.status == status)

        orders = query.order_by(desc(PurchaseOrder.date)).offset(skip).limit(limit).all()

        # Format response with supplier details
        result = []
        for order in orders:
            result.append({
                "id": order.id,
                "po_number": order.po_number,
                "date": order.date,
                "supplier_name": order.supplier.name if order.supplier else "Unknown",
                "status": order.status,
                "total_amount": float(order.total_amount) if order.total_amount else 0.0,
                "expected_delivery_date": order.expected_delivery_date,
                "notes": order.notes
            })

        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error fetching purchase orders: {str(e)}")


@router.get("/orders/{order_id}", response_model=dict)
async def get_purchase_order_detail(order_id: str, db: Session = Depends(get_db)):
    """Get a single purchase order with its line items."""
    order = db.query(PurchaseOrder).filter(PurchaseOrder.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Purchase order not found")
    items = []
    for item in (order.purchase_order_items or []):
        product_name = item.product.name if item.product else (item.description or "Unknown")
        product_sku  = item.product.sku  if item.product else ""
        items.append({
            "id": item.id,
            "product_id": item.product_id,
            "product_name": product_name,
            "product_sku": product_sku,
            "description": item.description,
            "quantity": float(item.quantity or 0),
            "unit_cost": float(item.unit_cost or 0),
            "total_cost": float(item.total_cost or 0),
            "vat_rate": float(item.vat_rate or 0),
            "vat_amount": float(item.vat_amount or 0),
        })
    return {
        "id": order.id,
        "po_number": order.po_number,
        "date": str(order.date) if order.date else None,
        "due_date": str(order.expected_delivery_date) if order.expected_delivery_date else None,
        "supplier_id": order.supplier_id,
        "supplier_name": order.supplier.name if order.supplier else "Unknown",
        "status": order.status,
        "total_amount": float(order.total_amount or 0),
        "total_vat_amount": float(order.total_vat_amount or 0),
        "notes": order.notes,
        "items": items,
    }


@router.patch("/orders/{order_id}/status", response_model=dict)
async def update_purchase_order_status(order_id: str, payload: dict, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
    """Update the status of a purchase order (e.g. draft → approved)."""
    order = db.query(PurchaseOrder).filter(PurchaseOrder.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Purchase order not found")
    allowed = {"draft", "pending", "approved", "ordered", "received", "cancelled"}
    new_status = payload.get("status", "").lower()
    if new_status not in allowed:
        raise HTTPException(status_code=400, detail=f"Invalid status. Allowed: {', '.join(sorted(allowed))}")
    order.status = new_status
    db.commit()
    db.refresh(order)
    return {"id": order.id, "status": order.status}


@router.post("/orders", response_model=dict)
async def create_purchase_order(order_data: dict, db: Session = Depends(get_db)):
    """Create a new purchase order with optional line items."""
    try:
        from decimal import Decimal as _D
        import re as _re

        supplier_id = order_data.get("supplier_id")
        if not supplier_id:
            raise HTTPException(status_code=400, detail="supplier_id is required")

        supplier = db.query(Supplier).filter(Supplier.id == supplier_id).first()
        if not supplier:
            raise HTTPException(status_code=404, detail="Supplier not found")

        # Auto-generate PO number if not provided
        po_number = order_data.get("po_number") or None
        if not po_number:
            from datetime import datetime as _dt
            last_po = db.query(PurchaseOrder).order_by(desc(PurchaseOrder.date)).first()
            seq = 1
            if last_po and last_po.po_number:
                m = _re.search(r'(\d+)$', last_po.po_number)
                if m:
                    seq = int(m.group(1)) + 1
            po_number = f"PO-{_dt.now().strftime('%Y%m')}-{seq:04d}"

        # Parse dates
        from datetime import date as _date
        def _parse_date(v):
            if not v:
                return None
            if isinstance(v, _date):
                return v
            try:
                return _date.fromisoformat(str(v)[:10])
            except Exception:
                return None

        po_date = _parse_date(order_data.get("date")) or _date.today()
        due_date = _parse_date(order_data.get("due_date"))

        # Compute totals from items
        items_data = order_data.get("items") or []
        total_amount = _D("0.00")
        total_vat = _D("0.00")

        new_order = PurchaseOrder(
            supplier_id=supplier_id,
            date=po_date,
            expected_delivery_date=due_date,
            po_number=po_number,
            status=order_data.get("status", "draft"),
            notes=order_data.get("notes"),
            total_amount=_D("0.00"),
            total_vat_amount=_D("0.00"),
        )
        db.add(new_order)
        db.flush()  # get new_order.id

        for item in items_data:
            product_id = item.get("product_id")
            if not product_id:
                continue
            qty = _D(str(item.get("quantity") or 1))
            unit_cost = _D(str(item.get("cost") or 0))
            vat_rate = _D(str(item.get("vat_rate") or 0))
            line_total = qty * unit_cost
            vat_amt = line_total * (vat_rate / _D("100"))

            total_amount += line_total
            total_vat += vat_amt

            poi = PurchaseOrderItem(
                purchase_order_id=new_order.id,
                product_id=product_id,
                quantity=qty,
                unit_cost=unit_cost,
                total_cost=line_total + vat_amt,
                vat_rate=vat_rate,
                vat_amount=vat_amt,
                description=item.get("description"),
            )
            db.add(poi)

        new_order.total_amount = total_amount + total_vat
        new_order.total_vat_amount = total_vat
        db.commit()
        db.refresh(new_order)

        return {
            "id": new_order.id,
            "po_number": new_order.po_number,
            "supplier_id": new_order.supplier_id,
            "date": str(new_order.date),
            "status": new_order.status,
            "total_amount": float(new_order.total_amount or 0),
            "total_vat_amount": float(new_order.total_vat_amount or 0),
        }
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=f"Error creating purchase order: {str(e)}")


@router.get("/products")
async def get_products_for_purchase(db: Session = Depends(get_db)):
    """Get products available for purchasing"""
    try:
        products = db.query(Product).filter(Product.active == True).all()
        return [
            {
                "id": product.id,
                "name": product.name,
                "sku": product.sku,
                "description": product.description,
                "cost_price": float(product.cost_price) if product.cost_price else 0.0,
                "current_cost": float(product.cost_price) if product.cost_price else 0.0,
                "selling_price": float(product.selling_price) if product.selling_price else 0.0,
                "stock_quantity": float(product.quantity) if product.quantity else 0.0,
                "unit_of_measure": product.unit_of_measure_id,
                "is_serialized": bool(getattr(product, "is_serialized", False)),
            }
            for product in products
        ]
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error fetching products: {str(e)}")


@router.get("/dashboard-stats")
async def get_purchase_dashboard_stats(
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """Get purchase dashboard statistics"""
    try:
        from sqlalchemy import func
        from datetime import datetime, timedelta

        today = datetime.now().date()
        first_day_of_month = today.replace(day=1)

        _branch_filter = []
        if company_id:
            _branch_ids_sq = db.query(Branch.id).filter(Branch.company_id == company_id).subquery()
            _branch_filter = [Purchase.branch_id.in_(_branch_ids_sq)]
            _sup_branch_filter = [Supplier.branch_id.in_(_branch_ids_sq)]
        else:
            _sup_branch_filter = []

        # Total purchases this month
        total_this_month = db.query(func.sum(Purchase.total_amount)).filter(
            Purchase.purchase_date >= first_day_of_month, *_branch_filter
        ).scalar() or 0

        # Overall totals for compatibility with UI cards
        total_purchases_count = db.query(func.count(Purchase.id)).filter(*_branch_filter).scalar() or 0
        total_amount_sum = db.query(func.sum(Purchase.total_amount)).filter(*_branch_filter).scalar() or 0

        # Pending purchases count
        pending_count = db.query(func.count(Purchase.id)).filter(
            Purchase.status == "pending", *_branch_filter
        ).scalar() or 0

        # Active suppliers count
        suppliers_count = db.query(func.count(Supplier.id)).filter(
            Supplier.active == True, *_sup_branch_filter
        ).scalar() or 0

        # Recent purchases
        recent_purchases = db.query(Purchase).filter(*_branch_filter).order_by(desc(Purchase.purchase_date)).limit(5).all()

        recent_data = []
        for purchase in recent_purchases:
            recent_data.append({
                "id": purchase.id,
                "date": purchase.purchase_date,
                "supplier_name": purchase.supplier.name if purchase.supplier else "Unknown",
                "amount": float(purchase.total_amount) if purchase.total_amount else 0.0,
                "status": purchase.status
            })



        # Preserve existing keys and add UI-expected aliases
        return {
            # Existing keys
            "total_this_month": float(total_this_month),
            "pending_purchases": int(pending_count),
            "active_suppliers": int(suppliers_count),
            "recent_purchases": recent_data,

            # UI-expected keys for purchases.html cards
            "total_purchases": int(total_purchases_count),
            "total_amount": float(total_amount_sum),
            "pending_orders": int(pending_count),
            "this_month": float(total_this_month),
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error fetching dashboard stats: {str(e)}")


# Dimensional Accounting GL Posting Endpoints ==============================

from pydantic import BaseModel
from app.utils.logger import get_logger, log_exception, log_error_with_context

logger = get_logger(__name__)
class GLPostingResponse(BaseModel):
    """Response for GL posting operations"""
    success: bool
    purchase_id: str
    entries_created: int
    journal_entry_ids: List[str]
    total_amount: float
    posting_date: str


class DimensionAccountingDetailsResponse(BaseModel):
    """Purchase accounting dimension details"""
    purchase_id: str
    reference: Optional[str] = None
    total_amount: float
    cost_center: Optional[str] = None
    project: Optional[str] = None
    department: Optional[str] = None
    expense_account: Optional[str] = None
    payable_account: Optional[str] = None
    posting_status: str
    last_posted_date: Optional[str] = None


class JournalEntryResponse(BaseModel):
    """Journal entry response"""
    id: str
    accounting_code: str
    debit_amount: float
    credit_amount: float
    description: str
    source: str
    entry_date: str
    dimensions: List[Dict] = []


class DimensionalAnalysisResponse(BaseModel):
    """Dimensional analysis for purchases"""
    total_expenses: float
    by_cost_center: Dict[str, float]
    by_project: Dict[str, float]
    by_department: Dict[str, float]


class ReconciliationResponse(BaseModel):
    """Reconciliation result"""
    period: str
    purchase_total: float
    gl_total: float
    variance: float
    is_reconciled: bool
    by_dimension: List[Dict] = []


@router.post("/{purchase_id}/post-accounting", response_model=GLPostingResponse, tags=["accounting"])
def post_purchase_to_accounting(
    purchase_id: str,
    user_id: Optional[str] = None,
    db: Session = Depends(get_db)
):
    """
    Post purchase to General Ledger with dimensional assignments.
    Creates Expense Debit and AP Credit entries with dimension tracking.
    """
    try:
        service = PurchaseService(db)
        result = service.post_purchase_to_accounting(purchase_id, user_id)
        return result
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error posting to accounting: {str(e)}")


@router.get("/{purchase_id}/accounting-details", response_model=DimensionAccountingDetailsResponse, tags=["accounting"])
def get_purchase_accounting_details(
    purchase_id: str,
    db: Session = Depends(get_db)
):
    """
    Get detailed accounting dimension information for a purchase.
    Shows cost center, project, department assignments and GL accounts.
    """
    try:
        purchase = db.query(Purchase).filter(Purchase.id == purchase_id).first()
        if not purchase:
            raise HTTPException(status_code=404, detail="Purchase not found")

        # Get dimension names
        cost_center_name = None
        project_name = None
        department_name = None

        if purchase.cost_center_id:
            cc = db.query(AccountingDimensionValue).filter(AccountingDimensionValue.id == purchase.cost_center_id).first()
            cost_center_name = cc.value if cc else None

        if purchase.project_id:
            proj = db.query(AccountingDimensionValue).filter(AccountingDimensionValue.id == purchase.project_id).first()
            project_name = proj.value if proj else None

        if purchase.department_id:
            dept = db.query(AccountingDimensionValue).filter(AccountingDimensionValue.id == purchase.department_id).first()
            department_name = dept.value if dept else None

        return {
            'purchase_id': purchase.id,
            'reference': purchase.reference or '',
            'total_amount': float(purchase.total_amount or 0),
            'cost_center': cost_center_name,
            'project': project_name,
            'department': department_name,
            'expense_account': purchase.expense_account.code if purchase.expense_account else None,
            'payable_account': purchase.payable_account.code if purchase.payable_account else None,
            'posting_status': purchase.posting_status or 'draft',
            'last_posted_date': purchase.last_posted_date.isoformat() if purchase.last_posted_date else None
        }
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@router.get("/accounting-bridge", response_model=List[Dict], tags=["accounting"])
def get_purchases_accounting_bridge(
    start_date: Optional[date] = Query(None),
    end_date: Optional[date] = Query(None),
    posting_status: Optional[str] = Query(None),
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """
    Get the bridge table showing purchase-to-GL entry mappings.
    Useful for auditing and reconciliation.
    """
    try:
        query = db.query(Purchase)

        if company_id:
            branch_ids_sq = db.query(Branch.id).filter(Branch.company_id == company_id)
            query = query.filter(Purchase.branch_id.in_(branch_ids_sq))

        if start_date:
            query = query.filter(Purchase.purchase_date >= start_date)
        if end_date:
            query = query.filter(Purchase.purchase_date <= end_date)
        if posting_status:
            query = query.filter(Purchase.posting_status == posting_status)

        purchases = query.all()

        bridge_data = []
        for purchase in purchases:
            # Get related journal entries
            journal_entries = db.query(JournalEntry).filter(
                JournalEntry.reference.like(f"%PURCHASE-{purchase.id}%")
            ).all()

            bridge_data.append({
                'purchase_id': purchase.id,
                'reference': purchase.reference or '',
                'purchase_date': purchase.purchase_date.isoformat() if purchase.purchase_date else None,
                'total_amount': float(purchase.total_amount or 0),
                'posting_status': purchase.posting_status or 'draft',
                'journal_entry_count': len(journal_entries),
                'journal_entry_ids': [je.id for je in journal_entries],
                'cost_center_id': purchase.cost_center_id,
                'project_id': purchase.project_id,
                'department_id': purchase.department_id
            })

        return bridge_data
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@router.get("/journal-entries", response_model=List[JournalEntryResponse], tags=["accounting"])
def get_purchases_journal_entries(
    start_date: Optional[date] = Query(None),
    end_date: Optional[date] = Query(None),
    source: str = Query("PURCHASES"),
    db: Session = Depends(get_db)
):
    """
    Get all journal entries for purchase transactions.
    Shows debit/credit entries with dimension assignments.
    Optimized with eager loading and name fields instead of UUIDs.
    """
    try:
        # Add eager loading for all relationships
        query = db.query(JournalEntry).options(
            joinedload(JournalEntry.accounting_code),
            joinedload(JournalEntry.accounting_entry),
            joinedload(JournalEntry.branch),
            joinedload(JournalEntry.ledger),
            joinedload(JournalEntry.dimension_assignments)
        ).filter(JournalEntry.source == source)

        if start_date:
            query = query.filter(JournalEntry.entry_date >= start_date)
        if end_date:
            query = query.filter(JournalEntry.entry_date <= end_date)

        entries = query.all()

        result = []
        for entry in entries:
            dimensions = []
            if entry.dimension_assignments:
                for dim_assign in entry.dimension_assignments:
                    dimensions.append({
                        'dimension_value_id': dim_assign.dimension_value_id,
                        'dimension_type': dim_assign.dimension_value.dimension.code if dim_assign.dimension_value and dim_assign.dimension_value.dimension else None,
                        'dimension_value': dim_assign.dimension_value.value if dim_assign.dimension_value else None
                    })

            result.append({
                'id': entry.id,
                'accounting_code': entry.accounting_code.code if entry.accounting_code else None,
                'accounting_code_name': entry.accounting_code.name if entry.accounting_code else None,
                'accounting_entry_id': entry.accounting_entry_id,
                'accounting_entry_particulars': entry.accounting_entry.particulars if entry.accounting_entry else None,
                'branch_id': entry.branch_id,
                'branch_name': entry.branch.name if entry.branch else None,
                'ledger_id': entry.ledger_id,
                'ledger_description': entry.ledger.description if entry.ledger else None,
                'debit_amount': float(entry.debit_amount or 0),
                'credit_amount': float(entry.credit_amount or 0),
                'description': entry.description or '',
                'source': entry.source or '',
                'entry_date': entry.entry_date.isoformat() if entry.entry_date else None,
                'dimensions': dimensions
            })

        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@router.get("/dimensional-analysis", response_model=DimensionalAnalysisResponse, tags=["accounting"])
def get_purchases_dimensional_analysis(
    start_date: Optional[date] = Query(None),
    end_date: Optional[date] = Query(None),
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """
    Analyze purchase expenses by accounting dimensions.
    Groups expenses by cost center, project, and department.
    """
    try:
        query = db.query(Purchase)

        if company_id:
            branch_ids_sq = db.query(Branch.id).filter(Branch.company_id == company_id)
            query = query.filter(Purchase.branch_id.in_(branch_ids_sq))

        if start_date:
            query = query.filter(Purchase.purchase_date >= start_date)
        if end_date:
            query = query.filter(Purchase.purchase_date <= end_date)

        purchases = query.all()

        total_expenses = Decimal('0')
        by_cost_center = {}
        by_project = {}
        by_department = {}

        for purchase in purchases:
            amount = Decimal(str(purchase.total_amount or 0))
            total_expenses += amount

            # Group by cost center
            if purchase.cost_center_id:
                cc = db.query(AccountingDimensionValue).filter(AccountingDimensionValue.id == purchase.cost_center_id).first()
                cc_name = cc.value if cc else 'Unknown'
                by_cost_center[cc_name] = float(by_cost_center.get(cc_name, Decimal('0')) + amount)

            # Group by project
            if purchase.project_id:
                proj = db.query(AccountingDimensionValue).filter(AccountingDimensionValue.id == purchase.project_id).first()
                proj_name = proj.value if proj else 'Unknown'
                by_project[proj_name] = float(by_project.get(proj_name, Decimal('0')) + amount)

            # Group by department
            if purchase.department_id:
                dept = db.query(AccountingDimensionValue).filter(AccountingDimensionValue.id == purchase.department_id).first()
                dept_name = dept.value if dept else 'Unknown'
                by_department[dept_name] = float(by_department.get(dept_name, Decimal('0')) + amount)

        return {
            'total_expenses': float(total_expenses),
            'by_cost_center': by_cost_center,
            'by_project': by_project,
            'by_department': by_department
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@router.get("/reconcile", response_model=ReconciliationResponse, tags=["accounting"])
def reconcile_purchases(
    period: str = Query(..., description="Period in YYYY-MM format"),
    db: Session = Depends(get_db)
):
    """
    Reconcile purchases against GL entries by dimension.
    Returns variance analysis to identify discrepancies.
    """
    try:
        service = PurchaseService(db)
        result = service.reconcile_purchases_by_dimension(period)
        return result
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))
