import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model, Op } from 'sequelize';
import type { LiteralUnion } from 'type-fest';

import { createIdGenerator } from '../../libs/util';

const nextId = createIdGenerator('mbur', 24);

// eslint-disable-next-line prettier/prettier
export class UsageRecord extends Model<InferAttributes<UsageRecord>, InferCreationAttributes<UsageRecord>> {
  declare id: CreationOptional<string>;

  declare livemode: boolean;
  declare billed: boolean;

  // The timestamp when this usage occurred.
  declare timestamp: number;

  // The usage quantity for the specified date.
  declare quantity: number;

  // The ID of the subscription item this usage record contains data for.
  declare subscription_item_id: string;

  // Props only exist on creation
  // declare action: LiteralUnion<'increment' | 'set', string>;

  declare metadata: Record<string, any>;

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

  public static readonly GENESIS_ATTRIBUTES = {
    id: {
      type: DataTypes.STRING(30),
      primaryKey: true,
      allowNull: false,
      defaultValue: nextId,
    },
    livemode: {
      type: DataTypes.BOOLEAN,
      allowNull: false,
    },
    timestamp: {
      type: DataTypes.INTEGER,
      allowNull: false,
    },
    quantity: {
      type: DataTypes.BIGINT,
      allowNull: false,
    },
    subscription_item_id: {
      type: DataTypes.STRING(30),
      allowNull: false,
    },
    metadata: {
      type: DataTypes.JSON,
      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(
      {
        ...UsageRecord.GENESIS_ATTRIBUTES,
        billed: {
          type: DataTypes.BOOLEAN,
          defaultValue: false,
        },
      },
      {
        sequelize,
        modelName: 'UsageRecord',
        tableName: 'usage_records',
        createdAt: 'created_at',
        updatedAt: 'updated_at',
      }
    );
  }

  public static associate(models: any) {
    this.belongsTo(models.SubscriptionItem, {
      foreignKey: 'subscription_item_id',
      as: 'subscription_item',
    });
  }

  public static async getSummary({
    id,
    start,
    end,
    method,
    dryRun,
    billed = false,
    searchBilled = true,
  }: {
    id: string;
    start: number;
    end: number;
    method: LiteralUnion<'sum' | 'last_during_period' | 'max' | 'last_ever', string>;
    dryRun: boolean;
    billed?: boolean;
    searchBilled?: boolean;
  }): Promise<number> {
    const query = {
      where: {
        subscription_item_id: id,
        ...(searchBilled ? { billed } : {}),
        timestamp: {
          [Op.gt]: start,
          [Op.lte]: end,
        },
      },
      order: [['timestamp', 'DESC']],
    };

    const update = () => (dryRun ? Promise.resolve() : UsageRecord.update({ billed: true }, query));

    if (method === 'sum') {
      const sum = await this.sum('quantity', query);
      await update();
      return sum || 0;
    }

    if (method === 'max') {
      const max = await this.max('quantity', query);
      await update();
      return (max as number) || 0;
    }

    if (method === 'last_during_period') {
      // @ts-ignore
      const item = await this.findOne(query);
      await update();
      return item ? item.quantity : 0;
    }

    if (method === 'last_ever') {
      const item = await this.findOne({
        where: { subscription_item_id: id },
        order: [['timestamp', 'DESC']],
      });
      await update();
      return item ? item.quantity : 0;
    }

    return 0;
  }
}

export type TUsageRecord = InferAttributes<UsageRecord>;
