from sqlalchemy.orm import Session
from fastapi import HTTPException, status
from typing import List, Optional
from datetime import datetime
from . import repository
from .schema import (
    OrderCreate, OrderUpdate, OrderResponse, OrderDetailResponse,
    OrderitemCreate, OrderitemUpdate, OrderitemResponse,
    PaymentCreate, PaymentUpdate, PaymentResponse,
    ShipmentCreate, ShipmentUpdate, ShipmentResponse,
    OrderStatus, PaymentStatus, ShipmentStatus,
    OrderitemDetailResponse
)
from user_management import repository as user_repo
from user_management.models import Address
from listing_management import repository as listing_repo
from card_catalog import repository as card_repo


def _get_or_raise(db: Session, entity_id: str, repo_module) -> any:
    entity = repo_module.get_by_id(db, entity_id)
    if not entity:
        raise HTTPException(status_code=404, detail=f"Entity with id {entity_id} not found")
    return entity


# Order handlers
def create_order(db: Session, data: OrderCreate) -> OrderResponse:
    # Validate buyer exists
    buyer = user_repo.get_by_id(db, data.buyer_id)
    if not buyer:
        raise HTTPException(status_code=404, detail=f"Buyer with id {data.buyer_id} not found")
    
    # Validate seller exists
    seller = user_repo.get_by_id(db, data.seller_id)
    if not seller:
        raise HTTPException(status_code=404, detail=f"Seller with id {data.seller_id} not found")
    
    # Validate addresses exist
    shipping_address = db.query(Address).filter(Address.id == data.shipping_address_id).first()
    if not shipping_address:
        raise HTTPException(status_code=404, detail=f"Shipping address with id {data.shipping_address_id} not found")
    
    billing_address = db.query(Address).filter(Address.id == data.billing_address_id).first()
    if not billing_address:
        raise HTTPException(status_code=404, detail=f"Billing address with id {data.billing_address_id} not found")
    
    # Check for duplicate order number
    existing = repository.get_by_order_number(db, data.order_number)
    if existing:
        raise HTTPException(status_code=409, detail=f"Order with number {data.order_number} already exists")
    
    try:
        order = repository.create(db, data.model_dump())
        db.commit()
        db.refresh(order)
        return OrderResponse.model_validate(order)
    except Exception:
        db.rollback()
        raise


def get_order(db: Session, order_id: str) -> OrderResponse:
    order = _get_or_raise(db, order_id, repository)
    return OrderResponse.model_validate(order)


def list_orders(
    db: Session,
    limit: int,
    offset: int,
    buyer_id: Optional[str] = None,
    seller_id: Optional[str] = None,
    status: Optional[OrderStatus] = None,
    order_number: Optional[str] = None
) -> List[OrderResponse]:
    filters = {}
    if buyer_id:
        filters["buyer_id"] = buyer_id
    if seller_id:
        filters["seller_id"] = seller_id
    if status:
        filters["status"] = status
    if order_number:
        filters["order_number"] = order_number
    
    orders = repository.list_all(db, limit, offset, **filters)
    return [OrderResponse.model_validate(order) for order in orders]


