"""
Stock Receipt API Endpoints

Handles delivery receipt confirmation with lot-level tracking.
Critical for food safety compliance - captures expiration dates per lot.
"""

from fastapi import APIRouter, HTTPException, Depends, status
from pydantic import BaseModel, Field, validator
from typing import List, Optional
from uuid import UUID
from datetime import datetime, date
from decimal import Decimal

import structlog
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.stock_receipt import StockReceipt, StockReceiptLineItem, StockLot, ReceiptStatus
from app.models.inventory import Stock, StockMovement, StockMovementType
from shared.database.dependencies import get_db
from shared.security import get_current_user

logger = structlog.get_logger()

router = APIRouter(prefix="/stock-receipts", tags=["stock-receipts"])


# ============================================================
# Request/Response Models
# ============================================================

class LotInput(BaseModel):
    """Individual lot details within a line item"""
    lot_number: Optional[str] = None
    supplier_lot_number: Optional[str] = None
    quantity: Decimal = Field(..., gt=0)
    unit_of_measure: str
    expiration_date: date = Field(..., description="Required for food safety")
    best_before_date: Optional[date] = None
    warehouse_location: Optional[str] = None
    storage_zone: Optional[str] = None
    quality_notes: Optional[str] = None


class LineItemInput(BaseModel):
    """Line item input for stock receipt"""
    ingredient_id: UUID
    ingredient_name: Optional[str] = None
    po_line_id: Optional[UUID] = None
    expected_quantity: Decimal
    actual_quantity: Decimal
    unit_of_measure: str
    discrepancy_reason: Optional[str] = None
    unit_cost: Optional[Decimal] = None
    lots: List[LotInput] = Field(..., min_items=1, description="At least one lot required")

    @validator('lots')
    def validate_lot_totals(cls, lots, values):
        """Ensure lot quantities sum to actual quantity"""
        if 'actual_quantity' not in values:
            return lots

        total_lot_qty = sum(lot.quantity for lot in lots)
        actual_qty = values['actual_quantity']

        if abs(total_lot_qty - actual_qty) > Decimal('0.01'):  # Allow small floating point errors
            raise ValueError(
                f"Lot quantities ({total_lot_qty}) must sum to actual quantity ({actual_qty})"
            )

        return lots


class CreateStockReceiptRequest(BaseModel):
    """Create draft stock receipt"""
    tenant_id: UUID
    po_id: UUID
    po_number: Optional[str] = None
    received_by_user_id: UUID
    supplier_id: Optional[UUID] = None
    supplier_name: Optional[str] = None
    notes: Optional[str] = None
    line_items: List[LineItemInput] = Field(..., min_items=1)


class UpdateStockReceiptRequest(BaseModel):
    """Update draft stock receipt"""
    notes: Optional[str] = None
    line_items: Optional[List[LineItemInput]] = None


class ConfirmStockReceiptRequest(BaseModel):
    """Confirm stock receipt and update inventory"""
    confirmed_by_user_id: UUID


# ============================================================
# API Endpoints
# ============================================================

