/**
 * Coupons Service
 *
 * Service for managing discount coupons and redemptions
 */

import { Repository, DataSource, LessThanOrEqual, MoreThanOrEqual, IsNull, Or } from 'typeorm';
import { Coupon, CouponRedemption, TenantSubscription, DiscountType } from '../entities/index.js';
import { logger } from '../../../shared/utils/logger.js';

export interface CreateCouponDto {
  code: string;
  name: string;
  description?: string;
  discountType: DiscountType;
  discountValue: number;
  currency?: string;
  applicablePlans?: string[];
  minAmount?: number;
  durationPeriod?: 'once' | 'forever' | 'months';
  durationMonths?: number;
  maxRedemptions?: number;
  validFrom?: Date;
  validUntil?: Date;
}

export interface UpdateCouponDto {
  name?: string;
  description?: string;
  maxRedemptions?: number;
  validUntil?: Date;
  isActive?: boolean;
}

export interface ApplyCouponResult {
  success: boolean;
  discountAmount: number;
  message: string;
  coupon?: Coupon;
}

export class CouponsService {
  private couponRepository: Repository<Coupon>;
  private redemptionRepository: Repository<CouponRedemption>;
  private subscriptionRepository: Repository<TenantSubscription>;

  constructor(private dataSource: DataSource) {
    this.couponRepository = dataSource.getRepository(Coupon);
    this.redemptionRepository = dataSource.getRepository(CouponRedemption);
    this.subscriptionRepository = dataSource.getRepository(TenantSubscription);
  }

  /**
   * Create a new coupon
   */
  async create(dto: CreateCouponDto): Promise<Coupon> {
    // Check if code already exists
    const existing = await this.couponRepository.findOne({
      where: { code: dto.code.toUpperCase() },
    });

    if (existing) {
      throw new Error(`Coupon with code ${dto.code} already exists`);
    }

    const coupon = this.couponRepository.create({
      code: dto.code.toUpperCase(),
      name: dto.name,
      description: dto.description,
      discountType: dto.discountType,
      discountValue: dto.discountValue,
      currency: dto.currency || 'MXN',
      applicablePlans: dto.applicablePlans || [],
      minAmount: dto.minAmount || 0,
      durationPeriod: dto.durationPeriod || 'once',
      durationMonths: dto.durationMonths,
      maxRedemptions: dto.maxRedemptions,
      validFrom: dto.validFrom,
      validUntil: dto.validUntil,
      isActive: true,
      currentRedemptions: 0,
    });

    const saved = await this.couponRepository.save(coupon);

    logger.info('Coupon created', { couponId: saved.id, code: saved.code });

    return saved;
  }

  /**
   * Find all coupons
   */
  async findAll(options?: { isActive?: boolean }): Promise<Coupon[]> {
    const query = this.couponRepository.createQueryBuilder('coupon');

    if (options?.isActive !== undefined) {
      query.andWhere('coupon.isActive = :isActive', { isActive: options.isActive });
    }

    return query.orderBy('coupon.createdAt', 'DESC').getMany();
  }

  /**
   * Find coupon by code
   */
  async findByCode(code: string): Promise<Coupon | null> {
    return this.couponRepository.findOne({
      where: { code: code.toUpperCase() },
    });
  }

  /**
   * Find coupon by ID
   */
  async findById(id: string): Promise<Coupon | null> {
    return this.couponRepository.findOne({
      where: { id },
      relations: ['redemptions'],
    });
  }

  /**
   * Update a coupon
   */
  async update(id: string, dto: UpdateCouponDto): Promise<Coupon> {
    const coupon = await this.findById(id);
    if (!coupon) {
      throw new Error('Coupon not found');
    }

    if (dto.name !== undefined) coupon.name = dto.name;
    if (dto.description !== undefined) coupon.description = dto.description;
    if (dto.maxRedemptions !== undefined) coupon.maxRedemptions = dto.maxRedemptions;
    if (dto.validUntil !== undefined) coupon.validUntil = dto.validUntil;
    if (dto.isActive !== undefined) coupon.isActive = dto.isActive;

    return this.couponRepository.save(coupon);
  }

