from sqlalchemy.orm import Session
import uuid
from datetime import datetime
from fastapi import HTTPException
from typing import Optional, Dict, Any, List

from app.models.order import Order
from app.models.order_item import OrderItem
from app.models.shopping_cart import ShoppingCart
from app.models.cart_item import CartItem
from app.models.product import Product
from app.models.user_address import UserAddress
from app.schemas.order import OrderCreate, ShippingDetails
from app.services import point_service
from app.services import address_service


def generate_order_number() -> str:
    """Generate a unique order number based on timestamp and UUID"""
    timestamp = datetime.now().strftime("%Y%m%d")
    unique_id = str(uuid.uuid4().int)[:8]  # Take first 8 digits of UUID
    return f"{timestamp}-{unique_id}"


def create_order_from_cart(
    db: Session, 
    user_id: int, 
    shipping_details: Optional[Dict[str, Any]] = None,
    address_id: Optional[int] = None,
    save_address: bool = False,
    address_title: Optional[str] = None
) -> Order:
    """
    Create a new order from the user's shopping cart.
    
    Steps:
    1. Get the user's shopping cart
    2. Check if the cart exists and has items
    3. Check product availability (stock)
    4. Check if user has enough points
    5. Get shipping details (from address_id or directly provided)
    6. If save_address is True, save the shipping details as a new address
    7. Create the order and order items
    8. Update product stock
    9. Deduct points from user's balance
    10. Clear the shopping cart
    11. Return the created order
    """
    # Get user's shopping cart
    cart = db.query(ShoppingCart).filter(ShoppingCart.user_id == user_id).first()
    if not cart or not cart.items:
        raise HTTPException(status_code=404, detail="Shopping cart is empty")
    
    # Check product availability and calculate total points
    total_points = 0
    order_items_data = []
    
    for cart_item in cart.items:
        product = db.query(Product).filter(Product.id == cart_item.product_id).first()
        if not product:
            raise HTTPException(
                status_code=404, 
                detail=f"Product with ID {cart_item.product_id} not found"
            )
        
        # Check if product is active
        if product.status != 1:  # 1: Active, 2: Inactive, 3: Deleted
            raise HTTPException(
                status_code=400, 
                detail=f"Product '{product.name}' is not available for purchase"
            )
        
        # Check if there is enough stock
        if product.stock < cart_item.quantity:
            raise HTTPException(
                status_code=400, 
                detail=f"Not enough stock for product '{product.name}'. Available: {product.stock}, Requested: {cart_item.quantity}"
            )
        
        # Calculate points for this item
        item_total_points = product.points_required * cart_item.quantity
        total_points += item_total_points
        
        # Prepare order item data
        order_items_data.append({
            "product_id": product.id,
            "quantity": cart_item.quantity,
            "points_per_item": product.points_required,
            "total_points": item_total_points
        })
    
    # 사용자의 포인트 잔액 확인
    user_available_points = point_service.get_user_points(db, user_id)
    if user_available_points < total_points:
        raise HTTPException(
            status_code=400,
            detail=f"Not enough points. Required: {total_points}, Available: {user_available_points}"
        )
    
    # 주소 ID가 제공된 경우 해당 주소의 배송 정보를 가져옴
    if address_id:
        address = db.query(UserAddress).filter(
            UserAddress.id == address_id,
            UserAddress.user_id == user_id
        ).first()
        
        if not address:
            raise HTTPException(
                status_code=404,
                detail=f"Address with ID {address_id} not found"
            )
        
        # 주소를 배송 정보로 변환
        shipping_details = {
            "name": address.recipient_name,
            "address": f"{address.address_line1}, {address.address_line2 or ''}".strip(", "),
            "city": address.city,
            "postal_code": address.postal_code,
            "country": address.country,
            "phone": address.phone
        }
    
    # 배송 정보가 없으면 오류
    if not shipping_details:
        raise HTTPException(
            status_code=400,
            detail="Shipping details are required"
        )
    
    # 주소 저장 옵션이 활성화되고 직접 배송 정보를 입력한 경우
    saved_address_id = None
    if save_address and shipping_details and address_title and not address_id:
        # 주소 형식을 AddressCreate 스키마에 맞게 변환
        from app.schemas.user_address import AddressCreate
        
        # 주소 문자열에서 address_line1과 address_line2 분리 (쉼표로 구분된 경우)
        address_parts = shipping_details["address"].split(", ", 1)
        address_line1 = address_parts[0]
        address_line2 = address_parts[1] if len(address_parts) > 1 else None
        
        # 새 주소 생성
        new_address = AddressCreate(
            title=address_title,
            recipient_name=shipping_details["name"],
            address_line1=address_line1,
            address_line2=address_line2,
            city=shipping_details["city"],
            postal_code=shipping_details["postal_code"],
            country=shipping_details["country"],
            phone=shipping_details["phone"],
            is_default=False  # 기본값은 False로 설정
        )
        
        # 주소 저장 및 ID 반환
        saved_address = address_service.create_address(db, new_address, user_id)
        saved_address_id = saved_address.id
    
    # Create the order
    new_order = Order(
        user_id=user_id,
        order_number=generate_order_number(),
        total_points=total_points,
        shipping_details=shipping_details,
        status="PENDING",  # Initial status
        address_id=saved_address_id or address_id  # 저장된 주소 ID 또는 기존 주소 ID 사용
    )
    
    db.add(new_order)
    db.flush()  # Get the order ID without committing
    
    # Create order items
    for item_data in order_items_data:
        order_item = OrderItem(
            order_id=new_order.id,
            product_id=item_data["product_id"],
            quantity=item_data["quantity"],
            points_per_item=item_data["points_per_item"],
            total_points=item_data["total_points"]
        )
        db.add(order_item)
    
    # Update product stock
    for cart_item in cart.items:
        product = db.query(Product).filter(Product.id == cart_item.product_id).first()
        product.stock -= cart_item.quantity
    
    # 사용자 포인트 차감
    point_service.deduct_points_for_order(
        db=db,
        user_id=user_id,
        amount=total_points,
        order_number=new_order.order_number,
        order_id=new_order.id
    )
    
    # Clear the shopping cart by deleting all cart items
    for item in cart.items:
        db.delete(item)
    
    # Commit all changes
    db.commit()
    db.refresh(new_order)
    
    return new_order


