# services/suppliers/app/services/purchase_order_service.py
"""
Purchase Order service for business logic operations
"""

import structlog
from typing import List, Optional, Dict, Any
from uuid import UUID
from datetime import datetime, timedelta
from sqlalchemy.orm import Session
from decimal import Decimal

from app.repositories.purchase_order_repository import PurchaseOrderRepository
from app.repositories.purchase_order_item_repository import PurchaseOrderItemRepository
from app.repositories.supplier_repository import SupplierRepository
from app.models.suppliers import (
    PurchaseOrder, PurchaseOrderItem, PurchaseOrderStatus, SupplierStatus
)
from app.schemas.suppliers import (
    PurchaseOrderCreate, PurchaseOrderUpdate, PurchaseOrderSearchParams,
    PurchaseOrderItemCreate, PurchaseOrderItemUpdate
)
from app.core.config import settings

logger = structlog.get_logger()


class PurchaseOrderService:
    """Service for purchase order management operations"""
    
    def __init__(self, db: Session):
        self.db = db
        self.repository = PurchaseOrderRepository(db)
        self.item_repository = PurchaseOrderItemRepository(db)
        self.supplier_repository = SupplierRepository(db)
    
    async def create_purchase_order(
        self,
        tenant_id: UUID,
        po_data: PurchaseOrderCreate,
        created_by: UUID
    ) -> PurchaseOrder:
        """Create a new purchase order with items"""
        logger.info(
            "Creating purchase order",
            tenant_id=str(tenant_id),
            supplier_id=str(po_data.supplier_id)
        )
        
        # Validate supplier exists and is active
        supplier = self.supplier_repository.get_by_id(po_data.supplier_id)
        if not supplier:
            raise ValueError("Supplier not found")
        if supplier.status != SupplierStatus.ACTIVE:
            raise ValueError("Cannot create orders for inactive suppliers")
        if supplier.tenant_id != tenant_id:
            raise ValueError("Supplier does not belong to this tenant")
        
        # Generate PO number
        po_number = self.repository.generate_po_number(tenant_id)
        
        # Calculate totals from items
        subtotal = sum(
            item.ordered_quantity * item.unit_price 
            for item in po_data.items
        )
        
        total_amount = (
            subtotal + 
            po_data.tax_amount + 
            po_data.shipping_cost - 
            po_data.discount_amount
        )
        
        # Determine if approval is required
        requires_approval = (
            total_amount >= settings.MANAGER_APPROVAL_THRESHOLD or
            po_data.priority == "urgent"
        )
        
        # Set initial status
        if requires_approval:
            status = PurchaseOrderStatus.PENDING_APPROVAL
        elif total_amount <= settings.AUTO_APPROVE_THRESHOLD:
            status = PurchaseOrderStatus.APPROVED
        else:
            status = PurchaseOrderStatus.DRAFT
        
        # Create purchase order
        po_create_data = po_data.model_dump(exclude={'items'})
        po_create_data.update({
            'tenant_id': tenant_id,
            'po_number': po_number,
            'status': status,
            'subtotal': subtotal,
            'total_amount': total_amount,
            'order_date': datetime.utcnow(),
            'requires_approval': requires_approval,
            'currency': supplier.currency,
            'created_by': created_by,
            'updated_by': created_by
        })
        
        # Set delivery date if not provided
        if not po_create_data.get('required_delivery_date'):
            po_create_data['required_delivery_date'] = (
                datetime.utcnow() + timedelta(days=supplier.standard_lead_time)
            )
        
        purchase_order = self.repository.create(po_create_data)
        
        # Create purchase order items
        for item_data in po_data.items:
            item_create_data = item_data.model_dump()
            item_create_data.update({
                'tenant_id': tenant_id,
                'purchase_order_id': purchase_order.id,
                'line_total': item_data.ordered_quantity * item_data.unit_price,
                'remaining_quantity': item_data.ordered_quantity
            })
            
            self.item_repository.create(item_create_data)
        
        logger.info(
            "Purchase order created successfully",
            tenant_id=str(tenant_id),
            po_id=str(purchase_order.id),
            po_number=po_number,
            total_amount=float(total_amount)
        )
        
        return purchase_order
    
    async def get_purchase_order(self, po_id: UUID) -> Optional[PurchaseOrder]:
        """Get purchase order by ID with items"""
        return await self.repository.get_with_items(po_id)
    
    async def update_purchase_order(
        self,
        po_id: UUID,
        po_data: PurchaseOrderUpdate,
        updated_by: UUID
    ) -> Optional[PurchaseOrder]:
        """Update purchase order information"""
        logger.info("Updating purchase order", po_id=str(po_id))
        
        po = self.repository.get_by_id(po_id)
        if not po:
            return None
        
        # Check if order can be modified
        if po.status in [
            PurchaseOrderStatus.COMPLETED,
            PurchaseOrderStatus.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']):
            po = self.repository.calculate_order_totals(po_id)
        
        po = self.repository.update(po_id, update_data)
        
        logger.info("Purchase order updated successfully", po_id=str(po_id))
        return po
    
    async def update_order_status(
        self,
        po_id: UUID,
        status: PurchaseOrderStatus,
        updated_by: UUID,
        notes: Optional[str] = None
    ) -> Optional[PurchaseOrder]:
        """Update purchase order status"""
        logger.info("Updating PO status", po_id=str(po_id), status=status.value)
        
        po = self.repository.get_by_id(po_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.value} to {status.value}")
        
        return self.repository.update_order_status(po_id, status, updated_by, notes)
    
    async def approve_purchase_order(
        self,
        po_id: UUID,
        approved_by: UUID,
        approval_notes: Optional[str] = None
    ) -> Optional[PurchaseOrder]:
        """Approve a purchase order"""
        logger.info("Approving purchase order", po_id=str(po_id))
        
        po = self.repository.approve_order(po_id, approved_by, approval_notes)
        if not po:
            logger.warning("Failed to approve PO - not found or not pending approval")
            return None
        
        logger.info("Purchase order approved successfully", po_id=str(po_id))
        return po
    
    async def reject_purchase_order(
        self,
        po_id: UUID,
        rejection_reason: str,
        rejected_by: UUID
    ) -> Optional[PurchaseOrder]:
        """Reject a purchase order"""
        logger.info("Rejecting purchase order", po_id=str(po_id))
        
        po = self.repository.get_by_id(po_id)
        if not po or po.status != PurchaseOrderStatus.PENDING_APPROVAL:
            return None
        
        update_data = {
            'status': PurchaseOrderStatus.CANCELLED,
            'rejection_reason': rejection_reason,
            'approved_by': rejected_by,
            'approved_at': datetime.utcnow(),
            'updated_by': rejected_by,
            'updated_at': datetime.utcnow()
        }
        
        po = self.repository.update(po_id, update_data)
        logger.info("Purchase order rejected successfully", po_id=str(po_id))
        return po
    
    async def send_to_supplier(
        self,
        po_id: UUID,
        sent_by: UUID,
        send_email: bool = True
    ) -> Optional[PurchaseOrder]:
        """Send purchase order to supplier"""
        logger.info("Sending PO to supplier", po_id=str(po_id))
        
        po = self.repository.get_by_id(po_id)
        if not po:
            return None
        
        if po.status != PurchaseOrderStatus.APPROVED:
            raise ValueError("Only approved orders can be sent to suppliers")
        
        # Update status and timestamp
        po = self.repository.update_order_status(
            po_id,
            PurchaseOrderStatus.SENT_TO_SUPPLIER,
            sent_by,
            "Order sent to supplier"
        )

        # Send email to supplier if requested
        if send_email:
            try:
                supplier = self.supplier_repository.get_by_id(po.supplier_id)
                if supplier and supplier.email:
                    from shared.clients.notification_client import create_notification_client

                    notification_client = create_notification_client(settings)

                    # Prepare email content
                    subject = f"Purchase Order {po.po_number} from {po.tenant_id}"
                    message = f"""
Dear {supplier.name},

We are sending you Purchase Order #{po.po_number}.

Order Details:
- PO Number: {po.po_number}
- Expected Delivery: {po.expected_delivery_date}
- Total Amount: €{po.total_amount}

Please confirm receipt of this purchase order.

Best regards
"""

                    await notification_client.send_email(
                        tenant_id=str(po.tenant_id),
                        to_email=supplier.email,
                        subject=subject,
                        message=message,
                        priority="normal"
                    )

                    logger.info("Email sent to supplier",
                              po_id=str(po_id),
                              supplier_email=supplier.email)
                else:
                    logger.warning("Supplier email not available",
                                 po_id=str(po_id),
                                 supplier_id=str(po.supplier_id))
            except Exception as e:
                logger.error("Failed to send email to supplier",
                           error=str(e),
                           po_id=str(po_id))
                # Don't fail the entire operation if email fails

        logger.info("Purchase order sent to supplier", po_id=str(po_id))
        return po
    
    async def confirm_supplier_receipt(
        self,
        po_id: UUID,
        supplier_reference: Optional[str] = None,
        confirmed_by: UUID = None
    ) -> Optional[PurchaseOrder]:
        """Confirm supplier has received and accepted the order"""
        logger.info("Confirming supplier receipt", po_id=str(po_id))
        
        po = self.repository.get_by_id(po_id)
        if not po:
            return None
        
        if po.status != PurchaseOrderStatus.SENT_TO_SUPPLIER:
            raise ValueError("Order must be sent to supplier before confirmation")
        
        update_data = {
            'status': PurchaseOrderStatus.CONFIRMED,
            'supplier_confirmation_date': datetime.utcnow(),
            'supplier_reference': supplier_reference,
            'updated_at': datetime.utcnow()
        }
        
        if confirmed_by:
            update_data['updated_by'] = confirmed_by
        
        po = self.repository.update(po_id, update_data)
        logger.info("Supplier receipt confirmed", po_id=str(po_id))
        return po
    
    async def search_purchase_orders(
        self,
        tenant_id: UUID,
        search_params: PurchaseOrderSearchParams
    ) -> List[PurchaseOrder]:
        """Search purchase orders with filters"""
        return await self.repository.search_purchase_orders(
            tenant_id=tenant_id,
            supplier_id=search_params.supplier_id,
            status=search_params.status,
            priority=search_params.priority,
            date_from=search_params.date_from,
            date_to=search_params.date_to,
            search_term=search_params.search_term,
            limit=search_params.limit,
            offset=search_params.offset
        )
    
    async def get_orders_by_supplier(
        self,
        tenant_id: UUID,
        supplier_id: UUID,
        limit: int = 20
    ) -> List[PurchaseOrder]:
        """Get recent orders for a supplier"""
        return self.repository.get_orders_by_supplier(tenant_id, supplier_id, limit)
    
    async def get_orders_requiring_approval(
        self,
        tenant_id: UUID
    ) -> List[PurchaseOrder]:
        """Get orders pending approval"""
        return self.repository.get_orders_requiring_approval(tenant_id)
    
    async def get_overdue_orders(self, tenant_id: UUID) -> List[PurchaseOrder]:
        """Get orders that are overdue for delivery"""
        return self.repository.get_overdue_orders(tenant_id)
    
    async def get_purchase_order_statistics(
        self,
        tenant_id: UUID
    ) -> Dict[str, Any]:
        """Get purchase order statistics"""
        return self.repository.get_purchase_order_statistics(tenant_id)
    
    async def update_order_items(
        self,
        po_id: UUID,
        items_updates: List[Dict[str, Any]],
        updated_by: UUID
    ) -> Optional[PurchaseOrder]:
        """Update multiple items in a purchase order"""
        logger.info("Updating order items", po_id=str(po_id))
        
        po = self.repository.get_by_id(po_id)
        if not po:
            return None
        
        # Check if order can be modified
        if po.status in [
            PurchaseOrderStatus.COMPLETED,
            PurchaseOrderStatus.CANCELLED,
            PurchaseOrderStatus.SENT_TO_SUPPLIER,
            PurchaseOrderStatus.CONFIRMED
        ]:
            raise ValueError("Cannot modify items for orders in current status")
        
        # Update items
        self.item_repository.bulk_update_items(po_id, items_updates)
        
        # Recalculate order totals
        po = self.repository.calculate_order_totals(po_id)
        
        # Update the order timestamp
        self.repository.update(po_id, {
            'updated_by': updated_by,
            'updated_at': datetime.utcnow()
        })
        
        logger.info("Order items updated successfully", po_id=str(po_id))
        return po
    
    async def cancel_purchase_order(
        self,
        po_id: UUID,
        cancellation_reason: str,
        cancelled_by: UUID
    ) -> Optional[PurchaseOrder]:
        """Cancel a purchase order"""
        logger.info("Cancelling purchase order", po_id=str(po_id))
        
        po = self.repository.get_by_id(po_id)
        if not po:
            return None
        
        if po.status in [PurchaseOrderStatus.COMPLETED, PurchaseOrderStatus.CANCELLED]:
            raise ValueError("Cannot cancel completed or already cancelled orders")
        
        update_data = {
            'status': PurchaseOrderStatus.CANCELLED,
            'rejection_reason': cancellation_reason,
            'updated_by': cancelled_by,
            'updated_at': datetime.utcnow()
        }
        
        po = self.repository.update(po_id, update_data)
        
        logger.info("Purchase order cancelled successfully", po_id=str(po_id))
        return po
    
    def _is_valid_status_transition(
        self,
        from_status: PurchaseOrderStatus,
        to_status: PurchaseOrderStatus
    ) -> bool:
        """Validate if status transition is allowed"""
        # Define valid transitions
        valid_transitions = {
            PurchaseOrderStatus.DRAFT: [
                PurchaseOrderStatus.PENDING_APPROVAL,
                PurchaseOrderStatus.APPROVED,
                PurchaseOrderStatus.CANCELLED
            ],
            PurchaseOrderStatus.PENDING_APPROVAL: [
                PurchaseOrderStatus.APPROVED,
                PurchaseOrderStatus.CANCELLED
            ],
            PurchaseOrderStatus.APPROVED: [
                PurchaseOrderStatus.SENT_TO_SUPPLIER,
                PurchaseOrderStatus.CANCELLED
            ],
            PurchaseOrderStatus.SENT_TO_SUPPLIER: [
                PurchaseOrderStatus.CONFIRMED,
                PurchaseOrderStatus.CANCELLED
            ],
            PurchaseOrderStatus.CONFIRMED: [
                PurchaseOrderStatus.PARTIALLY_RECEIVED,
                PurchaseOrderStatus.COMPLETED,
                PurchaseOrderStatus.DISPUTED
            ],
            PurchaseOrderStatus.PARTIALLY_RECEIVED: [
                PurchaseOrderStatus.COMPLETED,
                PurchaseOrderStatus.DISPUTED
            ],
            PurchaseOrderStatus.DISPUTED: [
                PurchaseOrderStatus.CONFIRMED,
                PurchaseOrderStatus.CANCELLED
            ]
        }
        
        return to_status in valid_transitions.get(from_status, [])
    
    async def get_inventory_product_purchase_history(
        self,
        tenant_id: UUID,
        inventory_product_id: UUID,
        days_back: int = 90
    ) -> Dict[str, Any]:
        """Get purchase history for an inventory product"""
        return self.item_repository.get_inventory_product_purchase_history(
            tenant_id, inventory_product_id, days_back
        )
    
    async def get_top_purchased_inventory_products(
        self,
        tenant_id: UUID,
        days_back: int = 30,
        limit: int = 10
    ) -> List[Dict[str, Any]]:
        """Get most purchased inventory products"""
        return self.item_repository.get_top_purchased_inventory_products(
            tenant_id, days_back, limit
        )

    async def delete_purchase_order(self, po_id: UUID) -> bool:
        """
        Delete (soft delete) a purchase order
        Only allows deletion of draft orders
        """
        logger.info("Deleting purchase order", po_id=str(po_id))

        po = self.repository.get_by_id(po_id)
        if not po:
            return False

        # Only allow deletion of draft orders
        if po.status not in [PurchaseOrderStatus.DRAFT, PurchaseOrderStatus.CANCELLED]:
            raise ValueError(
                f"Cannot delete purchase order with status {po.status.value}. "
                "Only draft and cancelled orders can be deleted."
            )

        # Perform soft delete
        try:
            self.repository.delete(po_id)
            self.db.commit()
            logger.info("Purchase order deleted successfully", po_id=str(po_id))
            return True
        except Exception as e:
            self.db.rollback()
            logger.error("Failed to delete purchase order", po_id=str(po_id), error=str(e))
            raise