import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { CreateCouponDto } from '../dto/create-coupon.dto';
import { UpdateCouponDto } from '../dto/update-coupon.dto';
import { FindCouponsDto } from '../dto/find-coupons.dto';
import { CouponRepository } from '../repositories/coupon.repository';
import { RestRepository } from '../../restaurants/repositories/rest.repository';
import { EntityManager } from '@mikro-orm/postgresql';
import { RequiredEntityData } from '@mikro-orm/core';
import { Coupon } from '../entities/coupon.entity';
import { CouponType } from '../interface/coupon';
import { CouponMessage, RestMessage } from 'src/common/enums/message.enum';
import { randomBytes } from 'node:crypto';

@Injectable()
export class CouponService {
  constructor(
    private readonly couponRepository: CouponRepository,
    private readonly restRepository: RestRepository,
    private readonly em: EntityManager,
  ) { }

  async create(restId: string, createCouponDto: CreateCouponDto): Promise<Coupon> {
    const restaurant = await this.restRepository.findOne({ id: restId });
    if (!restaurant) {
      throw new NotFoundException(RestMessage.NOT_FOUND);
    }

    // Check if code already exists
    const existingCoupon = await this.couponRepository.findOne({ code: createCouponDto.code });
    if (existingCoupon) {
      throw new BadRequestException(CouponMessage.CODE_ALREADY_EXISTS);
    }

    // Validate dates
    if (createCouponDto.startDate && createCouponDto.endDate) {
      const startDate = new Date(createCouponDto.startDate);
      const endDate = new Date(createCouponDto.endDate);
      if (endDate <= startDate) {
        throw new BadRequestException(CouponMessage.END_DATE_MUST_BE_AFTER_START_DATE);
      }
    }

    // Validate percentage coupon
    if (createCouponDto.type === CouponType.PERCENTAGE) {
      if (createCouponDto.value < 0 || createCouponDto.value > 100) {
        throw new BadRequestException(CouponMessage.PERCENTAGE_MUST_BE_BETWEEN_0_AND_100);
      }
    }

    const data: RequiredEntityData<Coupon> = {
      restaurant,
      code: createCouponDto.code,
      name: createCouponDto.name,
      description: createCouponDto.description,
      type: createCouponDto.type,
      value: createCouponDto.value,
      maxDiscount: createCouponDto.maxDiscount,
      minOrderAmount: createCouponDto.minOrderAmount,
      maxUses: createCouponDto.maxUses,
      maxUsesPerUser: createCouponDto.maxUsesPerUser,
      startDate: createCouponDto.startDate ? new Date(createCouponDto.startDate) : undefined,
      endDate: createCouponDto.endDate ? new Date(createCouponDto.endDate) : undefined,
      isActive: createCouponDto.isActive ?? true,
      usedCount: 0,
      foodCategories: createCouponDto.foodCategories,
      foods: createCouponDto.foods,
      userPhone: createCouponDto.userPhone,
    };

    const coupon = this.couponRepository.create(data);
    if (!coupon) {
      throw new Error('Failed to create coupon entity');
    }

    await this.em.persistAndFlush(coupon);
    return coupon;
  }

  findAll(restId: string, dto: FindCouponsDto) {
    return this.couponRepository.findAllPaginated(restId, dto);
  }

  async findById(id: string): Promise<Coupon> {
    const coupon = await this.couponRepository.findOne({ id }, { populate: ['restaurant'] });
    if (!coupon) {
      throw new NotFoundException(CouponMessage.NOT_FOUND);
    }
    return coupon;
  }

  async findByCode(code: string, restId: string): Promise<Coupon> {
    const coupon = await this.couponRepository.findOne({ code, restaurant: { id: restId } });
    if (!coupon) {
      throw new NotFoundException(CouponMessage.NOT_FOUND);
    }
    return coupon;
  }

