/* eslint-disable @typescript-eslint/lines-between-class-members */
import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model } from 'sequelize';
import type { LiteralUnion } from 'type-fest';
import { fromTokenToUnit } from '@ocap/util';

import { createEvent } from '../../libs/audit';
import { createCodeGenerator, formatMetadata } from '../../libs/util';
import { trimDecimals } from '../../libs/math-utils';
import logger from '../../libs/logger';
import type { TPaymentCurrency } from './payment-currency';

const nextId = createCodeGenerator('', 8);

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

  declare amount_off: string;
  declare percent_off: number;

  declare currency_id: string;

  declare duration: LiteralUnion<'forever' | 'once' | 'repeating', string>;

  declare duration_in_months: number;

  declare name: string;

  declare description?: string;

  declare metadata: Record<string, any>;

  declare applies_to?: {
    products: string[];
  };

  declare currency_options?: Record<
    string,
    {
      amount_off: string;
    }
  >;

  declare max_redemptions?: number;

  declare redeem_by?: number;

  declare times_redeemed?: number;

  declare valid?: boolean;
  declare created_via: LiteralUnion<'api' | 'dashboard' | 'portal', string>;

  declare created_at: CreationOptional<Date>;

  declare updated_at: CreationOptional<Date>;

  public static readonly GENESIS_ATTRIBUTES = {
    id: {
      type: DataTypes.STRING(15),
      primaryKey: true,
      allowNull: false,
      defaultValue: nextId,
    },
    livemode: {
      type: DataTypes.BOOLEAN,
      allowNull: false,
    },
    locked: {
      type: DataTypes.BOOLEAN,
      defaultValue: false,
    },
    amount_off: {
      type: DataTypes.STRING(32),
      defaultValue: '0',
    },
    percent_off: {
      type: DataTypes.INTEGER,
      defaultValue: 0,
    },
    currency_id: {
      type: DataTypes.STRING(15),
      allowNull: false,
      defaultValue: '',
    },
    duration: {
      type: DataTypes.ENUM('forever', 'once', 'repeating'),
      allowNull: false,
    },
    duration_in_months: {
      type: DataTypes.INTEGER,
      allowNull: false,
      defaultValue: 0,
    },
    name: {
      type: DataTypes.STRING(64),
      allowNull: false,
    },
    description: {
      type: DataTypes.TEXT,
      allowNull: true,
    },
    applies_to: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    currency_options: {
      type: DataTypes.JSON,
      defaultValue: {},
    },
    max_redemptions: {
      type: DataTypes.INTEGER,
      allowNull: true,
    },
    redeem_by: {
      type: DataTypes.INTEGER,
      allowNull: true,
    },
    times_redeemed: {
      type: DataTypes.INTEGER,
      defaultValue: 0,
    },
    valid: {
      type: DataTypes.BOOLEAN,
      defaultValue: false,
    },
    metadata: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    created_via: {
      type: DataTypes.ENUM('api', 'dashboard', 'portal'),
      allowNull: false,
    },
    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(Coupon.GENESIS_ATTRIBUTES, {
      sequelize,
      modelName: 'Coupon',
      tableName: 'coupons',
      createdAt: 'created_at',
      updatedAt: 'updated_at',
      hooks: {
        afterCreate: (model: Coupon, options) => {
          createEvent('Coupon', 'coupon.created', model, options).catch(console.error);
        },
        afterUpdate: (model: Coupon, options) => {
          createEvent('Coupon', 'coupon.updated', model, options).catch(console.error);
        },
        afterDestroy: (model: Coupon, options) => {
          createEvent('Coupon', 'coupon.deleted', model, options).catch(console.error);
        },
      },
    });
  }

  public static associate(models: any) {
    this.hasOne(models.PaymentCurrency, {
      sourceKey: 'currency_id',
      foreignKey: 'id',
      as: 'currency',
    });
  }

  public static formatBeforeSave(coupon: Partial<TCoupon>) {
    if (coupon.metadata) {
      coupon.metadata = formatMetadata(coupon.metadata);
    }

    if (coupon.max_redemptions) {
      coupon.max_redemptions = Number(coupon.max_redemptions);
    }

    if (coupon.times_redeemed) {
      coupon.times_redeemed = Number(coupon.times_redeemed);
    }

    if (coupon.duration === 'repeating') {
      coupon.duration_in_months = Number(coupon.duration_in_months || 0);
    }

    return coupon;
  }

  public static async insert(coupon: Partial<TCoupon>) {
    const formattedData = this.formatBeforeSave({
      locked: false,
      times_redeemed: 0,
      valid: true,
      metadata: {},
      amount_off: '0',
      currency_options: {},
      currency_id: '',
      duration_in_months: 0,
      ...coupon,
    });
    logger.info('Formatted data', { formattedData });
    try {
      return await this.create(formattedData as any);
    } catch (error) {
      logger.error('Coupon creation error details:', {
        error: error.message,
        formattedData,
        validationErrors: error.errors || [],
        sqlMessage: error.original?.message || 'No SQL message',
      });
      throw error;
    }
  }

  public async isUsed() {
    const { Discount } = this.sequelize.models;
    const discountCount = await Discount!.count({
      where: { coupon_id: this.id },
    });

    if (discountCount > 0) {
      return true;
    }

    return false;
  }

  public async getAppliedProducts() {
    if (!this.applies_to || !this.applies_to?.products) {
      return [];
    }
    const { Product } = this.sequelize.models;
    const productsWithPrices = await Promise.all(
      // @ts-ignore
      this.applies_to.products.map((productId) => Product!.expand(productId))
    );
    return productsWithPrices;
  }

  /**
   * Format currency options for storage - convert unit amounts to token amounts
   */
  public static formatCurrencyOptions(
    currencyOptions: Record<string, { amount_off: string }>,
    currencies: TPaymentCurrency[]
  ): Record<
    string,
    {
      amount_off: string;
    }
  > {
    const formatted: Record<string, { amount_off: string }> = {};

    for (const [currencyId, amountInfo] of Object.entries(currencyOptions || {})) {
      const currency = currencies.find((c) => c.id === currencyId);
      if (!currency) {
        throw new Error(`currency ${currencyId} used in coupon not found or inactive`);
      }

      formatted[currencyId] = {
        amount_off: fromTokenToUnit(
          trimDecimals(amountInfo.amount_off || '0', currency.decimal),
          currency.decimal
        ).toString(),
      };
    }

    return formatted;
  }
}

export type TCoupon = InferAttributes<Coupon>;
