/**
 * Coupon Service
 * Manages coupons and redemptions for billing
 *
 * @module BillingUsage
 */

import { Repository, FindOptionsWhere, LessThanOrEqual, MoreThanOrEqual } from 'typeorm';
import { AppDataSource } from '../../../shared/database/typeorm.config';
import { Coupon, DiscountType, DurationPeriod } from '../entities/coupon.entity';
import { CouponRedemption } from '../entities/coupon-redemption.entity';

export interface ServiceContext {
  tenantId: string;
  userId?: string;
}

export interface CreateCouponDto {
  code: string;
  name: string;
  description?: string;
  discountType: DiscountType;
  discountValue: number;
  currency?: string;
  applicablePlans?: string[];
  minAmount?: number;
  durationPeriod?: DurationPeriod;
  durationMonths?: number;
  maxRedemptions?: number;
  validFrom?: Date;
  validUntil?: Date;
  isActive?: boolean;
}

export interface UpdateCouponDto {
  name?: string;
  description?: string;
  discountType?: DiscountType;
  discountValue?: number;
  currency?: string;
  applicablePlans?: string[];
  minAmount?: number;
  durationPeriod?: DurationPeriod;
  durationMonths?: number;
  maxRedemptions?: number;
  validFrom?: Date;
  validUntil?: Date;
  isActive?: boolean;
}

export interface CouponFilters {
  isActive?: boolean;
  discountType?: DiscountType;
  validNow?: boolean;
}

export interface CouponValidationResult {
  isValid: boolean;
  error?: string;
  coupon?: Coupon;
  discountAmount?: number;
}

export class CouponService {
  private couponRepository: Repository<Coupon>;
  private redemptionRepository: Repository<CouponRedemption>;

  constructor() {
    this.couponRepository = AppDataSource.getRepository(Coupon);
    this.redemptionRepository = AppDataSource.getRepository(CouponRedemption);
  }

  async findAll(filters: CouponFilters = {}): Promise<Coupon[]> {
    const where: FindOptionsWhere<Coupon> = {};

    if (filters.isActive !== undefined) {
      where.isActive = filters.isActive;
    }

    if (filters.discountType) {
      where.discountType = filters.discountType;
    }

    return this.couponRepository.find({
      where,
      order: { createdAt: 'DESC' },
    });
  }

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

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

  async findValidCoupons(): Promise<Coupon[]> {
    const now = new Date();

    return this.couponRepository.find({
      where: {
        isActive: true,
        validFrom: LessThanOrEqual(now),
        validUntil: MoreThanOrEqual(now),
      },
      order: { discountValue: 'DESC' },
    });
  }

  async create(data: CreateCouponDto): Promise<Coupon> {
    const coupon = this.couponRepository.create({
      ...data,
      code: data.code.toUpperCase(),
    });
    return this.couponRepository.save(coupon);
  }

  async update(id: string, data: UpdateCouponDto): Promise<Coupon | null> {
    const coupon = await this.findById(id);
    if (!coupon) {
      return null;
    }

    // Note: code is not part of UpdateCouponDto - codes are immutable after creation

    Object.assign(coupon, data);
    return this.couponRepository.save(coupon);
  }

  async delete(id: string): Promise<boolean> {
    const result = await this.couponRepository.delete(id);
    return result.affected ? result.affected > 0 : false;
  }

