import { Injectable, Inject, NotFoundException, BadRequestException } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { COUPON_REPOSITORY, ICouponRepository } from '../../domain/repositories/coupon.repository.interface';
import { REDEMPTION_REPOSITORY, IRedemptionRepository } from '../../domain/repositories/redemption.repository.interface';
import { Coupon, CouponStatus } from '../../domain/entities/coupon.entity';

@Injectable()
export class RedemptionService {
  constructor(
    @Inject(COUPON_REPOSITORY)
    private readonly couponRepo: ICouponRepository,
    @Inject(REDEMPTION_REPOSITORY)
    private readonly redemptionRepo: IRedemptionRepository,
    private readonly dataSource: DataSource,
  ) {}

  /**
   * Redeem via QR code scan.
   * The qrCode is expected to contain the coupon ID (UUID).
   */
  async scanRedeem(issuerId: string, qrCode: string, storeId?: string) {
    // Parse coupon ID from QR code (assumes QR contains the coupon UUID)
    const couponId = qrCode.trim();
    return this.processRedemption(issuerId, couponId, 'scan', storeId);
  }

  /**
   * Redeem via manual coupon code entry.
   * The couponCode is expected to be the coupon ID (UUID).
   */
  async manualRedeem(issuerId: string, couponCode: string, storeId?: string) {
    const couponId = couponCode.trim();
    return this.processRedemption(issuerId, couponId, 'manual', storeId);
  }

  /**
   * Batch redeem multiple coupons.
   */
  async batchRedeem(issuerId: string, couponCodes: string[], storeId?: string) {
    const results: Array<{ couponCode: string; success: boolean; error?: string; redemption?: any }> = [];

    for (const code of couponCodes) {
      try {
        const redemption = await this.processRedemption(issuerId, code.trim(), 'batch', storeId);
        results.push({ couponCode: code, success: true, redemption });
      } catch (err: any) {
        results.push({ couponCode: code, success: false, error: err.message });
      }
    }

    const successCount = results.filter((r) => r.success).length;
    const failCount = results.filter((r) => !r.success).length;

    return { results, successCount, failCount, total: couponCodes.length };
  }

  /**
   * Core redemption logic with pessimistic lock.
   */
  private async processRedemption(
    issuerId: string,
    couponId: string,
    method: string,
    storeId?: string,
  ) {
    return this.dataSource.transaction(async (manager) => {
      const coupon = await manager.findOne(Coupon, {
        where: { id: couponId },
        lock: { mode: 'pessimistic_write' },
      });

      if (!coupon) throw new NotFoundException(`Coupon not found: ${couponId}`);
      if (coupon.issuerId !== issuerId) {
        throw new BadRequestException('Coupon does not belong to this issuer');
      }
      if (coupon.status === CouponStatus.REDEEMED) {
        throw new BadRequestException('Coupon already redeemed');
      }
      if (coupon.status === CouponStatus.EXPIRED) {
        throw new BadRequestException('Coupon has expired');
      }
      if (coupon.status === CouponStatus.RECALLED) {
        throw new BadRequestException('Coupon has been recalled');
      }
      if (![CouponStatus.SOLD, CouponStatus.IN_CIRCULATION].includes(coupon.status as CouponStatus)) {
        throw new BadRequestException(`Coupon is not in a redeemable state (current: ${coupon.status})`);
      }

      // Update coupon status
      coupon.status = CouponStatus.REDEEMED;
      await manager.save(coupon);

      // Create redemption record
      const redemption = await this.redemptionRepo.create({
        couponId: coupon.id,
        issuerId,
        userId: coupon.ownerUserId || issuerId, // use owner if available
        storeId: storeId || null,
        method,
        amount: coupon.faceValue,
        status: 'completed',
      });

      return redemption;
    });
  }

  async listRedemptions(
    issuerId: string,
    page: number,
    limit: number,
    startDate?: string,
    endDate?: string,
  ) {
    const [items, total] = await this.redemptionRepo.findAndCount({
      issuerId,
      startDate,
      endDate,
      page,
      limit,
    });
    return { items, total, page, limit };
  }

  async getTodayStats(issuerId: string) {
    return this.redemptionRepo.getTodayStats(issuerId);
  }
}