def update_order(db: Session, order_id: str, data: OrderUpdate) -> OrderResponse:
    order = _get_or_raise(db, order_id, repository)
    
    update_data = data.model_dump(exclude_unset=True)
    
    # Validate buyer if being updated
    if "buyer_id" in update_data:
        buyer = user_repo.get_by_id(db, update_data["buyer_id"])
        if not buyer:
            raise HTTPException(status_code=404, detail=f"Buyer with id {update_data['buyer_id']} not found")
    
    # Validate seller if being updated
    if "seller_id" in update_data:
        seller = user_repo.get_by_id(db, update_data["seller_id"])
        if not seller:
            raise HTTPException(status_code=404, detail=f"Seller with id {update_data['seller_id']} not found")
    
    # Validate addresses if being updated
    if "shipping_address_id" in update_data:
        shipping_address = db.query(Address).filter(Address.id == update_data["shipping_address_id"]).first()
        if not shipping_address:
            raise HTTPException(status_code=404, detail=f"Shipping address with id {update_data['shipping_address_id']} not found")
    
    if "billing_address_id" in update_data:
        billing_address = db.query(Address).filter(Address.id == update_data["billing_address_id"]).first()
        if not billing_address:
            raise HTTPException(status_code=404, detail=f"Billing address with id {update_data['billing_address_id']} not found")
    
    # Check for duplicate order number if being updated
    if "order_number" in update_data:
        existing = repository.get_by_order_number(db, update_data["order_number"])
        if existing and existing.id != order_id:
            raise HTTPException(status_code=409, detail=f"Order with number {update_data['order_number']} already exists")
    
    try:
        updated_order = repository.update(db, order_id, update_data)
        db.commit()
        db.refresh(updated_order)
        return OrderResponse.model_validate(updated_order)
    except Exception:
        db.rollback()
        raise


def delete_order(db: Session, order_id: str) -> dict:
    order = _get_or_raise(db, order_id, repository)
    
    try:
        repository.delete(db, order_id)
        db.commit()
        return {"message": "Order deleted successfully"}
    except Exception:
        db.rollback()
        raise


def get_order_details(db: Session, order_id: str) -> OrderDetailResponse:
    order = repository.get_with_details(db, order_id)
    if not order:
        raise HTTPException(status_code=404, detail=f"Order with id {order_id} not found")
    
    order_items_detail = []
    for item in order.order_items:
        order_items_detail.append(OrderitemDetailResponse(
            id=item.id,
            order_id=item.order_id,
            listing_id=item.listing_id,
            card_id=item.card_id,
            quantity=item.quantity,
            price=item.price,
            condition=item.condition,
            listing_title=item.listing.title if item.listing else "",
            card_name=item.card.name if item.card else "",
            created_at=item.created_at,
            updated_at=item.updated_at
        ))
    
    return OrderDetailResponse(
        id=order.id,
        order_number=order.order_number,
        buyer_id=order.buyer_id,
        seller_id=order.seller_id,
        status=order.status,
        subtotal=order.subtotal,
        shipping_cost=order.shipping_cost,
        tax_amount=order.tax_amount,
        platform_fee=order.platform_fee,
        total_amount=order.total_amount,
        shipping_address_id=order.shipping_address_id,
        billing_address_id=order.billing_address_id,
        notes=order.notes,
        cancelled_at=order.cancelled_at,
        cancellation_reason=order.cancellation_reason,
        buyer_email=order.buyer.email if order.buyer else "",
        seller_email=order.seller.email if order.seller else "",
        seller_store_name=order.seller.seller_profile.store_name if order.seller and order.seller.seller_profile else None,
        order_items=order_items_detail,
        payment_status=order.payment.status if order.payment else None,
        shipment_tracking_number=order.shipment.tracking_number if order.shipment else None,
        shipping_address_line1=order.shipping_address.address_line1 if order.shipping_address else "",
        billing_address_line1=order.billing_address.address_line1 if order.billing_address else "",
        created_at=order.created_at,
        updated_at=order.updated_at
    )


def ship_order(db: Session, order_id: str) -> OrderResponse:
    order = _get_or_raise(db, order_id, repository)
    
    if order.status != OrderStatus.PROCESSING:
        raise HTTPException(status_code=400, detail="Order must be in PROCESSING status to ship")
    
    shipment = repository.get_shipment_by_order_id(db, order_id)
    if not shipment:
        raise HTTPException(status_code=400, detail="Shipment record not found for this order")
    
    try:
        repository.update(db, order_id, {"status": OrderStatus.SHIPPED})
        repository.update_shipment(db, shipment.id, {
            "status": ShipmentStatus.IN_TRANSIT,
            "shipped_at": datetime.utcnow()
        })
        db.commit()
        db.refresh(order)
        return OrderResponse.model_validate(order)
    except Exception:
        db.rollback()
        raise


