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 PromotionCreate, PromotionUpdate, PromotionusageCreate, PromotionusageUpdate
from user_management import repository as user_repo
from order_management import repository as order_repo


def _get_or_raise(db: Session, entity_id: str, repo_module) -> object:
    entity = repo_module.get_by_id(db, entity_id)
    if not entity:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Entity not found")
    return entity


def create_promotion(db: Session, data: PromotionCreate):
    if data.code:
        existing = repository.get_by_code(db, data.code)
        if existing:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail="Promotion code already exists"
            )
    
    if data.expires_at <= data.starts_at:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Expiration date must be after start date"
        )
    
    if data.promotion_type == "Percentage Discount" and not data.discount_percentage:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Discount percentage is required for percentage discount promotions"
        )
    
    if data.promotion_type == "Fixed Discount" and not data.discount_amount:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Discount amount is required for fixed discount promotions"
        )
    
    try:
        promotion = repository.create(db, data.model_dump())
        db.commit()
        db.refresh(promotion)
        return promotion
    except Exception:
        db.rollback()
        raise


def list_promotions(db: Session, limit: int, offset: int, is_active: Optional[bool] = None, promotion_type: Optional[str] = None, search: Optional[str] = None):
    return repository.list_all(db, limit, offset, is_active=is_active, promotion_type=promotion_type, search=search)


def get_promotion(db: Session, promotion_id: str):
    return _get_or_raise(db, promotion_id, repository)


def get_promotion_by_code(db: Session, code: str):
    promotion = repository.get_by_code(db, code)
    if not promotion:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Promotion not found")
    return promotion


def update_promotion(db: Session, promotion_id: str, data: PromotionUpdate):
    promotion = _get_or_raise(db, promotion_id, repository)
    
    update_data = data.model_dump(exclude_unset=True)
    
    if "code" in update_data and update_data["code"]:
        if update_data["code"] != promotion.code:
            existing = repository.get_by_code(db, update_data["code"])
            if existing:
                raise HTTPException(
                    status_code=status.HTTP_409_CONFLICT,
                    detail="Promotion code already exists"
                )
    
    if "expires_at" in update_data or "starts_at" in update_data:
        starts = update_data.get("starts_at", promotion.starts_at)
        expires = update_data.get("expires_at", promotion.expires_at)
        if expires <= starts:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Expiration date must be after start date"
            )
    
    try:
        updated = repository.update(db, promotion_id, update_data)
        db.commit()
        db.refresh(updated)
        return updated
    except Exception:
        db.rollback()
        raise


def delete_promotion(db: Session, promotion_id: str):
    promotion = _get_or_raise(db, promotion_id, repository)
    
    try:
        repository.delete(db, promotion_id)
        db.commit()
        return {"message": "Promotion deleted successfully"}
    except Exception:
        db.rollback()
        raise


def validate_promotion(db: Session, code: str, user_id: str, order_total: float):
    promotion = repository.get_by_code(db, code)
    if not promotion:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Promotion not found")
    
    if not promotion.is_active:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Promotion is not active")
    
    now = datetime.utcnow()
    if now < promotion.starts_at:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Promotion has not started yet")
    
    if now > promotion.expires_at:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Promotion has expired")
    
    if promotion.max_uses is not None and promotion.current_uses >= promotion.max_uses:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Promotion usage limit reached")
    
    if promotion.minimum_purchase_amount and order_total < float(promotion.minimum_purchase_amount):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Minimum purchase amount of {promotion.minimum_purchase_amount} required"
        )
    
    return promotion


def create_promotion_usage(db: Session, data: PromotionusageCreate):
    promotion = _get_or_raise(db, data.promotion_id, repository)
    
    user = user_repo.get_by_id(db, data.user_id)
    if not user:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
    
    order = order_repo.get_by_id(db, data.order_id)
    if not order:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Order not found")
    
    try:
        usage = repository.create_usage(db, data.model_dump())
        
        promotion.current_uses += 1
        repository.update(db, promotion.id, {"current_uses": promotion.current_uses})
        
        db.commit()
        db.refresh(usage)
        return usage
    except Exception:
        db.rollback()
        raise


def list_promotion_usages(db: Session, limit: int, offset: int, promotion_id: Optional[str] = None, user_id: Optional[str] = None, order_id: Optional[str] = None):
    return repository.list_all_usages(db, limit, offset, promotion_id=promotion_id, user_id=user_id, order_id=order_id)


def get_promotion_usage(db: Session, usage_id: str):
    usage = repository.get_usage_by_id(db, usage_id)
    if not usage:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Promotion usage not found")
    return usage


def update_promotion_usage(db: Session, usage_id: str, data: PromotionusageUpdate):
    usage = repository.get_usage_by_id(db, usage_id)
    if not usage:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Promotion usage not found")
    
    update_data = data.model_dump(exclude_unset=True)
    
    if "promotion_id" in update_data:
        promotion = _get_or_raise(db, update_data["promotion_id"], repository)
    
    if "user_id" in update_data:
        user = user_repo.get_by_id(db, update_data["user_id"])
        if not user:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
    
    if "order_id" in update_data:
        order = order_repo.get_by_id(db, update_data["order_id"])
        if not order:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Order not found")
    
    try:
        updated = repository.update_usage(db, usage_id, update_data)
        db.commit()
        db.refresh(updated)
        return updated
    except Exception:
        db.rollback()
        raise


def delete_promotion_usage(db: Session, usage_id: str):
    usage = repository.get_usage_by_id(db, usage_id)
    if not usage:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Promotion usage not found")
    
    try:
        repository.delete_usage(db, usage_id)
        db.commit()
        return {"message": "Promotion usage deleted successfully"}
    except Exception:
        db.rollback()
        raise