import { Injectable } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql';
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
import { Delivery } from '../../delivery/entities/delivery.entity';
import { DeliveryFeeTypeEnum } from '../../delivery/interface/delivery';
import { CouponType } from '../../coupons/interface/coupon';
import { Food } from '../../foods/entities/food.entity';
import { Cart } from '../interfaces/cart.interface';
import { CouponService } from '../../coupons/providers/coupon.service';
import { GeographicUtils } from '../utils/geographic.utils';

@Injectable()
export class CartCalculationService {
  constructor(
    private readonly em: EntityManager,
    private readonly couponService: CouponService,
  ) {}

  /**
   * Calculate total price for a cart item
   */
  calculateItemTotalPrice(unitPrice: number, unitDiscount: number, quantity: number): number {
    const safeUnitPrice = Number(unitPrice) || 0;
    const safeUnitDiscount = Math.min(Number(unitDiscount) || 0, safeUnitPrice);
    const itemTotalPrice = safeUnitPrice * quantity;
    const itemTotalDiscount = safeUnitDiscount * quantity;
    return itemTotalPrice - itemTotalDiscount;
  }

  /**
   * Calculate items totals (subtotal, items discount, total items)
   */
  calculateItemsTotals(cart: Cart): { subTotal: number; itemsDiscount: number; totalItems: number } {
    let subTotal = 0;
    let itemsDiscount = 0;
    let totalItems = 0;

    for (const item of cart.items) {
      const unitPrice = Number(item.price) || 0;
      const unitDiscount = Math.min(Number(item.discount) || 0, unitPrice);
      const itemPrice = unitPrice * item.quantity;
      const itemDiscount = unitDiscount * item.quantity;
      subTotal += itemPrice;
      itemsDiscount += itemDiscount;
      totalItems += item.quantity;
      item.totalPrice = itemPrice - itemDiscount;
    }

    return { subTotal, itemsDiscount, totalItems };
  }

  /**
   * Calculate coupon discount
   */
  async calculateCouponDiscount(cart: Cart, subTotal: number, itemsDiscount: number): Promise<number> {
    if (!cart.coupon) return 0;

    const coupon = await this.getCouponRestrictionsOrClear(cart);
    if (!cart.coupon || !coupon) return 0;

    const hasCategoryRestriction = (coupon.foodCategories?.length ?? 0) > 0;
    const hasFoodRestriction = (coupon.foods?.length ?? 0) > 0;

    let eligibleItemsTotal = subTotal;
    let eligibleItemsDiscount = itemsDiscount;

    if (hasCategoryRestriction || hasFoodRestriction) {
      eligibleItemsTotal = 0;
      eligibleItemsDiscount = 0;

      const foodMap = await this.getFoodsInCartWithCategories(cart);

      for (const item of cart.items) {
        const food = foodMap.get(item.foodId);
        if (!food) continue;

        const matchesCategory =
          hasCategoryRestriction && food.category && (coupon.foodCategories ?? []).includes(food.category.id);
        const matchesFood = hasFoodRestriction && (coupon.foods ?? []).includes(food.id);

        if (matchesCategory || matchesFood) {
          const unitPrice = Number(item.price) || 0;
          const unitDiscount = Math.min(Number(item.discount) || 0, unitPrice);
          eligibleItemsTotal += unitPrice * item.quantity;
          eligibleItemsDiscount += unitDiscount * item.quantity;
        }
      }
    }

    const priceAfterItemDiscount = Math.max(0, eligibleItemsTotal - eligibleItemsDiscount);

    if (cart.coupon.type === CouponType.PERCENTAGE) {
      let discount = (priceAfterItemDiscount * cart.coupon.value) / 100;
      if (cart.coupon.maxDiscount && discount > cart.coupon.maxDiscount) {
        discount = cart.coupon.maxDiscount;
      }
      return discount;
    }

    return Math.min(cart.coupon.value, priceAfterItemDiscount);
  }

  /**
   * Calculate tax
   */
  async calculateTax(restaurantId: string, amountAfterDiscounts: number): Promise<number> {
    const restaurant = await this.em.findOne(Restaurant, { id: restaurantId });
    const vat = restaurant?.vat ? Number(restaurant.vat) : 0;
    if (!vat || vat <= 0) return 0;
    return (Math.max(0, amountAfterDiscounts) * vat) / 100;
  }