  async update(id: string, dto: UpdateCouponDto): Promise<Coupon> {
    const coupon = await this.couponRepository.findOne({ id });
    if (!coupon) {
      throw new NotFoundException(CouponMessage.NOT_FOUND);
    }

    // Check if code is being changed and if it already exists
    if (dto.code && dto.code !== coupon.code) {
      const existingCoupon = await this.couponRepository.findOne({ code: dto.code });
      if (existingCoupon) {
        throw new BadRequestException(CouponMessage.CODE_ALREADY_EXISTS);
      }
    }

    // Validate dates
    const startDate = dto.startDate ? new Date(dto.startDate) : coupon.startDate;
    const endDate = dto.endDate ? new Date(dto.endDate) : coupon.endDate;
    if (startDate && endDate && endDate <= startDate) {
      throw new BadRequestException(CouponMessage.END_DATE_MUST_BE_AFTER_START_DATE);
    }

    // Validate percentage coupon
    if (dto.type === CouponType.PERCENTAGE || coupon.type === CouponType.PERCENTAGE) {
      const value = dto.value ?? coupon.value;
      if (value < 0 || value > 100) {
        throw new BadRequestException(CouponMessage.PERCENTAGE_MUST_BE_BETWEEN_0_AND_100);
      }
    }

    // Update fields
    if (dto.code) coupon.code = dto.code;
    if (dto.name) coupon.name = dto.name;
    if (dto.description !== undefined) coupon.description = dto.description;
    if (dto.type) coupon.type = dto.type;
    if (dto.value !== undefined) coupon.value = dto.value;
    if (dto.maxDiscount !== undefined) coupon.maxDiscount = dto.maxDiscount;
    if (dto.minOrderAmount !== undefined) coupon.minOrderAmount = dto.minOrderAmount;
    if (dto.maxUses !== undefined) coupon.maxUses = dto.maxUses;
    if (dto.maxUsesPerUser !== undefined) coupon.maxUsesPerUser = dto.maxUsesPerUser;
    if (dto.startDate !== undefined) coupon.startDate = dto.startDate ? new Date(dto.startDate) : undefined;
    if (dto.endDate !== undefined) coupon.endDate = dto.endDate ? new Date(dto.endDate) : undefined;
    if (dto.isActive !== undefined) coupon.isActive = dto.isActive;
    if (dto.foodCategories !== undefined) coupon.foodCategories = dto.foodCategories;
    if (dto.foods !== undefined) coupon.foods = dto.foods;
    if (dto.userPhone !== undefined) coupon.userPhone = dto.userPhone;

    await this.em.persistAndFlush(coupon);
    return coupon;
  }

  async remove(id: string) {
    const coupon = await this.couponRepository.findOne({ id });
    if (!coupon) {
      throw new NotFoundException(CouponMessage.NOT_FOUND);
    }
    coupon.deletedAt = new Date();
    await this.em.persistAndFlush(coupon);
  }

  /**
   * Validate  coupon
   */
  async validateCoupon(
    code: string,
    restId: string,
    orderAmount: number,
    userPhone: string,
  ): Promise<{
    valid: boolean;
    coupon?: Coupon;
    message?: string;
  }> {
    const coupon = await this.couponRepository.findOne(
      { code, restaurant: { id: restId } },
      { populate: ['restaurant'] },
    );

    if (!coupon) {
      return {
        valid: false,
        message: CouponMessage.NOT_FOUND,
      };
    }

    if (coupon.userPhone) {
      if (userPhone !== coupon.userPhone) {
        return {
          valid: false,
          message: CouponMessage.COUPON_RESTRICTED_TO_USER,
        };
      }
    }
    // Check if coupon is active
    if (!coupon.isActive) {
      return {
        valid: false,
        message: CouponMessage.COUPON_INACTIVE,
      };
    }

    // Check date validity
    const now = new Date();
    if (coupon.startDate && now < coupon.startDate) {
      return {
        valid: false,
        message: CouponMessage.COUPON_NOT_STARTED,
      };
    }

    if (coupon.endDate && now > coupon.endDate) {
      return {
        valid: false,
        message: CouponMessage.COUPON_EXPIRED,
      };
    }

    // Check max uses
    if (coupon.maxUses && coupon.usedCount >= coupon.maxUses) {
      return {
        valid: false,
        message: CouponMessage.COUPON_MAX_USES_REACHED,
      };
    }

    // Check minimum order amount
    if (coupon.minOrderAmount && orderAmount < coupon.minOrderAmount) {
      return {
        valid: false,
        message: CouponMessage.MIN_ORDER_AMOUNT_NOT_MET,
      };
    }

    return {
      valid: true,
      coupon,
    };
  }

  /**
   * Increment coupon usage count
   */
  async incrementUsage(id: string): Promise<void> {
    const coupon = await this.couponRepository.findOne({ id });
    if (coupon) {
      coupon.usedCount += 1;
      await this.em.persistAndFlush(coupon);
    }
  }

  generateShortCode(restaurantSlug: string, length = 5) {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    const bytes = randomBytes(length);
    let result = '';

    for (let i = 0; i < bytes.length; i++) {
      result += chars[bytes[i] % chars.length];
    }
    const prefix = restaurantSlug.slice(0, 2).toUpperCase();
    return prefix + '-' + result;
  }

  async generateCouponCode(restId: string): Promise<{ code: string }> {
    const restaurant = await this.restRepository.findOne({ id: restId });
    if (!restaurant) {
      throw new NotFoundException(RestMessage.NOT_FOUND);
    }
    const code = this.generateShortCode(restaurant.slug);
    const existing = await this.couponRepository.findOne({ code, restaurant: { id: restId } });

    if (existing) return this.generateCouponCode(restId);
    return { code };
  }
}
