import { Injectable, Inject, NotFoundException, BadRequestException } from '@nestjs/common';
import { COUPON_REPOSITORY, ICouponRepository } from '../../domain/repositories/coupon.repository.interface';
import { COUPON_RULE_REPOSITORY, ICouponRuleRepository } from '../../domain/repositories/coupon-rule.repository.interface';
import { ISSUER_REPOSITORY, IIssuerRepository } from '../../domain/repositories/issuer.repository.interface';
import { Coupon, CouponStatus } from '../../domain/entities/coupon.entity';
import { CouponRule } from '../../domain/entities/coupon-rule.entity';
import { Issuer } from '../../domain/entities/issuer.entity';

export interface ListCouponsFilters {
  page?: number;
  limit?: number;
  status?: string;
  issuerId?: string;
  category?: string;
  search?: string;
}

export interface CouponDetailResult {
  coupon: Coupon;
  rules: CouponRule[];
  issuer: Issuer | null;
  soldCount: number;
  redemptionMetrics: {
    totalSupply: number;
    remainingSupply: number;
    soldCount: number;
    soldPercentage: number;
  };
}

@Injectable()
export class AdminCouponService {
  constructor(
    @Inject(COUPON_REPOSITORY)
    private readonly couponRepo: ICouponRepository,
    @Inject(COUPON_RULE_REPOSITORY)
    private readonly ruleRepo: ICouponRuleRepository,
    @Inject(ISSUER_REPOSITORY)
    private readonly issuerRepo: IIssuerRepository,
  ) {}

  /**
   * List coupons with pagination, filters, and issuer join info.
   */
  async listCoupons(filters: ListCouponsFilters) {
    const page = filters.page || 1;
    const limit = filters.limit || 20;

    const [items, total] = await this.couponRepo.findAndCountWithIssuerJoin({
      status: filters.status,
      issuerId: filters.issuerId,
      category: filters.category,
      search: filters.search,
      page,
      limit,
    });

    // Enrich each coupon with issuer name
    const issuerIds = [...new Set(items.map((c) => c.issuerId))];
    const issuers = await this.issuerRepo.findByIds(issuerIds);
    const issuerMap = new Map(issuers.map((i) => [i.id, i]));

    const enrichedItems = items.map((coupon) => ({
      ...coupon,
      issuerName: issuerMap.get(coupon.issuerId)?.companyName || null,
    }));

    return {
      items: enrichedItems,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  /**
   * Get full coupon detail with rules, issuer info, and metrics.
   */
  async getCouponDetail(id: string): Promise<CouponDetailResult> {
    const coupon = await this.couponRepo.findById(id);
    if (!coupon) throw new NotFoundException('Coupon not found');

    const rules = await this.ruleRepo.findByCouponId(id);
    const issuer = await this.issuerRepo.findById(coupon.issuerId);

    const soldCount = coupon.totalSupply - coupon.remainingSupply;
    const soldPercentage =
      coupon.totalSupply > 0
        ? Math.round((soldCount / coupon.totalSupply) * 10000) / 100
        : 0;

    return {
      coupon,
      rules,
      issuer,
      soldCount,
      redemptionMetrics: {
        totalSupply: coupon.totalSupply,
        remainingSupply: coupon.remainingSupply,
        soldCount,
        soldPercentage,
      },
    };
  }

  /**
   * Approve a coupon: set status from DRAFT to ACTIVE.
   */
  async approveCoupon(id: string): Promise<Coupon> {
    const coupon = await this.couponRepo.findById(id);
    if (!coupon) throw new NotFoundException('Coupon not found');

    if (coupon.status !== CouponStatus.MINTED) {
      throw new BadRequestException(
        `Cannot approve coupon with status "${coupon.status}". Only minted coupons can be approved.`,
      );
    }

    coupon.status = CouponStatus.LISTED;
    return this.couponRepo.save(coupon);
  }

  /**
   * Reject a coupon: set status to EXPIRED and store rejection reason.
   */
  async rejectCoupon(id: string, reason: string): Promise<Coupon> {
    const coupon = await this.couponRepo.findById(id);
    if (!coupon) throw new NotFoundException('Coupon not found');

    if (coupon.status !== CouponStatus.MINTED) {
      throw new BadRequestException(
        `Cannot reject coupon with status "${coupon.status}". Only minted coupons can be rejected.`,
      );
    }

    coupon.status = CouponStatus.RECALLED;
    return this.couponRepo.save(coupon);
  }

  /**
   * Suspend an active coupon: set status to PAUSED.
   */
  async suspendCoupon(id: string): Promise<Coupon> {
    const coupon = await this.couponRepo.findById(id);
    if (!coupon) throw new NotFoundException('Coupon not found');

    if (coupon.status !== CouponStatus.LISTED) {
      throw new BadRequestException(
        `Cannot suspend coupon with status "${coupon.status}". Only listed coupons can be suspended.`,
      );
    }

    coupon.status = CouponStatus.RECALLED;
    return this.couponRepo.save(coupon);
  }
}