  /**
   * Calculate delivery fee
   */
  async calculateDeliveryFee(cart: Cart): Promise<number> {
    const deliveryMethodId = cart.deliveryMethodId;
    if (!deliveryMethodId) return 0;

    const deliveryMethod = await this.em.findOne(Delivery, { id: deliveryMethodId }, { populate: ['restaurant'] });
    if (!deliveryMethod || !deliveryMethod.enabled) return 0;

    // If not distance based, return fixed delivery fee
    if (deliveryMethod.deliveryFeeType !== DeliveryFeeTypeEnum.DISTANCE_BASED) {
      return Number(deliveryMethod.deliveryFee) || 0;
    }

    // For distance based calculation we need restaurant and user coordinates
    const restaurant = (deliveryMethod as any).restaurant as Restaurant | undefined;
    const userAddr = cart.userAddress;

    if (!restaurant || restaurant.latitude == null || restaurant.longitude == null) {
      // fallback to configured fixed fee when coordinates are missing
      return Number(deliveryMethod.deliveryFee) || 0;
    }

    if (!userAddr || userAddr.latitude == null || userAddr.longitude == null) {
      return Number(deliveryMethod.deliveryFee) || 0;
    }

    const restLat = Number(restaurant.latitude);
    const restLng = Number(restaurant.longitude);
    const userLat = Number(userAddr.latitude);
    const userLng = Number(userAddr.longitude);

    const distanceKm = GeographicUtils.getDistanceKmRounded(restLat, restLng, userLat, userLng);

    // Try to read either possible property names (legacy vs new)
    const perKm = Number(deliveryMethod.perKilometerFee);
    const minFee = Number(deliveryMethod.distanceBasedMinCost);

    let fee = 0;
    if (perKm <= 0) {
      fee = Number(deliveryMethod.deliveryFee) || 0;
    } else {
      fee = distanceKm * perKm;
    }

    if (minFee > 0 && fee < minFee) fee = minFee;

    return Math.max(0, Number(fee));
  }

  /**
   * Recalculate cart totals (including coupon discount and tax)
   */
  async recalculateCartTotals(cart: Cart): Promise<void> {
    const { subTotal, itemsDiscount, totalItems } = this.calculateItemsTotals(cart);
    cart.subTotal = subTotal;
    cart.itemsDiscount = itemsDiscount;

    const couponDiscount = await this.calculateCouponDiscount(cart, subTotal, itemsDiscount);
    cart.couponDiscount = couponDiscount;
    cart.totalDiscount = couponDiscount + itemsDiscount;

    cart.tax = await this.calculateTax(cart.restaurantId, Math.max(0, subTotal - cart.totalDiscount));
    cart.deliveryFee = await this.calculateDeliveryFee(cart);

    // total = subtotal – totalDiscount + tax + deliveryFee
    cart.total = Math.max(0, subTotal - cart.totalDiscount) + cart.tax + cart.deliveryFee;
    cart.totalItems = totalItems;
    cart.updatedAt = this.nowIso();
  }

  /**
   * Get coupon restrictions or clear if invalid
   */
  private async getCouponRestrictionsOrClear(cart: Cart): Promise<{
    isActive?: boolean;
    startDate?: Date;
    endDate?: Date;
    foodCategories?: string[];
    foods?: string[];
  } | null> {
    if (!cart.coupon) return null;

    try {
      const c = await this.couponService.findById(cart.coupon.couponId);
      const now = new Date();
      if (c.isActive === false) {
        cart.coupon = undefined;
        return null;
      }
      if (c.startDate && now < c.startDate) {
        cart.coupon = undefined;
        return null;
      }
      if (c.endDate && now > c.endDate) {
        cart.coupon = undefined;
        return null;
      }

      return {
        isActive: c.isActive,
        startDate: c.startDate,
        endDate: c.endDate,
        foodCategories: c.foodCategories,
        foods: c.foods,
      };
    } catch {
      cart.coupon = undefined;
      return null;
    }
  }

  /**
   * Get foods in cart with categories
   */
  private async getFoodsInCartWithCategories(cart: Cart): Promise<Map<string, Food>> {
    const foodIds = cart.items.map(item => item.foodId);
    if (foodIds.length === 0) return new Map();

    const foods = await this.em.find(Food, { id: { $in: foodIds } }, { populate: ['category'] });
    return new Map(foods.map(f => [f.id, f]));
  }

  /**
   * Get current ISO timestamp
   */
  private nowIso(): string {
    return new Date().toISOString();
  }
}