def cancel_order(db: Session, order_id: str, cancellation_reason: str) -> OrderResponse:
    order = _get_or_raise(db, order_id, repository)
    
    if order.status == OrderStatus.SHIPPED:
        raise HTTPException(status_code=400, detail="Cannot cancel order after shipment")
    
    if order.status in [OrderStatus.CANCELLED, OrderStatus.REFUNDED]:
        raise HTTPException(status_code=400, detail="Order is already cancelled or refunded")
    
    try:
        repository.update(db, order_id, {
            "status": OrderStatus.CANCELLED,
            "cancelled_at": datetime.utcnow(),
            "cancellation_reason": cancellation_reason
        })
        db.commit()
        db.refresh(order)
        return OrderResponse.model_validate(order)
    except Exception:
        db.rollback()
        raise


def confirm_delivery(db: Session, order_id: str) -> OrderResponse:
    order = _get_or_raise(db, order_id, repository)
    
    if order.status != OrderStatus.SHIPPED:
        raise HTTPException(status_code=400, detail="Order must be in SHIPPED status to confirm delivery")
    
    shipment = repository.get_shipment_by_order_id(db, order_id)
    if not shipment:
        raise HTTPException(status_code=400, detail="Shipment record not found for this order")
    
    try:
        repository.update(db, order_id, {"status": OrderStatus.DELIVERED})
        repository.update_shipment(db, shipment.id, {
            "status": ShipmentStatus.DELIVERED,
            "delivered_at": datetime.utcnow()
        })
        db.commit()
        db.refresh(order)
        return OrderResponse.model_validate(order)
    except Exception:
        db.rollback()
        raise


# Orderitem handlers
def create_orderitem(db: Session, data: OrderitemCreate) -> OrderitemResponse:
    # Validate order exists
    order = repository.get_by_id(db, data.order_id)
    if not order:
        raise HTTPException(status_code=404, detail=f"Order with id {data.order_id} not found")
    
    # Validate listing exists
    listing = listing_repo.get_by_id(db, data.listing_id)
    if not listing:
        raise HTTPException(status_code=404, detail=f"Listing with id {data.listing_id} not found")
    
    # Validate card exists
    card = card_repo.get_by_id(db, data.card_id)
    if not card:
        raise HTTPException(status_code=404, detail=f"Card with id {data.card_id} not found")
    
    try:
        orderitem = repository.create_orderitem(db, data.model_dump())
        db.commit()
        db.refresh(orderitem)
        return OrderitemResponse.model_validate(orderitem)
    except Exception:
        db.rollback()
        raise


def get_orderitem(db: Session, orderitem_id: str) -> OrderitemResponse:
    orderitem = _get_or_raise(db, orderitem_id, repository)
    return OrderitemResponse.model_validate(orderitem)


def list_orderitems(
    db: Session,
    limit: int,
    offset: int,
    order_id: Optional[str] = None,
    listing_id: Optional[str] = None,
    card_id: Optional[str] = None
) -> List[OrderitemResponse]:
    filters = {}
    if order_id:
        filters["order_id"] = order_id
    if listing_id:
        filters["listing_id"] = listing_id
    if card_id:
        filters["card_id"] = card_id
    
    orderitems = repository.list_orderitems(db, limit, offset, **filters)
    return [OrderitemResponse.model_validate(item) for item in orderitems]


def update_orderitem(db: Session, orderitem_id: str, data: OrderitemUpdate) -> OrderitemResponse:
    orderitem = _get_or_raise(db, orderitem_id, repository)
    
    update_data = data.model_dump(exclude_unset=True)
    
    # Validate order if being updated
    if "order_id" in update_data:
        order = repository.get_by_id(db, update_data["order_id"])
        if not order:
            raise HTTPException(status_code=404, detail=f"Order with id {update_data['order_id']} not found")
    
    # Validate listing if being updated
    if "listing_id" in update_data:
        listing = listing_repo.get_by_id(db, update_data["listing_id"])
        if not listing:
            raise HTTPException(status_code=404, detail=f"Listing with id {update_data['listing_id']} not found")
    
    # Validate card if being updated
    if "card_id" in update_data:
        card = card_repo.get_by_id(db, update_data["card_id"])
        if not card:
            raise HTTPException(status_code=404, detail=f"Card with id {update_data['card_id']} not found")
    
    try:
        updated_orderitem = repository.update_orderitem(db, orderitem_id, update_data)
        db.commit()
        db.refresh(updated_orderitem)
        return OrderitemResponse.model_validate(updated_orderitem)
    except Exception:
        db.rollback()
        raise


