import { Injectable } from "@nestjs/common";
import { PaymentIntentStatus } from "@prisma/client";
import { PrismaService } from "../prisma/prisma.service";
import {
  CurrencyVolumeDto,
  GlobalBillingStatsDto,
  MerchantBillingStatsDto,
  TopMerchantDto,
} from "./dto/billing-stats.dto";

interface PeriodRange {
  start: Date;
  end: Date;
  prevStart: Date;
  prevEnd: Date;
  periodStr: string;
}

const PENDING_STATUSES: PaymentIntentStatus[] = [
  PaymentIntentStatus.created,
  PaymentIntentStatus.pending,
  PaymentIntentStatus.processing,
];

@Injectable()
export class BillingStatsService {
  constructor(private readonly prisma: PrismaService) {}

  async merchantStats(
    merchantId: string,
    period?: string,
  ): Promise<MerchantBillingStatsDto> {
    const { start, end, periodStr } = this.resolvePeriod(period);

    const [byStatus, byCurrency] = await Promise.all([
      this.prisma.paymentIntent.groupBy({
        by: ["status"],
        where: { merchantId, createdAt: { gte: start, lt: end } },
        _count: { _all: true },
      }),
      this.prisma.paymentIntent.groupBy({
        by: ["currency"],
        where: {
          merchantId,
          status: PaymentIntentStatus.approved,
          createdAt: { gte: start, lt: end },
        },
        _count: { _all: true },
        _sum: { amount: true },
      }),
    ]);

    const countOf = (status: PaymentIntentStatus) =>
      byStatus.find((row) => row.status === status)?._count._all ?? 0;

    return {
      merchantId,
      period: periodStr,
      approvedCount: countOf(PaymentIntentStatus.approved),
      rejectedCount: countOf(PaymentIntentStatus.rejected),
      pendingCount: PENDING_STATUSES.reduce(
        (acc, status) => acc + countOf(status),
        0,
      ),
      volumes: byCurrency.map(this.toCurrencyVolume),
    };
  }

  async globalStats(period?: string): Promise<GlobalBillingStatsDto> {
    const { start, end, prevStart, prevEnd, periodStr } =
      this.resolvePeriod(period);

    const [
      approvedCount,
      rejectedCount,
      previousApprovedCount,
      activeMerchants,
      byCurrency,
      topRaw,
    ] = await Promise.all([
      this.prisma.paymentIntent.count({
        where: {
          status: PaymentIntentStatus.approved,
          createdAt: { gte: start, lt: end },
        },
      }),
      this.prisma.paymentIntent.count({
        where: {
          status: PaymentIntentStatus.rejected,
          createdAt: { gte: start, lt: end },
        },
      }),
      this.prisma.paymentIntent.count({
        where: {
          status: PaymentIntentStatus.approved,
          createdAt: { gte: prevStart, lt: prevEnd },
        },
      }),
      this.prisma.merchant.count({ where: { status: "active" } }),
      this.prisma.paymentIntent.groupBy({
        by: ["currency"],
        where: {
          status: PaymentIntentStatus.approved,
          createdAt: { gte: start, lt: end },
        },
        _count: { _all: true },
        _sum: { amount: true },
      }),
      this.prisma.paymentIntent.groupBy({
        by: ["merchantId"],
        where: {
          status: PaymentIntentStatus.approved,
          currency: "PYG",
          createdAt: { gte: start, lt: end },
        },
        _count: { _all: true },
        _sum: { amount: true },
        orderBy: { _sum: { amount: "desc" } },
        take: 5,
      }),
    ]);

    const topMerchants = await this.hydrateTopMerchants(topRaw);

    return {
      period: periodStr,
      approvedCount,
      rejectedCount,
      previousApprovedCount,
      activeMerchants,
      volumes: byCurrency.map(this.toCurrencyVolume),
      topMerchants,
    };
  }

  private async hydrateTopMerchants(
    raw: Array<{
      merchantId: string;
      _count: { _all: number };
      _sum: { amount: unknown };
    }>,
  ): Promise<TopMerchantDto[]> {
    if (raw.length === 0) return [];
    const ids = raw.map((r) => r.merchantId);
    const merchants = await this.prisma.merchant.findMany({
      where: { id: { in: ids } },
      select: { id: true, name: true },
    });
    const nameById = new Map(merchants.map((m) => [m.id, m.name]));
    return raw.map((r) => ({
      merchantId: r.merchantId,
      merchantName: nameById.get(r.merchantId) ?? r.merchantId,
      approvedCount: r._count._all,
      approvedVolumePyg: (r._sum.amount as { toString(): string } | null)?.toString() ?? "0",
    }));
  }

  private toCurrencyVolume = (row: {
    currency: string;
    _count: { _all: number };
    _sum: { amount: unknown };
  }): CurrencyVolumeDto => ({
    currency: row.currency,
    approvedCount: row._count._all,
    approvedVolume:
      (row._sum.amount as { toString(): string } | null)?.toString() ?? "0",
  });

  private resolvePeriod(period?: string): PeriodRange {
    let year: number;
    let month: number;
    if (period && /^\d{4}-(0[1-9]|1[0-2])$/.test(period)) {
      const [y, m] = period.split("-").map(Number);
      year = y;
      month = m;
    } else {
      const now = new Date();
      year = now.getUTCFullYear();
      month = now.getUTCMonth() + 1;
    }
    const start = new Date(Date.UTC(year, month - 1, 1));
    const end = new Date(Date.UTC(year, month, 1));
    const prevStart = new Date(Date.UTC(year, month - 2, 1));
    const prevEnd = start;
    const periodStr = `${year}-${String(month).padStart(2, "0")}`;
    return { start, end, prevStart, prevEnd, periodStr };
  }
}
