# ================================================================
# services/procurement/app/services/purchase_order_service.py
# ================================================================
"""
Purchase Order Service - Business logic for purchase order management
Migrated from Suppliers Service to Procurement Service ownership
"""

import uuid
from datetime import datetime, date, timedelta
from decimal import Decimal
from typing import List, Optional, Dict, Any
import structlog
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.purchase_order import PurchaseOrder, PurchaseOrderItem, Delivery, DeliveryItem, SupplierInvoice
from app.repositories.purchase_order_repository import (
    PurchaseOrderRepository,
    PurchaseOrderItemRepository,
    DeliveryRepository,
    SupplierInvoiceRepository
)
from app.schemas.purchase_order_schemas import (
    PurchaseOrderCreate,
    PurchaseOrderUpdate,
    PurchaseOrderResponse,
    DeliveryCreate,
    DeliveryUpdate,
    SupplierInvoiceCreate,
)
from app.core.config import settings
from shared.clients.suppliers_client import SuppliersServiceClient
from shared.clients.inventory_client import InventoryServiceClient
from shared.config.base import BaseServiceSettings
from shared.messaging import RabbitMQClient, UnifiedEventPublisher, EVENT_TYPES
from shared.redis_utils import get_keys_pattern, get_redis_client

logger = structlog.get_logger()


