import { Injectable, Inject, NotFoundException, BadRequestException } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { COUPON_REPOSITORY, ICouponRepository } from '../../domain/repositories/coupon.repository.interface';
import { COUPON_RULE_REPOSITORY, ICouponRuleRepository } from '../../domain/repositories/coupon-rule.repository.interface';
import { STORE_REPOSITORY, IStoreRepository } from '../../domain/repositories/store.repository.interface';
import { Coupon, CouponStatus } from '../../domain/entities/coupon.entity';
import { Store } from '../../domain/entities/store.entity';

@Injectable()
export class CouponService {
  constructor(
    @Inject(COUPON_REPOSITORY)
    private readonly couponRepo: ICouponRepository,
    @Inject(COUPON_RULE_REPOSITORY)
    private readonly ruleRepo: ICouponRuleRepository,
    @Inject(STORE_REPOSITORY)
    private readonly storeRepo: IStoreRepository,
    private readonly dataSource: DataSource,
  ) {}

  async create(issuerId: string, data: Partial<Coupon> & { rules?: any[] }) {
    const saved = await this.couponRepo.create({
      ...data,
      issuerId,
      status: CouponStatus.MINTED,
      remainingSupply: data.totalSupply || 0,
    });

    if (data.rules?.length) {
      const rules = data.rules.map((r) => ({
        couponId: saved.id,
        ...r,
      }));
      await this.ruleRepo.createMany(rules);
    }

    return saved;
  }

  async findById(id: string) {
    const coupon = await this.couponRepo.findById(id);
    if (!coupon) throw new NotFoundException('Coupon not found');

    const rules = await this.ruleRepo.findByCouponId(id);
    return { ...coupon, rules };
  }

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

  async getByOwner(userId: string, page: number, limit: number, status?: string) {
    const [items, total] = await this.couponRepo.findByOwnerWithIssuerJoin(userId, {
      status,
      page,
      limit,
    });
    return { items, total, page, limit };
  }

  async getOwnerSummary(userId: string) {
    return this.couponRepo.getOwnerSummary(userId);
  }

  async updateStatus(id: string, status: string) {
    return this.couponRepo.updateStatus(id, status);
  }

  async purchase(couponId: string, quantity: number = 1) {
    return this.couponRepo.purchaseWithLock(couponId, quantity);
  }

  /**
   * Search coupons with keyword, category, and sort.
   */
  async search(
    q?: string,
    category?: string,
    sort: string = 'newest',
    page: number = 1,
    limit: number = 20,
  ) {
    const qb = this.dataSource
      .getRepository(Coupon)
      .createQueryBuilder('c');

    if (q) {
      qb.andWhere('(c.name ILIKE :q OR c.description ILIKE :q)', { q: `%${q}%` });
    }
    if (category) {
      qb.andWhere('c.category = :category', { category });
    }

    // Default: only show listed coupons in search
    qb.andWhere('c.status = :status', { status: CouponStatus.LISTED });

    switch (sort) {
      case 'price_asc':
        qb.orderBy('CAST(c.current_price AS numeric)', 'ASC');
        break;
      case 'price_desc':
        qb.orderBy('CAST(c.current_price AS numeric)', 'DESC');
        break;
      case 'popular':
        qb.orderBy('c.total_supply - c.remaining_supply', 'DESC');
        break;
      case 'newest':
      default:
        qb.orderBy('c.created_at', 'DESC');
        break;
    }

    qb.skip((page - 1) * limit).take(limit);
    const [items, total] = await qb.getManyAndCount();
    return { items, total, page, limit };
  }

  /**
   * Find stores that accept a coupon (by the coupon's issuer).
   */
  async getNearbyStores(couponId: string): Promise<Store[]> {
    const coupon = await this.couponRepo.findById(couponId);
    if (!coupon) throw new NotFoundException('Coupon not found');

    return this.storeRepo.findByIssuerId(coupon.issuerId);
  }

  /**
   * Find similar coupons: same category, different issuer.
   */
  async findSimilar(couponId: string, limit: number = 10): Promise<Coupon[]> {
    const coupon = await this.couponRepo.findById(couponId);
    if (!coupon) throw new NotFoundException('Coupon not found');

    const qb = this.dataSource
      .getRepository(Coupon)
      .createQueryBuilder('c')
      .where('c.id != :id', { id: couponId })
      .andWhere('c.status = :status', { status: CouponStatus.LISTED });

    if (coupon.category) {
      qb.andWhere('c.category = :category', { category: coupon.category });
    }

    // Prefer different issuer
    qb.orderBy(
      'CASE WHEN c.issuer_id != :issuerId THEN 0 ELSE 1 END',
      'ASC',
    );
    qb.setParameter('issuerId', coupon.issuerId);
    qb.addOrderBy('c.created_at', 'DESC');
    qb.take(limit);

    return qb.getMany();
  }
}
