/* eslint-disable no-await-in-loop */
import { BN } from '@ocap/util';
import { Op } from 'sequelize';

import dayjs from '../dayjs';
import logger from '../logger';
import {
  CreditGrant,
  Invoice,
  InvoiceItem,
  Payout,
  PaymentCurrency,
  Refund,
  RevenueSnapshot,
} from '../../store/models';

const BILLING_REASON_EXCLUSIONS = ['stake', 'stake_overdraft_protection', 'recharge'];

type MonthlyRevenueData = {
  currencyId: string;
  livemode: boolean;
  totalRevenue: string;
  refundAmount: string;
  promotionCost: string;
  creditGrantCost: string;
  vendorCost: string;
  taxedRevenue: string;
  netRevenue: string;
};

async function getTaxedInvoiceIds(
  livemode: boolean,
  startDate: Date,
  endDate: Date,
  currencyIds: string[]
): Promise<Set<string>> {
  const invoiceItems = await InvoiceItem.findAll({
    where: {
      tax_rate_id: { [Op.ne]: null },
    } as any,
    include: [
      {
        model: Invoice,
        as: 'invoice',
        where: {
          livemode,
          status: 'paid',
          billing_reason: { [Op.notIn]: BILLING_REASON_EXCLUSIONS },
          currency_id: { [Op.in]: currencyIds },
          created_at: { [Op.gte]: startDate, [Op.lt]: endDate },
        },
        attributes: ['id'],
      },
    ],
    attributes: ['invoice_id'],
  });

  return new Set(invoiceItems.map((item: any) => item.invoice_id).filter(Boolean));
}

async function calculateMonthlyRevenue(
  livemode: boolean,
  startDate: Date,
  endDate: Date
): Promise<MonthlyRevenueData[]> {
  const currencies = await PaymentCurrency.findAll({ where: { livemode } });
  if (currencies.length === 0) {
    return [];
  }

  const currencyIds = currencies.map((c) => c.id);

  const [invoices, taxedInvoiceIds, payouts, refunds, creditGrants] = await Promise.all([
    Invoice.findAll({
      where: {
        livemode,
        status: 'paid',
        billing_reason: { [Op.notIn]: BILLING_REASON_EXCLUSIONS },
        currency_id: { [Op.in]: currencyIds },
        created_at: { [Op.gte]: startDate, [Op.lt]: endDate },
      },
      attributes: ['id', 'total', 'total_discount_amounts', 'currency_id'],
    }),
    getTaxedInvoiceIds(livemode, startDate, endDate, currencyIds),
    Payout.findAll({
      where: {
        livemode,
        status: { [Op.in]: ['paid', 'deferred'] },
        currency_id: { [Op.in]: currencyIds },
        created_at: { [Op.gte]: startDate, [Op.lt]: endDate },
      } as any,
      attributes: ['amount', 'currency_id', 'vendor_info'],
    }).then((result) => result.filter((p: any) => p.vendor_info != null)),
    Refund.findAll({
      where: {
        livemode,
        status: 'succeeded',
        currency_id: { [Op.in]: currencyIds },
        created_at: { [Op.gte]: startDate, [Op.lt]: endDate },
      },
      attributes: ['amount', 'currency_id'],
    }),
    // Only count CONSUMED credits as cost (depleted or expired)
    // granted credits are still active, voided credits were revoked
    // Cost = amount - remaining_amount (the actually consumed portion)
    CreditGrant.findAll({
      where: {
        livemode,
        status: { [Op.in]: ['depleted', 'expired'] },
        currency_id: { [Op.in]: currencyIds },
        created_at: { [Op.gte]: startDate, [Op.lt]: endDate },
      },
      attributes: ['amount', 'remaining_amount', 'currency_id'],
    }),
  ]);

  const byCurrency: Record<
    string,
    {
      totalRevenue: BN;
      refundAmount: BN;
      promotionCost: BN;
      creditGrantCost: BN;
      vendorCost: BN;
      taxedRevenue: BN;
      livemode: boolean;
    }
  > = {};

  for (const currency of currencies) {
    byCurrency[currency.id] = {
      totalRevenue: new BN('0'),
      refundAmount: new BN('0'),
      promotionCost: new BN('0'),
      creditGrantCost: new BN('0'),
      vendorCost: new BN('0'),
      taxedRevenue: new BN('0'),
      livemode: currency.livemode,
    };
  }

  for (const inv of invoices) {
    const entry = byCurrency[inv.currency_id];
    if (entry) {
      entry.totalRevenue = entry.totalRevenue.add(new BN(inv.total || '0'));

      const discounts = inv.total_discount_amounts || [];
      for (const discount of discounts) {
        entry.promotionCost = entry.promotionCost.add(new BN(discount.amount || '0'));
      }

      if (taxedInvoiceIds.has(inv.id)) {
        entry.taxedRevenue = entry.taxedRevenue.add(new BN(inv.total || '0'));
      }
    }
  }

  for (const payout of payouts) {
    const entry = byCurrency[payout.currency_id];
    if (entry) {
      entry.vendorCost = entry.vendorCost.add(new BN(payout.amount || '0'));
    }
  }

  for (const refund of refunds) {
    const entry = byCurrency[refund.currency_id];
    if (entry) {
      entry.refundAmount = entry.refundAmount.add(new BN(refund.amount || '0'));
    }
  }

  for (const grant of creditGrants) {
    const entry = byCurrency[grant.currency_id];
    if (entry) {
      // Cost = consumed amount = total amount - remaining amount
      const consumed = new BN(grant.amount || '0').sub(new BN(grant.remaining_amount || '0'));
      entry.creditGrantCost = entry.creditGrantCost.add(consumed);
    }
  }

  const results: MonthlyRevenueData[] = [];
  for (const [currencyId, data] of Object.entries(byCurrency)) {
    // Net Revenue = Total Revenue - Refunds
    const netRevenue = data.totalRevenue.sub(data.refundAmount);
    results.push({
      currencyId,
      livemode: data.livemode,
      totalRevenue: data.totalRevenue.toString(),
      refundAmount: data.refundAmount.toString(),
      promotionCost: data.promotionCost.toString(),
      creditGrantCost: data.creditGrantCost.toString(),
      vendorCost: data.vendorCost.toString(),
      taxedRevenue: data.taxedRevenue.toString(),
      netRevenue: netRevenue.toString(),
    });
  }

  return results;
}