  async deactivate(id: string): Promise<Coupon | null> {
    const coupon = await this.findById(id);
    if (!coupon) {
      return null;
    }

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

  async validate(
    ctx: ServiceContext,
    code: string,
    planId: string,
    amount: number
  ): Promise<CouponValidationResult> {
    const coupon = await this.findByCode(code);

    if (!coupon) {
      return { isValid: false, error: 'Coupon not found' };
    }

    if (!coupon.isActive) {
      return { isValid: false, error: 'Coupon is not active' };
    }

    const now = new Date();

    if (coupon.validFrom && coupon.validFrom > now) {
      return { isValid: false, error: 'Coupon is not yet valid' };
    }

    if (coupon.validUntil && coupon.validUntil < now) {
      return { isValid: false, error: 'Coupon has expired' };
    }

    if (
      coupon.maxRedemptions &&
      coupon.currentRedemptions >= coupon.maxRedemptions
    ) {
      return { isValid: false, error: 'Coupon has reached maximum redemptions' };
    }

    if (
      coupon.applicablePlans &&
      coupon.applicablePlans.length > 0 &&
      !coupon.applicablePlans.includes(planId)
    ) {
      return { isValid: false, error: 'Coupon is not valid for this plan' };
    }

    if (coupon.minAmount && amount < Number(coupon.minAmount)) {
      return {
        isValid: false,
        error: `Minimum purchase amount of ${coupon.minAmount} required`,
      };
    }

    // Check if tenant already redeemed this coupon
    const existingRedemption = await this.redemptionRepository.findOne({
      where: {
        couponId: coupon.id,
        tenantId: ctx.tenantId,
      },
    });

    if (existingRedemption) {
      return { isValid: false, error: 'Coupon already redeemed by this account' };
    }

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

    return {
      isValid: true,
      coupon,
      discountAmount: Math.round(discountAmount * 100) / 100,
    };
  }

  async redeem(
    ctx: ServiceContext,
    code: string,
    planId: string,
    amount: number,
    subscriptionId?: string
  ): Promise<CouponRedemption | null> {
    const validation = await this.validate(ctx, code, planId, amount);

    if (!validation.isValid || !validation.coupon || !validation.discountAmount) {
      return null;
    }

    const coupon = validation.coupon;

    // Calculate expiration date based on duration
    let expiresAt: Date | undefined;
    if (coupon.durationPeriod === 'months' && coupon.durationMonths) {
      expiresAt = new Date();
      expiresAt.setMonth(expiresAt.getMonth() + coupon.durationMonths);
    }

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

    await this.redemptionRepository.save(redemption);

    // Increment redemption count
    coupon.currentRedemptions++;
    await this.couponRepository.save(coupon);

    return redemption;
  }

  async getTenantRedemptions(ctx: ServiceContext): Promise<CouponRedemption[]> {
    return this.redemptionRepository.find({
      where: { tenantId: ctx.tenantId },
      relations: ['coupon'],
      order: { redeemedAt: 'DESC' },
    });
  }

  async getActiveRedemption(ctx: ServiceContext): Promise<CouponRedemption | null> {
    const now = new Date();

    const redemptions = await this.redemptionRepository.find({
      where: { tenantId: ctx.tenantId },
      relations: ['coupon'],
    });

    // Find redemption that is still active
    for (const redemption of redemptions) {
      if (!redemption.expiresAt || redemption.expiresAt > now) {
        if (redemption.coupon.durationPeriod === 'forever') {
          return redemption;
        }
        if (redemption.coupon.durationPeriod === 'once') {
          // Check if it's been applied
          continue;
        }
        if (
          redemption.coupon.durationPeriod === 'months' &&
          redemption.expiresAt &&
          redemption.expiresAt > now
        ) {
          return redemption;
        }
      }
    }

    return null;
  }

  async getCouponRedemptions(couponId: string): Promise<CouponRedemption[]> {
    return this.redemptionRepository.find({
      where: { couponId },
      order: { redeemedAt: 'DESC' },
    });
  }

  async getCouponStats(couponId: string): Promise<{
    totalRedemptions: number;
    totalDiscountGiven: number;
    uniqueTenants: number;
  }> {
    const redemptions = await this.getCouponRedemptions(couponId);

    const tenantIds = new Set<string>();
    let totalDiscount = 0;

    for (const r of redemptions) {
      tenantIds.add(r.tenantId);
      totalDiscount += Number(r.discountAmount);
    }

    return {
      totalRedemptions: redemptions.length,
      totalDiscountGiven: Math.round(totalDiscount * 100) / 100,
      uniqueTenants: tenantIds.size,
    };
  }

  async generateCode(prefix: string = 'PROMO'): Promise<string> {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    let code: string;
    let exists = true;

    while (exists) {
      let randomPart = '';
      for (let i = 0; i < 6; i++) {
        randomPart += chars.charAt(Math.floor(Math.random() * chars.length));
      }
      code = `${prefix}-${randomPart}`;
      exists = !!(await this.findByCode(code));
    }

    return code!;
  }

  async getOverallStats(): Promise<{
    totalCoupons: number;
    activeCoupons: number;
    totalRedemptions: number;
    totalDiscountGiven: number;
  }> {
    const coupons = await this.couponRepository.find();
    const redemptions = await this.redemptionRepository.find();

    let totalDiscount = 0;
    for (const r of redemptions) {
      totalDiscount += Number(r.discountAmount);
    }

    return {
      totalCoupons: coupons.length,
      activeCoupons: coupons.filter((c) => c.isActive).length,
      totalRedemptions: redemptions.length,
      totalDiscountGiven: Math.round(totalDiscount * 100) / 100,
    };
  }
}