def delete_orderitem(db: Session, orderitem_id: str) -> dict:
    orderitem = _get_or_raise(db, orderitem_id, repository)
    
    try:
        repository.delete_orderitem(db, orderitem_id)
        db.commit()
        return {"message": "Order item deleted successfully"}
    except Exception:
        db.rollback()
        raise


# Payment handlers
def create_payment(db: Session, data: PaymentCreate) -> PaymentResponse:
    # Validate order exists
    order = repository.get_by_id(db, data.order_id)
    if not order:
        raise HTTPException(status_code=404, detail=f"Order with id {data.order_id} not found")
    
    # Check if payment already exists for this order
    existing = repository.get_payment_by_order_id(db, data.order_id)
    if existing:
        raise HTTPException(status_code=409, detail=f"Payment already exists for order {data.order_id}")
    
    # Validate payment amount matches order total
    if data.amount != order.total_amount:
        raise HTTPException(status_code=400, detail="Payment amount must match order total")
    
    try:
        payment = repository.create_payment(db, data.model_dump())
        db.commit()
        db.refresh(payment)
        return PaymentResponse.model_validate(payment)
    except Exception:
        db.rollback()
        raise


def get_payment(db: Session, payment_id: str) -> PaymentResponse:
    payment = repository.get_payment_by_id(db, payment_id)
    if not payment:
        raise HTTPException(status_code=404, detail=f"Payment with id {payment_id} not found")
    return PaymentResponse.model_validate(payment)


def list_payments(
    db: Session,
    limit: int,
    offset: int,
    order_id: Optional[str] = None,
    status: Optional[PaymentStatus] = None,
    payment_method: Optional[str] = None
) -> List[PaymentResponse]:
    filters = {}
    if order_id:
        filters["order_id"] = order_id
    if status:
        filters["status"] = status
    if payment_method:
        filters["payment_method"] = payment_method
    
    payments = repository.list_payments(db, limit, offset, **filters)
    return [PaymentResponse.model_validate(payment) for payment in payments]


def update_payment(db: Session, payment_id: str, data: PaymentUpdate) -> PaymentResponse:
    payment = repository.get_payment_by_id(db, payment_id)
    if not payment:
        raise HTTPException(status_code=404, detail=f"Payment with id {payment_id} not found")
    
    update_data = data.model_dump(exclude_unset=True)
    
    # Validate order if being updated
    if "order_id" in update_data:
        order = repository.get_by_id(db, update_data["order_id"])
        if not order:
            raise HTTPException(status_code=404, detail=f"Order with id {update_data['order_id']} not found")
        
        # Check if another payment exists for the new order
        existing = repository.get_payment_by_order_id(db, update_data["order_id"])
        if existing and existing.id != payment_id:
            raise HTTPException(status_code=409, detail=f"Payment already exists for order {update_data['order_id']}")
    
    try:
        updated_payment = repository.update_payment(db, payment_id, update_data)
        db.commit()
        db.refresh(updated_payment)
        return PaymentResponse.model_validate(updated_payment)
    except Exception:
        db.rollback()
        raise


def delete_payment(db: Session, payment_id: str) -> dict:
    payment = repository.get_payment_by_id(db, payment_id)
    if not payment:
        raise HTTPException(status_code=404, detail=f"Payment with id {payment_id} not found")
    
    try:
        repository.delete_payment(db, payment_id)
        db.commit()
        return {"message": "Payment deleted successfully"}
    except Exception:
        db.rollback()
        raise