class PurchaseOrderService:
    """Service for purchase order management operations"""

    def __init__(
        self,
        db: AsyncSession,
        config: BaseServiceSettings,
        suppliers_client: Optional[SuppliersServiceClient] = None,
        rabbitmq_client: Optional[RabbitMQClient] = None,
        event_publisher: Optional[UnifiedEventPublisher] = None,
        inventory_client: Optional[InventoryServiceClient] = None
    ):
        self.db = db
        self.config = config
        self.po_repo = PurchaseOrderRepository(db)
        self.item_repo = PurchaseOrderItemRepository(db)
        self.delivery_repo = DeliveryRepository(db)
        self.invoice_repo = SupplierInvoiceRepository(db)

        # Initialize suppliers client for supplier validation
        self.suppliers_client = suppliers_client or SuppliersServiceClient(config)

        # Initialize inventory client for stock information
        self.inventory_client = inventory_client or InventoryServiceClient(config)

        # Initialize event publisher for RabbitMQ events
        self.rabbitmq_client = rabbitmq_client
        self.event_publisher = event_publisher or UnifiedEventPublisher(rabbitmq_client, "procurement")

        # Request-scoped cache for supplier data to avoid redundant API calls
        # When enriching multiple POs with the same supplier, cache prevents duplicate calls
        self._supplier_cache: Dict[str, Dict[str, Any]] = {}

    # ================================================================
    # PURCHASE ORDER CRUD
    # ================================================================

    async def create_purchase_order(
        self,
        tenant_id: uuid.UUID,
        po_data: PurchaseOrderCreate,
        created_by: Optional[uuid.UUID] = None
    ) -> PurchaseOrder:
        """
        Create a new purchase order with items

        Flow:
        1. Validate supplier exists and is active
        2. Generate PO number
        3. Calculate totals
        4. Determine approval requirements
        5. Create PO and items
        6. Link to procurement plan if provided
        """
        try:
            logger.info("Creating purchase order",
                       tenant_id=tenant_id,
                       supplier_id=po_data.supplier_id)

            # Validate supplier
            supplier = await self._get_and_validate_supplier(tenant_id, po_data.supplier_id)

            # Generate PO number
            po_number = await self.po_repo.generate_po_number(tenant_id)

            # Calculate totals
            subtotal = po_data.subtotal
            total_amount = (
                subtotal +
                po_data.tax_amount +
                po_data.shipping_cost -
                po_data.discount_amount
            )

            # Determine approval requirements
            requires_approval = self._requires_approval(total_amount, po_data.priority)
            initial_status = self._determine_initial_status(total_amount, requires_approval)

            # Set delivery date if not provided
            required_delivery_date = po_data.required_delivery_date
            estimated_delivery_date = date.today() + timedelta(days=supplier.get('standard_lead_time', 7))

            # Create PO
            po_create_data = {
                'tenant_id': tenant_id,
                'supplier_id': po_data.supplier_id,
                'po_number': po_number,
                'status': initial_status,
                'priority': po_data.priority,
                'order_date': datetime.utcnow(),
                'required_delivery_date': required_delivery_date,
                'estimated_delivery_date': estimated_delivery_date,
                'subtotal': subtotal,
                'tax_amount': po_data.tax_amount,
                'shipping_cost': po_data.shipping_cost,
                'discount_amount': po_data.discount_amount,
                'total_amount': total_amount,
                'currency': supplier.get('currency', 'EUR'),
                'requires_approval': requires_approval,
                'notes': po_data.notes,
                'procurement_plan_id': po_data.procurement_plan_id,
                'created_by': created_by,
                'updated_by': created_by,
                'created_at': datetime.utcnow(),
                'updated_at': datetime.utcnow(),
            }

            purchase_order = await self.po_repo.create_po(po_create_data)

            # Create PO items
            for item_data in po_data.items:
                item_create_data = {
                    'tenant_id': tenant_id,
                    'purchase_order_id': purchase_order.id,
                    'inventory_product_id': item_data.inventory_product_id,
                    'ordered_quantity': item_data.ordered_quantity,
                    'unit_price': item_data.unit_price,
                    'unit_of_measure': item_data.unit_of_measure,
                    'line_total': item_data.ordered_quantity * item_data.unit_price,
                    'received_quantity': Decimal('0'),
                    'quality_requirements': item_data.quality_requirements,
                    'item_notes': item_data.item_notes,
                    'created_at': datetime.utcnow(),
                    'updated_at': datetime.utcnow(),
                }

                await self.item_repo.create_item(item_create_data)

            await self.db.commit()

            logger.info("Purchase order created successfully",
                       tenant_id=tenant_id,
                       po_id=purchase_order.id,
                       po_number=po_number,
                       total_amount=float(total_amount))

            # Emit alert if PO requires approval
            if initial_status == 'pending_approval':
                try:
                    await self._emit_po_approval_alert(
                        tenant_id=tenant_id,
                        purchase_order=purchase_order,
                        supplier=supplier
                    )
                except Exception as alert_error:
                    # Log but don't fail PO creation if alert emission fails
                    logger.warning(
                        "Failed to emit PO approval alert",
                        po_id=str(purchase_order.id),
                        error=str(alert_error)
                    )

            return purchase_order

        except Exception as e:
            await self.db.rollback()
            logger.error("Error creating purchase order", error=str(e), tenant_id=tenant_id)
            raise

    async def get_purchase_order(
        self,
        tenant_id: uuid.UUID,
        po_id: uuid.UUID
    ) -> Optional[PurchaseOrder]:
        """Get purchase order by ID with items"""
        try:
            po = await self.po_repo.get_po_by_id(po_id, tenant_id)
            if po:
                # Enrich with supplier information
                await self._enrich_po_with_supplier(tenant_id, po)
            return po
        except Exception as e:
            logger.error("Error getting purchase order", error=str(e), po_id=po_id)
            return None

    async def list_purchase_orders(
        self,
        tenant_id: uuid.UUID,
        skip: int = 0,
        limit: int = 50,
        supplier_id: Optional[uuid.UUID] = None,
        status: Optional[str] = None,
        enrich_supplier: bool = True
    ) -> List[PurchaseOrder]:
        """
        List purchase orders with filters

        Args:
            tenant_id: Tenant UUID
            skip: Number of records to skip
            limit: Maximum number of records
            supplier_id: Optional supplier filter
            status: Optional status filter
            enrich_supplier: Whether to fetch and attach supplier details (default: True)
                            Set to False for faster queries when supplier data isn't needed

        Returns:
            List of purchase orders
        """
        try:
            # Convert status string to enum if provided
            status_enum = None
            if status:
                try:
                    from app.models.purchase_order import PurchaseOrderStatus
                    # Convert from UPPERCASE to lowercase for enum lookup
                    status_enum = PurchaseOrderStatus[status.lower()]
                except (KeyError, AttributeError):
                    logger.warning("Invalid status value provided", status=status)
                    status_enum = None

            pos = await self.po_repo.list_purchase_orders(
                tenant_id=tenant_id,
                offset=skip,  # Repository uses 'offset' parameter
                limit=limit,
                supplier_id=supplier_id,
                status=status_enum
            )

            # Only enrich with supplier information if requested
            # When enrich_supplier=False, returns POs with just supplier_id for client-side matching
            if pos and enrich_supplier:
                import asyncio
                # Enrich with supplier information in parallel (Fix #9: Avoid N+1 query pattern)
                # This fetches all supplier data concurrently instead of sequentially
                enrichment_tasks = [self._enrich_po_with_supplier(tenant_id, po) for po in pos]
                await asyncio.gather(*enrichment_tasks, return_exceptions=True)

            return pos
        except Exception as e:
            logger.error("Error listing purchase orders", error=str(e), tenant_id=tenant_id)
            return []

    async def update_po(
        self,
        tenant_id: uuid.UUID,
        po_id: uuid.UUID,
        po_data: PurchaseOrderUpdate,
        updated_by: Optional[uuid.UUID] = None
    ) -> Optional[PurchaseOrder]:
        """Update purchase order information"""
        try:
            logger.info("Updating purchase order", po_id=po_id)

            po = await self.po_repo.get_po_by_id(po_id, tenant_id)
            if not po:
                return None

            # Check if order can be modified
            if po.status in ['completed', 'cancelled']:
                raise ValueError("Cannot modify completed or cancelled orders")

            # Prepare update data
            update_data = po_data.model_dump(exclude_unset=True)
            update_data['updated_by'] = updated_by
            update_data['updated_at'] = datetime.utcnow()

            # Recalculate totals if financial fields changed
            if any(key in update_data for key in ['tax_amount', 'shipping_cost', 'discount_amount']):
                total_amount = (
                    po.subtotal +
                    update_data.get('tax_amount', po.tax_amount) +
                    update_data.get('shipping_cost', po.shipping_cost) -
                    update_data.get('discount_amount', po.discount_amount)
                )
                update_data['total_amount'] = total_amount

            po = await self.po_repo.update_po(po_id, tenant_id, update_data)
            await self.db.commit()

            logger.info("Purchase order updated successfully", po_id=po_id)
            return po

        except Exception as e:
            await self.db.rollback()
            logger.error("Error updating purchase order", error=str(e), po_id=po_id)
            raise

    async def update_order_status(
        self,
        tenant_id: uuid.UUID,
        po_id: uuid.UUID,
        status: str,
        updated_by: Optional[uuid.UUID] = None,
        notes: Optional[str] = None
    ) -> Optional[PurchaseOrder]:
        """Update purchase order status"""
        try:
            logger.info("Updating PO status", po_id=po_id, status=status)

            po = await self.po_repo.get_po_by_id(po_id, tenant_id)
            if not po:
                return None

            # Validate status transition
            if not self._is_valid_status_transition(po.status, status):
                raise ValueError(f"Invalid status transition from {po.status} to {status}")

            update_data = {
                'status': status,
                'updated_by': updated_by,
                'updated_at': datetime.utcnow()
            }

            if status == 'sent_to_supplier':
                update_data['sent_to_supplier_at'] = datetime.utcnow()
            elif status == 'confirmed':
                update_data['supplier_confirmation_date'] = datetime.utcnow()

            po = await self.po_repo.update_po(po_id, tenant_id, update_data)
            await self.db.commit()

            return po

        except Exception as e:
            await self.db.rollback()
            logger.error("Error updating PO status", error=str(e), po_id=po_id)
            raise

    async def approve_purchase_order(
        self,
        tenant_id: uuid.UUID,
        po_id: uuid.UUID,
        approved_by: uuid.UUID,
        approval_notes: Optional[str] = None
    ) -> Optional[PurchaseOrder]:
        """Approve a purchase order and publish approval event"""
        try:
            logger.info("Approving purchase order", po_id=po_id)

            po = await self.po_repo.get_po_by_id(po_id, tenant_id)
            if not po:
                return None

            if po.status not in ['draft', 'pending_approval']:
                raise ValueError(f"Cannot approve order with status {po.status}")

            # Get supplier details for event and delivery calculation
            supplier = await self._get_and_validate_supplier(tenant_id, po.supplier_id)

            # Calculate estimated delivery date based on supplier lead time
            approved_at = datetime.utcnow()
            standard_lead_time = supplier.get('standard_lead_time', 7)  # Default 7 days
            estimated_delivery_date = approved_at + timedelta(days=standard_lead_time)

            update_data = {
                'status': 'approved',
                'approved_by': approved_by,
                'approved_at': approved_at,
                'estimated_delivery_date': estimated_delivery_date,
                'updated_by': approved_by,
                'updated_at': approved_at
            }

            po = await self.po_repo.update_po(po_id, tenant_id, update_data)
            await self.db.commit()

            # PHASE 2: Invalidate purchase orders cache
            # Get all purchase order cache keys for this tenant and delete them
            try:
                cache_pattern = f"purchase_orders:{tenant_id}:*"
                client = await get_redis_client()
                if client:
                    keys = await client.keys(cache_pattern)
                    if keys:
                        await client.delete(*keys)
                        logger.debug("Invalidated purchase orders cache", pattern=cache_pattern, keys_deleted=len(keys), tenant_id=str(tenant_id))
            except Exception as e:
                logger.warning("Cache invalidation failed, continuing without cache invalidation", 
                              pattern=f"purchase_orders:{tenant_id}:*", error=str(e))

            # Acknowledge PO approval alerts (non-blocking)
            try:
                from shared.clients.alert_processor_client import get_alert_processor_client
                alert_client = get_alert_processor_client(self.config, "procurement")
                await alert_client.acknowledge_alerts_by_metadata(
                    tenant_id=tenant_id,
                    alert_type="po_approval_needed",
                    metadata_filter={"po_id": str(po_id)}
                )
                logger.debug("Acknowledged PO approval alerts", po_id=po_id)
            except Exception as e:
                # Log but don't fail the approval process
                logger.warning("Failed to acknowledge PO approval alerts", po_id=po_id, error=str(e))

            logger.info("Purchase order approved successfully", po_id=po_id)

            # Publish PO approved event (non-blocking, fire-and-forget)
            try:
                # Get PO items for event
                items = await self.item_repo.get_items_by_po(po_id)
                items_data = [
                    {
                        "inventory_product_id": item.inventory_product_id,
                        "product_name": item.product_name or item.product_code,
                        "ordered_quantity": item.ordered_quantity,
                        "unit_of_measure": item.unit_of_measure,
                        "unit_price": item.unit_price,
                        "line_total": item.line_total
                    }
                    for item in items
                ]

                event_data = {
                    "po_id": str(po_id),
                    "po_number": po.po_number,
                    "supplier_id": str(po.supplier_id),
                    "supplier_name": supplier.get('name', ''),
                    "supplier_email": supplier.get('email'),
                    "supplier_phone": supplier.get('phone'),
                    "total_amount": float(po.total_amount),
                    "currency": po.currency,
                    "required_delivery_date": po.required_delivery_date.isoformat() if po.required_delivery_date else None,
                    "items": items_data,
                    "approved_by": str(approved_by),
                    "approved_at": po.approved_at.isoformat()
                }

                await self.event_publisher.publish_business_event(
                    event_type=EVENT_TYPES.PROCUREMENT.PO_APPROVED,
                    tenant_id=tenant_id,
                    data=event_data
                )
            except Exception as event_error:
                # Log but don't fail the approval if event publishing fails
                logger.warning(
                    "Failed to publish PO approved event",
                    po_id=str(po_id),
                    error=str(event_error)
                )

            return po

        except Exception as e:
            await self.db.rollback()
            logger.error("Error approving purchase order", error=str(e), po_id=po_id)
            raise

    async def reject_purchase_order(
        self,
        tenant_id: uuid.UUID,
        po_id: uuid.UUID,
        rejected_by: uuid.UUID,
        rejection_reason: str
    ) -> Optional[PurchaseOrder]:
        """Reject a purchase order and publish rejection event"""
        try:
            logger.info("Rejecting purchase order", po_id=po_id)

            po = await self.po_repo.get_po_by_id(po_id, tenant_id)
            if not po:
                return None

            if po.status not in ['draft', 'pending_approval']:
                raise ValueError(f"Cannot reject order with status {po.status}")

            # Get supplier details for event
            supplier = await self._get_and_validate_supplier(tenant_id, po.supplier_id)

            update_data = {
                'status': 'rejected',
                'rejection_reason': rejection_reason,
                'updated_by': rejected_by,
                'updated_at': datetime.utcnow()
            }

            po = await self.po_repo.update_po(po_id, tenant_id, update_data)
            await self.db.commit()

            logger.info("Purchase order rejected", po_id=po_id)

            # Publish PO rejected event (non-blocking, fire-and-forget)
            try:
                event_data = {
                    "po_id": str(po_id),
                    "po_number": po.po_number,
                    "supplier_id": str(po.supplier_id),
                    "supplier_name": supplier.get('name', ''),
                    "rejection_reason": rejection_reason,
                    "rejected_by": str(rejected_by),
                    "rejected_at": datetime.utcnow().isoformat()
                }

                await self.event_publisher.publish_business_event(
                    event_type=EVENT_TYPES.PROCUREMENT.PO_REJECTED,
                    tenant_id=tenant_id,
                    data=event_data
                )
            except Exception as event_error:
                # Log but don't fail the rejection if event publishing fails
                logger.warning(
                    "Failed to publish PO rejected event",
                    po_id=str(po_id),
                    error=str(event_error)
                )

            return po

        except Exception as e:
            await self.db.rollback()
            logger.error("Error rejecting purchase order", error=str(e), po_id=po_id)
            raise

    async def cancel_purchase_order(
        self,
        tenant_id: uuid.UUID,
        po_id: uuid.UUID,
        cancelled_by: uuid.UUID,
        cancellation_reason: str
    ) -> Optional[PurchaseOrder]:
        """Cancel a purchase order"""
        try:
            logger.info("Cancelling purchase order", po_id=po_id)

            po = await self.po_repo.get_po_by_id(po_id, tenant_id)
            if not po:
                return None

            if po.status in ['completed', 'cancelled']:
                raise ValueError(f"Cannot cancel order with status {po.status}")

            update_data = {
                'status': 'cancelled',
                'notes': f"{po.notes or ''}\nCancellation: {cancellation_reason}",
                'updated_by': cancelled_by,
                'updated_at': datetime.utcnow()
            }

            po = await self.po_repo.update_po(po_id, tenant_id, update_data)
            await self.db.commit()

            logger.info("Purchase order cancelled", po_id=po_id)
            return po

        except Exception as e:
            await self.db.rollback()
            logger.error("Error cancelling purchase order", error=str(e), po_id=po_id)
            raise

    # ================================================================
    # DELIVERY MANAGEMENT
    # ================================================================

    async def create_delivery(
        self,
        tenant_id: uuid.UUID,
        delivery_data: DeliveryCreate,
        created_by: uuid.UUID
    ) -> Delivery:
        """Create a delivery record for a purchase order"""
        try:
            logger.info("Creating delivery", tenant_id=tenant_id, po_id=delivery_data.purchase_order_id)

            # Validate PO exists
            po = await self.po_repo.get_po_by_id(delivery_data.purchase_order_id, tenant_id)
            if not po:
                raise ValueError("Purchase order not found")

            # Generate delivery number
            delivery_number = await self.delivery_repo.generate_delivery_number(tenant_id)

            # Create delivery
            delivery_create_data = {
                'tenant_id': tenant_id,
                'purchase_order_id': delivery_data.purchase_order_id,
                'supplier_id': delivery_data.supplier_id,
                'delivery_number': delivery_number,
                'supplier_delivery_note': delivery_data.supplier_delivery_note,
                'status': 'scheduled',
                'scheduled_date': delivery_data.scheduled_date,
                'estimated_arrival': delivery_data.estimated_arrival,
                'carrier_name': delivery_data.carrier_name,
                'tracking_number': delivery_data.tracking_number,
                'notes': delivery_data.notes,
                'created_by': created_by,
                'created_at': datetime.utcnow(),
                'updated_at': datetime.utcnow(),
            }

            delivery = await self.delivery_repo.create_delivery(delivery_create_data)

            # Create delivery items
            for item_data in delivery_data.items:
                item_create_data = {
                    'tenant_id': tenant_id,
                    'delivery_id': delivery.id,
                    'purchase_order_item_id': item_data.purchase_order_item_id,
                    'inventory_product_id': item_data.inventory_product_id,
                    'ordered_quantity': item_data.ordered_quantity,
                    'delivered_quantity': item_data.delivered_quantity,
                    'accepted_quantity': item_data.accepted_quantity,
                    'rejected_quantity': item_data.rejected_quantity,
                    'batch_lot_number': item_data.batch_lot_number,
                    'expiry_date': item_data.expiry_date,
                    'quality_grade': item_data.quality_grade,
                    'quality_issues': item_data.quality_issues,
                    'rejection_reason': item_data.rejection_reason,
                    'item_notes': item_data.item_notes,
                    'created_at': datetime.utcnow(),
                    'updated_at': datetime.utcnow(),
                }

                await self.delivery_repo.create_delivery_item(item_create_data)

            await self.db.commit()

            logger.info("Delivery created successfully",
                       tenant_id=tenant_id,
                       delivery_id=delivery.id,
                       delivery_number=delivery_number)

            # Publish delivery received event (non-blocking, fire-and-forget)
            try:
                # Get all delivery items for the event
                items_data = []
                for item_data in delivery_data.items:
                    items_data.append({
                        "inventory_product_id": str(item_data.inventory_product_id),
                        "ordered_quantity": float(item_data.ordered_quantity),
                        "delivered_quantity": float(item_data.delivered_quantity),
                        "accepted_quantity": float(item_data.accepted_quantity),
                        "rejected_quantity": float(item_data.rejected_quantity),
                        "batch_lot_number": item_data.batch_lot_number,
                        "expiry_date": item_data.expiry_date.isoformat() if item_data.expiry_date else None,
                        "quality_grade": item_data.quality_grade,
                        "quality_issues": item_data.quality_issues,
                        "rejection_reason": item_data.rejection_reason
                    })

                event_data = {
                    "delivery_id": str(delivery.id),
                    "po_id": str(delivery_data.purchase_order_id),
                    "items": items_data,
                    "received_at": datetime.utcnow().isoformat(),
                    "received_by": str(created_by)
                }

                await self.event_publisher.publish_business_event(
                    event_type=EVENT_TYPES.PROCUREMENT.DELIVERY_RECEIVED,
                    tenant_id=tenant_id,
                    data=event_data
                )
            except Exception as event_error:
                # Log but don't fail the delivery creation if event publishing fails
                logger.warning(
                    "Failed to publish delivery received event",
                    delivery_id=str(delivery.id),
                    error=str(event_error)
                )

            return delivery

        except Exception as e:
            await self.db.rollback()
            logger.error("Error creating delivery", error=str(e), tenant_id=tenant_id)
            raise

    async def update_delivery_status(
        self,
        tenant_id: uuid.UUID,
        delivery_id: uuid.UUID,
        status: str,
        updated_by: uuid.UUID
    ) -> Optional[Delivery]:
        """Update delivery status"""
        try:
            update_data = {
                'status': status,
                'updated_at': datetime.utcnow()
            }

            if status == 'in_transit':
                update_data['actual_arrival'] = None
            elif status == 'delivered':
                update_data['actual_arrival'] = datetime.utcnow()
            elif status == 'completed':
                update_data['completed_at'] = datetime.utcnow()

            delivery = await self.delivery_repo.update_delivery(delivery_id, tenant_id, update_data)
            await self.db.commit()

            return delivery

        except Exception as e:
            await self.db.rollback()
            logger.error("Error updating delivery status", error=str(e), delivery_id=delivery_id)
            raise

    # ================================================================
    # INVOICE MANAGEMENT
    # ================================================================

    async def create_invoice(
        self,
        tenant_id: uuid.UUID,
        invoice_data: SupplierInvoiceCreate,
        created_by: uuid.UUID
    ) -> SupplierInvoice:
        """Create a supplier invoice"""
        try:
            logger.info("Creating supplier invoice", tenant_id=tenant_id)

            # Calculate total
            total_amount = (
                invoice_data.subtotal +
                invoice_data.tax_amount +
                invoice_data.shipping_cost -
                invoice_data.discount_amount
            )

            # Get PO for currency
            po = await self.po_repo.get_po_by_id(invoice_data.purchase_order_id, tenant_id)
            if not po:
                raise ValueError("Purchase order not found")

            invoice_create_data = {
                'tenant_id': tenant_id,
                'purchase_order_id': invoice_data.purchase_order_id,
                'supplier_id': invoice_data.supplier_id,
                'invoice_number': invoice_data.invoice_number,
                'status': 'received',
                'invoice_date': invoice_data.invoice_date,
                'due_date': invoice_data.due_date,
                'subtotal': invoice_data.subtotal,
                'tax_amount': invoice_data.tax_amount,
                'shipping_cost': invoice_data.shipping_cost,
                'discount_amount': invoice_data.discount_amount,
                'total_amount': total_amount,
                'currency': po.currency,
                'paid_amount': Decimal('0'),
                'remaining_amount': total_amount,
                'notes': invoice_data.notes,
                'payment_reference': invoice_data.payment_reference,
                'created_by': created_by,
                'updated_by': created_by,
                'created_at': datetime.utcnow(),
                'updated_at': datetime.utcnow(),
            }

            invoice = await self.invoice_repo.create_invoice(invoice_create_data)
            await self.db.commit()

            logger.info("Supplier invoice created", invoice_id=invoice.id)
            return invoice

        except Exception as e:
            await self.db.rollback()
            logger.error("Error creating invoice", error=str(e), tenant_id=tenant_id)
            raise

    # ================================================================
    # PRIVATE HELPER METHODS
    # ================================================================

    async def _emit_po_approval_alert(
        self,
        tenant_id: uuid.UUID,
        purchase_order: PurchaseOrder,
        supplier: Dict[str, Any]
    ) -> None:
        """Emit raw alert for PO approval needed with structured parameters"""
        try:
            # Calculate urgency fields based on required delivery date
            now = datetime.utcnow()
            hours_until_consequence = None
            deadline = None

            if purchase_order.required_delivery_date:
                # Deadline for approval is the required delivery date minus supplier lead time
                # We need to approve it early enough for supplier to deliver on time
                supplier_lead_time_days = supplier.get('standard_lead_time', 7)
                approval_deadline = purchase_order.required_delivery_date - timedelta(days=supplier_lead_time_days)
                deadline = approval_deadline
                hours_until_consequence = (approval_deadline - now).total_seconds() / 3600

            # Prepare alert payload matching RawAlert schema
            alert_data = {
                'id': str(uuid.uuid4()),  # Generate unique alert ID
                'tenant_id': str(tenant_id),
                'service': 'procurement',
                'type': 'po_approval_needed',
                'alert_type': 'po_approval_needed',  # Added for dashboard filtering
                'type_class': 'action_needed',  # Critical for dashboard action queue
                'severity': 'high' if purchase_order.priority == 'critical' else 'medium',
                'title': '',  # Empty - will be generated by frontend with i18n
                'message': '',  # Empty - will be generated by frontend with i18n
                'timestamp': datetime.utcnow().isoformat(),
                'metadata': {
                    'po_id': str(purchase_order.id),
                    'po_number': purchase_order.po_number,
                    'supplier_id': str(purchase_order.supplier_id),
                    'supplier_name': supplier.get('name', ''),
                    'total_amount': float(purchase_order.total_amount),
                    'currency': purchase_order.currency,
                    'priority': purchase_order.priority,
                    'required_delivery_date': purchase_order.required_delivery_date.isoformat() if purchase_order.required_delivery_date else None,
                    'created_at': purchase_order.created_at.isoformat(),
                    # Add urgency context for dashboard prioritization
                    'financial_impact': float(purchase_order.total_amount),
                    'urgency_score': 85,  # Default high urgency for pending approvals
                    # CRITICAL: Add deadline and hours_until_consequence for enrichment service
                    'deadline': deadline.isoformat() if deadline else None,
                    'hours_until_consequence': round(hours_until_consequence, 1) if hours_until_consequence else None,
                    # Include reasoning data from orchestrator OR build from inventory service
                    'reasoning_data': purchase_order.reasoning_data or await self._build_reasoning_data_fallback(
                        tenant_id, purchase_order, supplier
                    )
                },
                'message_params': {
                    'po_number': purchase_order.po_number,
                    'supplier_name': supplier.get('name', ''),
                    'total_amount': float(purchase_order.total_amount),
                    'currency': purchase_order.currency,
                    'priority': purchase_order.priority,
                    'required_delivery_date': purchase_order.required_delivery_date.isoformat() if purchase_order.required_delivery_date else None,
                    'items_count': len(purchase_order.items) if hasattr(purchase_order, 'items') else 0,
                    'created_at': purchase_order.created_at.isoformat()
                },
                'actions': ['approve_po', 'reject_po', 'modify_po'],
                'item_type': 'alert'
            }

            # Publish to RabbitMQ
            await self.rabbitmq_client.publish_event(
                exchange_name='alerts.exchange',
                routing_key=f'alert.{alert_data["severity"]}.procurement',
                event_data=alert_data
            )

            logger.info(
                "PO approval alert emitted",
                po_id=str(purchase_order.id),
                po_number=purchase_order.po_number,
                tenant_id=str(tenant_id)
            )

        except Exception as e:
            logger.error(
                "Failed to emit PO approval alert",
                po_id=str(purchase_order.id),
                error=str(e)
            )
            raise

    async def _build_reasoning_data_fallback(
        self,
        tenant_id: uuid.UUID,
        purchase_order: PurchaseOrder,
        supplier: Dict[str, Any]
    ) -> Dict[str, Any]:
        """Build rich reasoning data by querying inventory service for actual stock levels

        This method is called when a PO doesn't have reasoning_data (e.g., manually created POs).
        It queries the inventory service to get real stock levels and builds structured reasoning
        that can be translated via i18n on the frontend.
        """
        try:
            # Query inventory service for actual stock levels
            critical_products = []
            min_depletion_hours = float('inf')
            product_names = []

            # Get items from PO - handle both relationship and explicit loading
            items = purchase_order.items if hasattr(purchase_order, 'items') else []

            for item in items:
                product_names.append(item.product_name)

                # Only query if we have ingredient_id
                if not hasattr(item, 'ingredient_id') or not item.ingredient_id:
                    continue

                try:
                    # Call inventory service to get current stock - with 2 second timeout
                    stock_entries = await self.inventory_client.get_ingredient_stock(
                        ingredient_id=item.ingredient_id,
                        tenant_id=str(tenant_id)
                    )

                    if stock_entries:
                        # Calculate total available stock
                        total_stock = sum(entry.get('quantity', 0) for entry in stock_entries)

                        # Estimate daily usage (this would ideally come from forecast service)
                        # For now, use a simple heuristic: if PO quantity is X, daily usage might be X/7
                        estimated_daily_usage = item.quantity / 7.0 if item.quantity else 1.0

                        if estimated_daily_usage > 0:
                            hours_until_depletion = (total_stock / estimated_daily_usage) * 24

                            # Mark as critical if less than 48 hours (2 days)
                            if hours_until_depletion < 48:
                                critical_products.append(item.product_name)
                                min_depletion_hours = min(min_depletion_hours, hours_until_depletion)

                            logger.info(
                                "Calculated stock depletion for PO item",
                                tenant_id=str(tenant_id),
                                product=item.product_name,
                                current_stock=total_stock,
                                hours_until_depletion=round(hours_until_depletion, 1)
                            )

                except Exception as item_error:
                    logger.warning(
                        "Failed to get stock for PO item",
                        error=str(item_error),
                        product=item.product_name,
                        tenant_id=str(tenant_id)
                    )
                    # Continue with other items even if one fails
                    continue

            # Build rich reasoning data based on what we found
            if critical_products:
                # Use detailed reasoning type when we have critical products
                return {
                    "type": "low_stock_detection_detailed",
                    "parameters": {
                        "supplier_name": supplier.get('name', 'Supplier'),
                        "product_names": product_names,
                        "product_count": len(product_names),
                        "critical_products": critical_products,
                        "critical_product_count": len(critical_products),
                        "min_depletion_hours": round(min_depletion_hours, 1) if min_depletion_hours != float('inf') else 48,
                        "potential_loss_eur": float(purchase_order.total_amount * 1.5),  # Estimated opportunity cost
                    },
                    "consequence": {
                        "type": "stockout_risk",
                        "severity": "high",
                        "impact_days": 2
                    },
                    "metadata": {
                        "trigger_source": "manual_with_inventory_check",
                        "ai_assisted": False,
                        "enhanced_mode": True
                    }
                }
            else:
                # Use basic reasoning type when stock levels are not critical
                return {
                    "type": "low_stock_detection",
                    "parameters": {
                        "supplier_name": supplier.get('name', 'Supplier'),
                        "product_names": product_names,
                        "product_count": len(product_names),
                    },
                    "consequence": {
                        "type": "stockout_risk",
                        "severity": "medium",
                        "impact_days": 5
                    },
                    "metadata": {
                        "trigger_source": "manual_with_inventory_check",
                        "ai_assisted": False,
                        "enhanced_mode": False
                    }
                }

        except Exception as e:
            logger.warning(
                "Failed to build enhanced reasoning data, using basic fallback",
                error=str(e),
                tenant_id=str(tenant_id),
                po_id=str(purchase_order.id)
            )
            # Return basic fallback if inventory service is unavailable
            return {
                "type": "low_stock_detection",
                "parameters": {
                    "supplier_name": supplier.get('name', 'Supplier'),
                    "product_names": [item.product_name for item in (purchase_order.items if hasattr(purchase_order, 'items') else [])],
                    "product_count": len(purchase_order.items) if hasattr(purchase_order, 'items') else 0,
                },
                "consequence": {
                    "type": "stockout_risk",
                    "severity": "medium",
                    "impact_days": 5
                },
                "metadata": {
                    "trigger_source": "fallback_basic",
                    "ai_assisted": False
                }
            }

    async def _get_and_validate_supplier(self, tenant_id: uuid.UUID, supplier_id: uuid.UUID) -> Dict[str, Any]:
        """Get and validate supplier from Suppliers Service"""
        try:
            supplier = await self.suppliers_client.get_supplier_by_id(str(tenant_id), str(supplier_id))

            if not supplier:
                raise ValueError("Supplier not found")

            if supplier.get('status') != 'active':
                raise ValueError("Cannot create orders for inactive suppliers")

            return supplier

        except Exception as e:
            logger.error("Error validating supplier", error=str(e), supplier_id=supplier_id)
            raise

    async def _get_supplier_cached(self, tenant_id: uuid.UUID, supplier_id: uuid.UUID) -> Optional[Dict[str, Any]]:
        """
        Get supplier with request-scoped caching to avoid redundant API calls.

        When enriching multiple POs that share suppliers, this cache prevents
        duplicate calls to the suppliers service (Fix #11).

        Args:
            tenant_id: Tenant ID
            supplier_id: Supplier ID

        Returns:
            Supplier data dict or None
        """
        cache_key = f"{tenant_id}:{supplier_id}"

        if cache_key not in self._supplier_cache:
            supplier = await self.suppliers_client.get_supplier_by_id(str(tenant_id), str(supplier_id))
            self._supplier_cache[cache_key] = supplier
            logger.debug("Supplier cache MISS", tenant_id=str(tenant_id), supplier_id=str(supplier_id))
        else:
            logger.debug("Supplier cache HIT", tenant_id=str(tenant_id), supplier_id=str(supplier_id))

        return self._supplier_cache[cache_key]

    async def _enrich_po_with_supplier(self, tenant_id: uuid.UUID, po: PurchaseOrder) -> None:
        """Enrich purchase order with supplier information"""
        try:
            # Use cached supplier lookup to avoid redundant API calls
            supplier = await self._get_supplier_cached(tenant_id, po.supplier_id)
            if supplier:
                # Set supplier_name as a dynamic attribute on the model instance
                po.supplier_name = supplier.get('name', 'Unknown Supplier')

                # Create a supplier summary object with the required fields for the frontend
                # Using the same structure as the suppliers service SupplierSummary schema
                supplier_summary = {
                    'id': supplier.get('id'),
                    'name': supplier.get('name', 'Unknown Supplier'),
                    'supplier_code': supplier.get('supplier_code'),
                    'email': supplier.get('email'),
                    'phone': supplier.get('phone'),
                    'contact_person': supplier.get('contact_person'),
                    'address_line1': supplier.get('address_line1'),
                    'city': supplier.get('city'),
                    'country': supplier.get('country'),
                    'supplier_type': supplier.get('supplier_type', 'raw_material'),
                    'status': supplier.get('status', 'active'),
                    'mobile': supplier.get('mobile'),
                    'website': supplier.get('website'),
                    'payment_terms': supplier.get('payment_terms', 'NET_30'),
                    'standard_lead_time': supplier.get('standard_lead_time', 3),
                    'quality_rating': supplier.get('quality_rating'),
                    'delivery_rating': supplier.get('delivery_rating'),
                    'total_orders': supplier.get('total_orders', 0),
                    'total_amount': supplier.get('total_amount', 0)
                }

                # Set the full supplier object as a dynamic attribute
                po.supplier = supplier_summary
        except Exception as e:
            logger.warning("Failed to enrich PO with supplier info", error=str(e), po_id=po.id, supplier_id=po.supplier_id)
            po.supplier_name = None
            po.supplier = None

    def _requires_approval(self, total_amount: Decimal, priority: str) -> bool:
        """Determine if PO requires approval"""
        manager_threshold = Decimal(str(getattr(settings, 'MANAGER_APPROVAL_THRESHOLD', 1000)))
        return total_amount >= manager_threshold or priority == 'critical'

    def _determine_initial_status(self, total_amount: Decimal, requires_approval: bool) -> str:
        """Determine initial PO status"""
        auto_approve_threshold = Decimal(str(getattr(settings, 'AUTO_APPROVE_THRESHOLD', 100)))

        if requires_approval:
            return 'pending_approval'
        elif total_amount <= auto_approve_threshold:
            return 'approved'
        else:
            return 'draft'

    def _is_valid_status_transition(self, from_status: str, to_status: str) -> bool:
        """Validate status transition"""
        valid_transitions = {
            'draft': ['pending_approval', 'approved', 'cancelled'],
            'pending_approval': ['approved', 'rejected', 'cancelled'],
            'approved': ['sent_to_supplier', 'cancelled'],
            'sent_to_supplier': ['confirmed', 'cancelled'],
            'confirmed': ['in_production', 'cancelled'],
            'in_production': ['shipped', 'cancelled'],
            'shipped': ['delivered', 'cancelled'],
            'delivered': ['completed'],
            'rejected': [],
            'cancelled': [],
            'completed': []
        }

        return to_status in valid_transitions.get(from_status, [])