  /**
   * Validate if a coupon can be applied
   */
  async validateCoupon(
    code: string,
    tenantId: string,
    planId?: string,
    amount?: number
  ): Promise<ApplyCouponResult> {
    const coupon = await this.findByCode(code);

    if (!coupon) {
      return { success: false, discountAmount: 0, message: 'Cupón no encontrado' };
    }

    if (!coupon.isActive) {
      return { success: false, discountAmount: 0, message: 'Cupón inactivo' };
    }

    // Check validity dates
    const now = new Date();
    if (coupon.validFrom && now < coupon.validFrom) {
      return { success: false, discountAmount: 0, message: 'Cupón aún no válido' };
    }
    if (coupon.validUntil && now > coupon.validUntil) {
      return { success: false, discountAmount: 0, message: 'Cupón expirado' };
    }

    // Check max redemptions
    if (coupon.maxRedemptions && coupon.currentRedemptions >= coupon.maxRedemptions) {
      return { success: false, discountAmount: 0, message: 'Cupón agotado' };
    }

    // Check if already redeemed by this tenant
    const existingRedemption = await this.redemptionRepository.findOne({
      where: { couponId: coupon.id, tenantId },
    });
    if (existingRedemption) {
      return { success: false, discountAmount: 0, message: 'Cupón ya utilizado' };
    }

    // Check applicable plans
    if (planId && coupon.applicablePlans.length > 0) {
      if (!coupon.applicablePlans.includes(planId)) {
        return { success: false, discountAmount: 0, message: 'Cupón no aplicable a este plan' };
      }
    }

    // Check minimum amount
    if (amount && coupon.minAmount > 0 && amount < coupon.minAmount) {
      return {
        success: false,
        discountAmount: 0,
        message: `Monto mínimo requerido: ${coupon.minAmount} ${coupon.currency}`,
      };
    }

    // Calculate discount
    let discountAmount = 0;
    if (amount) {
      if (coupon.discountType === 'percentage') {
        discountAmount = (amount * coupon.discountValue) / 100;
      } else {
        discountAmount = Math.min(coupon.discountValue, amount);
      }
    }

    return {
      success: true,
      discountAmount,
      message: 'Cupón válido',
      coupon,
    };
  }

  /**
   * Apply a coupon to a subscription
   */
  async applyCoupon(
    code: string,
    tenantId: string,
    subscriptionId: string,
    amount: number
  ): Promise<CouponRedemption> {
    const validation = await this.validateCoupon(code, tenantId, undefined, amount);

    if (!validation.success || !validation.coupon) {
      throw new Error(validation.message);
    }

    const coupon = validation.coupon;

    // Create redemption record
    const redemption = this.redemptionRepository.create({
      couponId: coupon.id,
      tenantId,
      subscriptionId,
      discountAmount: validation.discountAmount,
      expiresAt: this.calculateRedemptionExpiry(coupon),
    });

    // Update coupon redemption count
    coupon.currentRedemptions += 1;

    // Save in transaction
    await this.dataSource.transaction(async (manager) => {
      await manager.save(redemption);
      await manager.save(coupon);
    });

    logger.info('Coupon applied', {
      couponId: coupon.id,
      code: coupon.code,
      tenantId,
      subscriptionId,
      discountAmount: validation.discountAmount,
    });

    return redemption;
  }

  /**
   * Calculate when a redemption expires based on coupon duration
   */
  private calculateRedemptionExpiry(coupon: Coupon): Date | undefined {
    if (coupon.durationPeriod === 'forever') {
      return undefined;
    }

    if (coupon.durationPeriod === 'once') {
      // Expires at end of current billing period (30 days)
      const expiry = new Date();
      expiry.setDate(expiry.getDate() + 30);
      return expiry;
    }

    if (coupon.durationPeriod === 'months' && coupon.durationMonths) {
      const expiry = new Date();
      expiry.setMonth(expiry.getMonth() + coupon.durationMonths);
      return expiry;
    }

    return undefined;
  }

  /**
   * Get active redemptions for a tenant
   */
  async getActiveRedemptions(tenantId: string): Promise<CouponRedemption[]> {
    const now = new Date();

    return this.redemptionRepository.find({
      where: [
        { tenantId, expiresAt: IsNull() },
        { tenantId, expiresAt: MoreThanOrEqual(now) },
      ],
      relations: ['coupon'],
    });
  }

  /**
   * Deactivate a coupon
   */
  async deactivate(id: string): Promise<Coupon> {
    const coupon = await this.findById(id);
    if (!coupon) {
      throw new Error('Coupon not found');
    }

    coupon.isActive = false;
    return this.couponRepository.save(coupon);
  }

  /**
   * Get coupon statistics
   */
  async getStats(id: string): Promise<{
    totalRedemptions: number;
    totalDiscountGiven: number;
    activeRedemptions: number;
  }> {
    const coupon = await this.findById(id);
    if (!coupon) {
      throw new Error('Coupon not found');
    }

    const now = new Date();

    const redemptions = await this.redemptionRepository.find({
      where: { couponId: id },
    });

    const totalDiscountGiven = redemptions.reduce(
      (sum, r) => sum + Number(r.discountAmount),
      0
    );

    const activeRedemptions = redemptions.filter(
      (r) => !r.expiresAt || r.expiresAt > now
    ).length;

    return {
      totalRedemptions: redemptions.length,
      totalDiscountGiven,
      activeRedemptions,
    };
  }
}