export async function createRevenueSnapshotsForArchive(
  cutoffTimestamp: number,
  archiveMetadataId: string
): Promise<number> {
  const cutoffDate = dayjs.unix(cutoffTimestamp);
  const monthsToSnapshot: { start: dayjs.Dayjs; end: dayjs.Dayjs }[] = [];

  const existingSnapshots = await RevenueSnapshot.findAll({
    attributes: ['timestamp', 'livemode', 'currency_id'],
    order: [['timestamp', 'DESC']],
    limit: 1,
  });

  let startMonth: dayjs.Dayjs;
  if (existingSnapshots.length > 0) {
    startMonth = dayjs.unix(existingSnapshots[0]!.timestamp).add(1, 'month').startOf('month');
  } else {
    const oldestInvoice = await Invoice.findOne({
      where: { status: 'paid' },
      order: [['created_at', 'ASC']],
      attributes: ['created_at'],
    });
    if (!oldestInvoice) {
      logger.info('No paid invoices found, skipping revenue snapshot');
      return 0;
    }
    startMonth = dayjs(oldestInvoice.created_at).startOf('month');
  }

  let current = startMonth;
  while (current.isBefore(cutoffDate)) {
    const monthEnd = current.endOf('month');
    if (monthEnd.isBefore(cutoffDate)) {
      monthsToSnapshot.push({
        start: current,
        end: current.add(1, 'month').startOf('month'),
      });
    }
    current = current.add(1, 'month').startOf('month');
  }

  if (monthsToSnapshot.length === 0) {
    logger.info('No complete months to snapshot before cutoff', { cutoffDate: cutoffDate.toISOString() });
    return 0;
  }

  let totalCreated = 0;

  for (const { start, end } of monthsToSnapshot) {
    const timestamp = start.unix();

    for (const livemode of [true, false]) {
      const revenueData = await calculateMonthlyRevenue(livemode, start.toDate(), end.toDate());

      const snapshots = revenueData.map((data) => ({
        livemode: data.livemode,
        currency_id: data.currencyId,
        timestamp,
        period_type: 'monthly' as const,
        total_revenue: data.totalRevenue,
        refund_amount: data.refundAmount,
        promotion_cost: data.promotionCost,
        credit_grant_cost: data.creditGrantCost,
        vendor_cost: data.vendorCost,
        taxed_revenue: data.taxedRevenue,
        net_revenue: data.netRevenue,
        archive_metadata_id: archiveMetadataId,
      }));

      if (snapshots.length > 0) {
        await RevenueSnapshot.bulkCreate(snapshots, {
          updateOnDuplicate: [
            'total_revenue',
            'refund_amount',
            'promotion_cost',
            'credit_grant_cost',
            'vendor_cost',
            'taxed_revenue',
            'net_revenue',
            'archive_metadata_id',
          ],
        });
        totalCreated += snapshots.length;
      }
    }

    logger.info('Revenue snapshot created', { month: start.format('YYYY-MM'), records: totalCreated });
  }

  return totalCreated;
}
