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

@Injectable()
export class IssuerFinanceService {
  constructor(
    @Inject(ISSUER_REPOSITORY)
    private readonly issuerRepo: IIssuerRepository,
    @Inject(COUPON_REPOSITORY)
    private readonly couponRepo: ICouponRepository,
    private readonly dataSource: DataSource,
  ) {}

  async getBalance(issuerId: string) {
    const issuer = await this.issuerRepo.findById(issuerId);
    if (!issuer) throw new NotFoundException('Issuer not found');

    // Aggregate from coupons
    const result = await this.dataSource
      .createQueryBuilder()
      .select([
        `COALESCE(SUM(CASE WHEN c.status IN ('sold', 'redeemed') THEN CAST(c.current_price AS numeric) ELSE 0 END), 0) as "total"`,
        `COALESCE(SUM(CASE WHEN c.status = 'redeemed' THEN CAST(c.current_price AS numeric) ELSE 0 END), 0) as "withdrawable"`,
        `COALESCE(SUM(CASE WHEN c.status = 'sold' THEN CAST(c.current_price AS numeric) ELSE 0 END), 0) as "pending"`,
      ])
      .from(Coupon, 'c')
      .where('c.issuer_id = :issuerId', { issuerId })
      .getRawOne();

    return {
      withdrawable: Math.round(Number(result.withdrawable) * 100) / 100,
      pending: Math.round(Number(result.pending) * 100) / 100,
      total: Math.round(Number(result.total) * 100) / 100,
    };
  }

  async getFinanceStats(issuerId: string) {
    const issuer = await this.issuerRepo.findById(issuerId);
    if (!issuer) throw new NotFoundException('Issuer not found');
    const feeRate = Number(issuer.feeRate) || 0.015;

    const result = await this.dataSource
      .createQueryBuilder()
      .select([
        `COALESCE(SUM(CASE WHEN c.status IN ('sold', 'redeemed') THEN CAST(c.current_price AS numeric) ELSE 0 END), 0) as "salesAmount"`,
        `COALESCE(SUM(CASE WHEN c.status = 'expired' THEN CAST(c.current_price AS numeric) ELSE 0 END), 0) as "breakageIncome"`,
        `COALESCE(SUM(CASE WHEN c.status = 'sold' THEN CAST(c.current_price AS numeric) ELSE 0 END), 0) as "pendingSettlement"`,
      ])
      .from(Coupon, 'c')
      .where('c.issuer_id = :issuerId', { issuerId })
      .getRawOne();

    const salesAmount = Math.round(Number(result.salesAmount) * 100) / 100;
    const breakageIncome = Math.round(Number(result.breakageIncome) * 100) / 100;
    const platformFee = Math.round(salesAmount * feeRate * 100) / 100;
    const pendingSettlement = Math.round(Number(result.pendingSettlement) * 100) / 100;
    const totalRevenue = Math.round((salesAmount + breakageIncome - platformFee) * 100) / 100;

    return {
      salesAmount,
      breakageIncome,
      platformFee,
      pendingSettlement,
      withdrawn: 0, // TODO: track actual withdrawals
      totalRevenue,
    };
  }

  async getTransactions(
    issuerId: string,
    page: number,
    limit: number,
    type?: string,
  ) {
    const issuer = await this.issuerRepo.findById(issuerId);
    if (!issuer) throw new NotFoundException('Issuer not found');

    // Build transaction-like view from coupons
    const qb = this.dataSource
      .createQueryBuilder()
      .select([
        'c.id as "id"',
        'c.name as "couponName"',
        'c.status as "type"',
        'c.current_price as "amount"',
        'c.updated_at as "date"',
      ])
      .from(Coupon, 'c')
      .where('c.issuer_id = :issuerId', { issuerId })
      .andWhere("c.status IN ('sold', 'redeemed', 'expired', 'recalled')");

    if (type) {
      const statusMap: Record<string, string> = {
        sale: 'sold',
        redemption: 'redeemed',
      };
      const mappedStatus = statusMap[type] || type;
      qb.andWhere('c.status = :mappedStatus', { mappedStatus });
    }

    const total = await qb.getCount();

    const items = await qb
      .orderBy('c.updated_at', 'DESC')
      .offset((page - 1) * limit)
      .limit(limit)
      .getRawMany();

    return { items, total, page, limit };
  }

  async requestWithdrawal(issuerId: string, amount: number) {
    const balance = await this.getBalance(issuerId);
    if (amount > balance.withdrawable) {
      throw new BadRequestException(
        `Insufficient withdrawable balance. Available: ${balance.withdrawable}`,
      );
    }

    // In a real system, this would create a withdrawal record and trigger a payout
    // For now, return a placeholder
    return {
      id: require('crypto').randomUUID(),
      issuerId,
      amount,
      status: 'pending',
      requestedAt: new Date(),
      estimatedArrival: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000), // T+3
    };
  }

  async getReconciliation(
    issuerId: string,
    startDate?: string,
    endDate?: string,
  ) {
    const issuer = await this.issuerRepo.findById(issuerId);
    if (!issuer) throw new NotFoundException('Issuer not found');

    const qb = this.dataSource
      .createQueryBuilder()
      .select([
        "TO_CHAR(c.updated_at, 'YYYY-MM-DD') as \"date\"",
        `SUM(CASE WHEN c.status = 'sold' THEN CAST(c.current_price AS numeric) ELSE 0 END) as "sales"`,
        `SUM(CASE WHEN c.status = 'redeemed' THEN CAST(c.current_price AS numeric) ELSE 0 END) as "redemptions"`,
        `SUM(CASE WHEN c.status = 'expired' THEN CAST(c.current_price AS numeric) ELSE 0 END) as "breakage"`,
        'COUNT(c.id) as "transactionCount"',
      ])
      .from(Coupon, 'c')
      .where('c.issuer_id = :issuerId', { issuerId })
      .andWhere("c.status IN ('sold', 'redeemed', 'expired')");

    if (startDate) qb.andWhere('c.updated_at >= :startDate', { startDate });
    if (endDate) qb.andWhere('c.updated_at <= :endDate', { endDate });

    qb.groupBy("TO_CHAR(c.updated_at, 'YYYY-MM-DD')")
      .orderBy('"date"', 'DESC');

    const rows = await qb.getRawMany();

    return rows.map((row) => ({
      date: row.date,
      sales: Math.round(Number(row.sales) * 100) / 100,
      redemptions: Math.round(Number(row.redemptions) * 100) / 100,
      breakage: Math.round(Number(row.breakage) * 100) / 100,
      transactionCount: Number(row.transactionCount),
    }));
  }
}