def get_order_by_id(db: Session, order_id: int, user_id: int) -> Optional[Order]:
    """Get an order by ID, ensuring it belongs to the specified user"""
    order = db.query(Order).filter(
        Order.id == order_id,
        Order.user_id == user_id
    ).first()
    
    if order:
        # Populate product information for order items
        for item in order.items:
            product = db.query(Product).filter(Product.id == item.product_id).first()
            if product:
                item.product_name = product.name
                item.product_price = product.points_required
                item.product_image = product.thumbnail
    
    return order


def get_user_orders(db: Session, user_id: int, skip: int = 0, limit: int = 100) -> List[Order]:
    """Get all orders for a specific user"""
    orders = db.query(Order).filter(
        Order.user_id == user_id
    ).order_by(Order.created_at.desc()).offset(skip).limit(limit).all()
    
    # Populate product information for order items in all orders
    for order in orders:
        for item in order.items:
            product = db.query(Product).filter(Product.id == item.product_id).first()
            if product:
                item.product_name = product.name
                item.product_price = product.points_required
                item.product_image = product.thumbnail
    
    return orders


def cancel_order(db: Session, order_id: int, user_id: int) -> Order:
    """
    주문을 취소하고 관련 처리를 수행합니다.
    
    처리 항목:
    1. 주문 상태를 "CANCELLED"로 변경
    2. 제품 재고 복원
    3. 사용한 포인트 환불
    
    Returns:
        업데이트된 주문 객체
    """
    # 주문 조회 및 권한 확인
    order = get_order_by_id(db, order_id, user_id)
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
    
    # 취소 가능한 상태인지 확인
    cancelable_statuses = ["PENDING", "PROCESSING"]
    if order.status not in cancelable_statuses:
        raise HTTPException(
            status_code=400, 
            detail=f"Cannot cancel order in '{order.status}' status. Only {', '.join(cancelable_statuses)} orders can be cancelled."
        )
    
    # 주문 상태 변경
    order.status = "CANCELLED"
    
    # 제품 재고 복원
    for order_item in order.items:
        product = db.query(Product).filter(Product.id == order_item.product_id).first()
        if product:
            product.stock += order_item.quantity
    
    # 사용한 포인트 환불
    description = f"Refund for cancelled order #{order.order_number}"
    point_service.add_points(
        db=db,
        user_id=user_id,
        amount=order.total_points,  # 주문 시 차감된 총 포인트
        transaction_type="etc",     # 기존 트랜잭션 타입 중 가장 적합한 것으로 (필요시 'redeem' 등으로 변경)
        description=description
    )
    
    # 변경사항 저장
    db.commit()
    db.refresh(order)
    
    return order


def get_weekly_order_summary(db: Session, date: Optional[datetime] = None) -> Dict[str, Any]:
    """
    특정 주(week)의 주문 요약 정보를 제공합니다.
    - 주간 고유 사용자 수
    - 주간 총 주문 건수
    - 주간 총 포인트

    Args:
        db (Session): 데이터베이스 세션
        date (Optional[datetime]): 기준 날짜. 제공되지 않으면 오늘을 기준으로 합니다.

    Returns:
        Dict[str, Any]: 주간 주문 요약 정보를 포함하는 딕셔너리
    """
    from sqlalchemy import func, distinct
    from datetime import datetime, timedelta
    
    # 기준 날짜가 없으면 오늘을 기준으로 함
    if date is None:
        date = datetime.now()
    
    # 해당 주의 시작일과 종료일 계산 (월요일부터 일요일까지)
    start_of_week = date - timedelta(days=date.weekday())
    start_of_week = datetime(start_of_week.year, start_of_week.month, start_of_week.day, 0, 0, 0)
    
    end_of_week = start_of_week + timedelta(days=6)
    end_of_week = datetime(end_of_week.year, end_of_week.month, end_of_week.day, 23, 59, 59)
    
    # 성공한 주문만 계산 (취소 주문 제외)
    query = db.query(
        func.count(distinct(Order.user_id)).label("unique_users_count"),
        func.count(Order.id).label("order_count"),
        func.sum(Order.total_points).label("total_points")
    ).filter(
        Order.created_at.between(start_of_week, end_of_week),
        Order.status != "CANCELLED"  # 취소된 주문 제외
    )
    
    result = query.first()
    
    # None 값 처리
    unique_users_count = result.unique_users_count or 0
    order_count = result.order_count or 0
    total_points = result.total_points or 0
    
    return {
        "unique_users_count": unique_users_count,
        "order_count": order_count,
        "total_points": total_points,
        "start_date": start_of_week,
        "end_date": end_of_week
    } 