@router.post("/", status_code=status.HTTP_201_CREATED)
async def create_stock_receipt(
    request: CreateStockReceiptRequest,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(get_current_user)
):
    """
    Create a draft stock receipt from a delivery.

    Workflow:
    1. User clicks "Mark as Received" on delivery alert
    2. This endpoint creates draft receipt
    3. Frontend opens StockReceiptModal with draft data
    4. User fills in lot details
    5. User saves draft (PUT endpoint) or confirms (POST /confirm)
    """
    try:
        # Create receipt
        receipt = StockReceipt(
            tenant_id=request.tenant_id,
            po_id=request.po_id,
            po_number=request.po_number,
            received_at=datetime.utcnow(),
            received_by_user_id=request.received_by_user_id,
            status=ReceiptStatus.DRAFT,
            supplier_id=request.supplier_id,
            supplier_name=request.supplier_name,
            notes=request.notes,
            has_discrepancies=False
        )

        db.add(receipt)
        await db.flush()  # Get receipt ID

        # Create line items and lots
        for line_input in request.line_items:
            has_discrepancy = abs(line_input.expected_quantity - line_input.actual_quantity) > Decimal('0.01')

            if has_discrepancy:
                receipt.has_discrepancies = True

            line_item = StockReceiptLineItem(
                tenant_id=request.tenant_id,
                receipt_id=receipt.id,
                ingredient_id=line_input.ingredient_id,
                ingredient_name=line_input.ingredient_name,
                po_line_id=line_input.po_line_id,
                expected_quantity=line_input.expected_quantity,
                actual_quantity=line_input.actual_quantity,
                unit_of_measure=line_input.unit_of_measure,
                has_discrepancy=has_discrepancy,
                discrepancy_reason=line_input.discrepancy_reason,
                unit_cost=line_input.unit_cost,
                total_cost=line_input.unit_cost * line_input.actual_quantity if line_input.unit_cost else None
            )

            db.add(line_item)
            await db.flush()  # Get line item ID

            # Create lots
            for lot_input in line_input.lots:
                lot = StockLot(
                    tenant_id=request.tenant_id,
                    line_item_id=line_item.id,
                    lot_number=lot_input.lot_number,
                    supplier_lot_number=lot_input.supplier_lot_number,
                    quantity=lot_input.quantity,
                    unit_of_measure=lot_input.unit_of_measure,
                    expiration_date=lot_input.expiration_date,
                    best_before_date=lot_input.best_before_date,
                    warehouse_location=lot_input.warehouse_location,
                    storage_zone=lot_input.storage_zone,
                    quality_notes=lot_input.quality_notes
                )
                db.add(lot)

        await db.commit()
        await db.refresh(receipt)

        logger.info(
            "Stock receipt created",
            receipt_id=str(receipt.id),
            po_id=str(request.po_id),
            line_items=len(request.line_items),
            tenant_id=str(request.tenant_id)
        )

        return receipt.to_dict()

    except Exception as e:
        await db.rollback()
        logger.error(
            "Failed to create stock receipt",
            error=str(e),
            po_id=str(request.po_id)
        )
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to create stock receipt: {str(e)}"
        )


@router.get("/{receipt_id}")
async def get_stock_receipt(
    receipt_id: UUID,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(get_current_user)
):
    """
    Retrieve stock receipt with all line items and lots.

    Used to resume editing a draft receipt.
    """
    try:
        stmt = select(StockReceipt).where(
            StockReceipt.id == receipt_id,
            StockReceipt.tenant_id == current_user['tenant_id']
        )
        result = await db.execute(stmt)
        receipt = result.scalar_one_or_none()

        if not receipt:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="Stock receipt not found"
            )

        return receipt.to_dict()

    except HTTPException:
        raise
    except Exception as e:
        logger.error(
            "Failed to retrieve stock receipt",
            receipt_id=str(receipt_id),
            error=str(e)
        )
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to retrieve stock receipt: {str(e)}"
        )


@router.put("/{receipt_id}")
async def update_stock_receipt(
    receipt_id: UUID,
    request: UpdateStockReceiptRequest,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(get_current_user)
):
    """
    Update draft stock receipt.

    Allows user to save progress while filling in lot details.
    """
    try:
        stmt = select(StockReceipt).where(
            StockReceipt.id == receipt_id,
            StockReceipt.tenant_id == current_user['tenant_id'],
            StockReceipt.status == ReceiptStatus.DRAFT
        )
        result = await db.execute(stmt)
        receipt = result.scalar_one_or_none()

        if not receipt:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="Draft stock receipt not found"
            )

        # Update notes if provided
        if request.notes is not None:
            receipt.notes = request.notes

        # Update line items if provided
        if request.line_items:
            # Delete existing line items (cascade deletes lots)
            for line_item in receipt.line_items:
                await db.delete(line_item)

            # Create new line items
            for line_input in request.line_items:
                has_discrepancy = abs(line_input.expected_quantity - line_input.actual_quantity) > Decimal('0.01')

                line_item = StockReceiptLineItem(
                    tenant_id=current_user['tenant_id'],
                    receipt_id=receipt.id,
                    ingredient_id=line_input.ingredient_id,
                    ingredient_name=line_input.ingredient_name,
                    po_line_id=line_input.po_line_id,
                    expected_quantity=line_input.expected_quantity,
                    actual_quantity=line_input.actual_quantity,
                    unit_of_measure=line_input.unit_of_measure,
                    has_discrepancy=has_discrepancy,
                    discrepancy_reason=line_input.discrepancy_reason,
                    unit_cost=line_input.unit_cost,
                    total_cost=line_input.unit_cost * line_input.actual_quantity if line_input.unit_cost else None
                )

                db.add(line_item)
                await db.flush()

                # Create lots
                for lot_input in line_input.lots:
                    lot = StockLot(
                        tenant_id=current_user['tenant_id'],
                        line_item_id=line_item.id,
                        lot_number=lot_input.lot_number,
                        supplier_lot_number=lot_input.supplier_lot_number,
                        quantity=lot_input.quantity,
                        unit_of_measure=lot_input.unit_of_measure,
                        expiration_date=lot_input.expiration_date,
                        best_before_date=lot_input.best_before_date,
                        warehouse_location=lot_input.warehouse_location,
                        storage_zone=lot_input.storage_zone,
                        quality_notes=lot_input.quality_notes
                    )
                    db.add(lot)

        await db.commit()
        await db.refresh(receipt)

        logger.info(
            "Stock receipt updated",
            receipt_id=str(receipt_id),
            tenant_id=str(current_user['tenant_id'])
        )

        return receipt.to_dict()

    except HTTPException:
        raise
    except Exception as e:
        await db.rollback()
        logger.error(
            "Failed to update stock receipt",
            receipt_id=str(receipt_id),
            error=str(e)
        )
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to update stock receipt: {str(e)}"
        )