# Shipment handlers
def create_shipment(db: Session, data: ShipmentCreate) -> ShipmentResponse:
    # Validate order exists
    order = repository.get_by_id(db, data.order_id)
    if not order:
        raise HTTPException(status_code=404, detail=f"Order with id {data.order_id} not found")
    
    # Check if shipment already exists for this order
    existing = repository.get_shipment_by_order_id(db, data.order_id)
    if existing:
        raise HTTPException(status_code=409, detail=f"Shipment already exists for order {data.order_id}")
    
    try:
        shipment = repository.create_shipment(db, data.model_dump())
        db.commit()
        db.refresh(shipment)
        return ShipmentResponse.model_validate(shipment)
    except Exception:
        db.rollback()
        raise


def get_shipment(db: Session, shipment_id: str) -> ShipmentResponse:
    shipment = repository.get_shipment_by_id(db, shipment_id)
    if not shipment:
        raise HTTPException(status_code=404, detail=f"Shipment with id {shipment_id} not found")
    return ShipmentResponse.model_validate(shipment)


def list_shipments(
    db: Session,
    limit: int,
    offset: int,
    order_id: Optional[str] = None,
    status: Optional[ShipmentStatus] = None,
    carrier: Optional[str] = None,
    tracking_number: Optional[str] = None
) -> List[ShipmentResponse]:
    filters = {}
    if order_id:
        filters["order_id"] = order_id
    if status:
        filters["status"] = status
    if carrier:
        filters["carrier"] = carrier
    if tracking_number:
        filters["tracking_number"] = tracking_number
    
    shipments = repository.list_shipments(db, limit, offset, **filters)
    return [ShipmentResponse.model_validate(shipment) for shipment in shipments]


def update_shipment(db: Session, shipment_id: str, data: ShipmentUpdate) -> ShipmentResponse:
    shipment = repository.get_shipment_by_id(db, shipment_id)
    if not shipment:
        raise HTTPException(status_code=404, detail=f"Shipment with id {shipment_id} not found")
    
    update_data = data.model_dump(exclude_unset=True)
    
    # Validate order if being updated
    if "order_id" in update_data:
        order = repository.get_by_id(db, update_data["order_id"])
        if not order:
            raise HTTPException(status_code=404, detail=f"Order with id {update_data['order_id']} not found")
        
        # Check if another shipment exists for the new order
        existing = repository.get_shipment_by_order_id(db, update_data["order_id"])
        if existing and existing.id != shipment_id:
            raise HTTPException(status_code=409, detail=f"Shipment already exists for order {update_data['order_id']}")
    
    try:
        updated_shipment = repository.update_shipment(db, shipment_id, update_data)
        db.commit()
        db.refresh(updated_shipment)
        return ShipmentResponse.model_validate(updated_shipment)
    except Exception:
        db.rollback()
        raise


def delete_shipment(db: Session, shipment_id: str) -> dict:
    shipment = repository.get_shipment_by_id(db, shipment_id)
    if not shipment:
        raise HTTPException(status_code=404, detail=f"Shipment with id {shipment_id} not found")
    
    try:
        repository.delete_shipment(db, shipment_id)
        db.commit()
        return {"message": "Shipment deleted successfully"}
    except Exception:
        db.rollback()
        raise


def get_order_tracking(db: Session, order_id: str) -> dict:
    order = _get_or_raise(db, order_id, repository)
    
    shipment = repository.get_shipment_by_order_id(db, order_id)
    if not shipment:
        raise HTTPException(status_code=404, detail="Shipment information not available for this order")
    
    return {
        "order_id": order.id,
        "order_number": order.order_number,
        "order_status": order.status,
        "carrier": shipment.carrier,
        "tracking_number": shipment.tracking_number,
        "shipping_method": shipment.shipping_method,
        "shipment_status": shipment.status,
        "shipped_at": shipment.shipped_at,
        "estimated_delivery_date": shipment.estimated_delivery_date,
        "delivered_at": shipment.delivered_at
    }