/* eslint-disable @typescript-eslint/lines-between-class-members */
import { col, CreationOptional, DataTypes, fn, InferAttributes, InferCreationAttributes, Model, Op } from 'sequelize';

import { BN } from '@ocap/util';
import { createEvent } from '../../libs/audit';
import { createIdGenerator } from '../../libs/util';
import { CreditGrant } from './credit-grant';

export const nextCreditTransactionId = createIdGenerator('cbtxn', 14);

export class CreditTransaction extends Model<
  InferAttributes<CreditTransaction>,
  InferCreationAttributes<CreditTransaction>
> {
  declare id: CreationOptional<string>;
  declare quantity: string;
  declare credit_amount: string;
  declare remaining_balance: string;

  // foreign keys
  declare customer_id: string;
  declare credit_grant_id: string;
  declare meter_id?: string;
  declare subscription_id?: string;
  declare source?: string;

  declare meter_event_name: string;
  declare meter_unit: string;
  declare description?: string;
  declare metadata?: Record<string, any>;

  // Token transfer fields
  declare transfer_status?: 'pending' | 'completed' | 'failed' | null;
  declare transfer_hash?: string;

  declare created_at: CreationOptional<Date>;
  declare updated_at: CreationOptional<Date>;

  public static readonly GENESIS_ATTRIBUTES = {
    id: {
      type: DataTypes.STRING(18),
      primaryKey: true,
      allowNull: false,
      defaultValue: nextCreditTransactionId,
    },
    quantity: {
      type: DataTypes.STRING(32),
      allowNull: false,
    },
    credit_amount: {
      type: DataTypes.STRING(32),
      allowNull: false,
    },
    remaining_balance: {
      type: DataTypes.STRING(32),
      allowNull: false,
    },
    customer_id: {
      type: DataTypes.STRING(18),
      allowNull: false,
    },
    credit_grant_id: {
      type: DataTypes.STRING(18),
      allowNull: false,
    },
    meter_id: {
      type: DataTypes.STRING(18),
      allowNull: true,
    },
    subscription_id: {
      type: DataTypes.STRING(18),
      allowNull: true,
    },
    source: {
      type: DataTypes.STRING(255),
      allowNull: true,
    },

    meter_event_name: {
      type: DataTypes.STRING(128),
      allowNull: false,
    },
    meter_unit: {
      type: DataTypes.STRING(32),
      allowNull: false,
    },
    description: {
      type: DataTypes.TEXT,
      allowNull: true,
    },
    metadata: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    transfer_status: {
      type: DataTypes.ENUM('pending', 'completed', 'failed'),
      allowNull: true,
    },
    transfer_hash: {
      type: DataTypes.STRING(255),
      allowNull: true,
    },
    created_at: {
      type: DataTypes.DATE,
      defaultValue: DataTypes.NOW,
      allowNull: false,
    },
    updated_at: {
      type: DataTypes.DATE,
      defaultValue: DataTypes.NOW,
      allowNull: false,
    },
  };

  public static initialize(sequelize: any) {
    this.init(this.GENESIS_ATTRIBUTES, {
      sequelize,
      modelName: 'CreditTransaction',
      tableName: 'credit_transactions',
      createdAt: 'created_at',
      updatedAt: 'updated_at',
      indexes: [
        { fields: ['customer_id'] },
        { fields: ['credit_grant_id'] },
        { fields: ['source'] },
        { fields: ['transfer_status'] },
      ],
      hooks: {
        afterCreate: (model: CreditTransaction, options) => {
          createEvent('CreditTransaction', 'customer.credit_transaction.created', model, options).catch(console.error);
        },
      },
    });
  }

  public static associate(models: any) {
    this.belongsTo(models.Customer, {
      foreignKey: 'customer_id',
      as: 'customer',
    });

    this.belongsTo(models.CreditGrant, {
      foreignKey: 'credit_grant_id',
      as: 'creditGrant',
    });

    this.belongsTo(models.Meter, {
      foreignKey: 'meter_id',
      as: 'meter',
    });

    this.belongsTo(models.Subscription, {
      foreignKey: 'subscription_id',
      as: 'subscription',
    });

    this.belongsTo(models.MeterEvent, {
      foreignKey: 'source',
      as: 'meterEvent',
    });
  }

  public static async getUsageSummary({
    customerId,
    subscriptionId,
    meterEventName,
    currencyId,
    meterId,
    startTime,
    endTime,
  }: {
    customerId?: string;
    subscriptionId?: string;
    meterEventName?: string;
    currencyId?: string;
    meterId?: string;
    startTime?: Date;
    endTime?: Date;
  }) {
    const whereClause: any = {};

    if (customerId) {
      whereClause.customer_id = customerId;
    }
    if (subscriptionId) {
      whereClause.subscription_id = subscriptionId;
    }

    if (meterEventName) {
      whereClause.meter_event_name = meterEventName;
    }

    if (currencyId) {
      whereClause['$creditGrant.currency_id$'] = currencyId;
    }

    if (meterId) {
      whereClause.meter_id = meterId;
    }

    if (startTime || endTime) {
      whereClause.created_at = {};
      if (startTime) {
        whereClause.created_at[Op.gte] = startTime;
      }
      if (endTime) {
        whereClause.created_at[Op.lte] = endTime;
      }
    }

    const include = currencyId
      ? [
          {
            model: CreditGrant,
            as: 'creditGrant',
            attributes: [],
            required: true,
            where: { currency_id: currencyId },
          },
        ]
      : [];

    const transactions = await this.findAll({
      attributes: ['quantity', 'credit_amount'],
      where: whereClause,
      include: include.length > 0 ? include : undefined,
      raw: true,
    });
    const totalQuantity = transactions.reduce((acc, curr) => new BN(acc).add(new BN(curr.quantity)).toString(), '0');
    const totalCreditAmount = transactions.reduce(
      (acc, curr) => new BN(acc).add(new BN(curr.credit_amount)).toString(),
      '0'
    );
    const transactionCount = transactions.length;

    return {
      total_quantity: totalQuantity,
      total_credit_amount: totalCreditAmount,
      transaction_count: transactionCount,
      filters: {
        customer_id: customerId,
        subscription_id: subscriptionId,
        meter_event_name: meterEventName,
        meter_id: meterId,
        currency_id: currencyId,
        start_time: startTime?.toISOString(),
        end_time: endTime?.toISOString(),
      },
    };
  }

  public static getUsageSummaryForCustomer(
    customerId: string,
    meterEventName?: string,
    startTime?: Date,
    endTime?: Date
  ) {
    return this.getUsageSummary({
      customerId,
      meterEventName,
      startTime,
      endTime,
    });
  }

  public static getUsageSummaryForSubscription(subscriptionId: string, startTime?: Date, endTime?: Date) {
    return this.getUsageSummary({
      subscriptionId,
      startTime,
      endTime,
    });
  }

  public static async getUsageTrend(customerId: string, meterEventName: string, startTime: Date, endTime: Date) {
    const transactions = await this.findAll({
      attributes: [
        [fn('DATE', col('created_at')), 'date'],
        [fn('SUM', col('quantity')), 'total_quantity'],
        [fn('SUM', col('credit_amount')), 'total_credit_amount'],
        [fn('COUNT', col('id')), 'transaction_count'],
      ],
      where: {
        customer_id: customerId,
        meter_event_name: meterEventName,
        created_at: {
          [Op.between]: [startTime, endTime],
        },
      },
      group: [fn('DATE', col('created_at'))],
      order: [[fn('DATE', col('created_at')), 'ASC']],
      raw: true,
    });

    return transactions;
  }
}

export type TCreditTransaction = InferAttributes<CreditTransaction>;