@router.post("/{receipt_id}/confirm")
async def confirm_stock_receipt(
    receipt_id: UUID,
    request: ConfirmStockReceiptRequest,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(get_current_user)
):
    """
    Confirm stock receipt and update inventory.

    This finalizes the receipt:
    1. Creates Stock records for each lot
    2. Creates StockMovement records (PURCHASE type)
    3. Marks receipt as CONFIRMED
    4. Updates PO status to RECEIVED (via procurement service)
    """
    try:
        stmt = select(StockReceipt).where(
            StockReceipt.id == receipt_id,
            StockReceipt.tenant_id == current_user['tenant_id'],
            StockReceipt.status == ReceiptStatus.DRAFT
        )
        result = await db.execute(stmt)
        receipt = result.scalar_one_or_none()

        if not receipt:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="Draft stock receipt not found"
            )

        # Process each line item and its lots
        for line_item in receipt.line_items:
            for lot in line_item.lots:
                # Create Stock record
                stock = Stock(
                    tenant_id=current_user['tenant_id'],
                    ingredient_id=line_item.ingredient_id,
                    supplier_id=receipt.supplier_id,
                    batch_number=f"RCV-{receipt_id}-{lot.id}",
                    lot_number=lot.lot_number,
                    supplier_batch_ref=lot.supplier_lot_number,
                    production_stage='raw_ingredient',
                    current_quantity=float(lot.quantity),
                    reserved_quantity=0.0,
                    available_quantity=float(lot.quantity),
                    received_date=receipt.received_at,
                    expiration_date=datetime.combine(lot.expiration_date, datetime.min.time()),
                    best_before_date=datetime.combine(lot.best_before_date, datetime.min.time()) if lot.best_before_date else None,
                    unit_cost=line_item.unit_cost,
                    total_cost=line_item.unit_cost * lot.quantity if line_item.unit_cost else None,
                    storage_location=lot.warehouse_location,
                    warehouse_zone=lot.storage_zone,
                    is_available=True,
                    is_expired=False,
                    quality_status="good"
                )
                db.add(stock)
                await db.flush()

                # Link lot to stock
                lot.stock_id = stock.id

                # Create StockMovement record
                movement = StockMovement(
                    tenant_id=current_user['tenant_id'],
                    ingredient_id=line_item.ingredient_id,
                    stock_id=stock.id,
                    movement_type=StockMovementType.PURCHASE,
                    quantity=float(lot.quantity),
                    unit_cost=line_item.unit_cost,
                    total_cost=line_item.unit_cost * lot.quantity if line_item.unit_cost else None,
                    quantity_before=0.0,
                    quantity_after=float(lot.quantity),
                    reference_number=receipt.po_number,
                    supplier_id=receipt.supplier_id,
                    notes=f"Stock receipt {receipt_id}",
                    movement_date=receipt.received_at
                )
                db.add(movement)

        # Mark receipt as confirmed
        receipt.status = ReceiptStatus.CONFIRMED
        receipt.confirmed_at = datetime.utcnow()

        await db.commit()

        logger.info(
            "Stock receipt confirmed",
            receipt_id=str(receipt_id),
            po_id=str(receipt.po_id),
            tenant_id=str(current_user['tenant_id'])
        )

        return {
            "status": "success",
            "receipt_id": str(receipt_id),
            "message": "Stock receipt confirmed and inventory updated"
        }

    except HTTPException:
        raise
    except Exception as e:
        await db.rollback()
        logger.error(
            "Failed to confirm stock receipt",
            receipt_id=str(receipt_id),
            error=str(e)
        )
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to confirm stock receipt: {str(e)}"
        )
