/* eslint-disable @typescript-eslint/lines-between-class-members */
import {
  CreationOptional,
  DataTypes,
  FindOptions,
  InferAttributes,
  InferCreationAttributes,
  Model,
  Op,
  QueryTypes,
} from 'sequelize';
import { getUrl } from '@blocklet/sdk';
import type { LiteralUnion } from 'type-fest';
import { fromTokenToUnit } from '@ocap/util';
import { createIdGenerator } from '../../libs/util';
import { RechargeConfig, VaultConfig } from './types';

const nextId = createIdGenerator('pc', 12);

// eslint-disable-next-line prettier/prettier
export class PaymentCurrency extends Model<InferAttributes<PaymentCurrency>, InferCreationAttributes<PaymentCurrency>> {
  // Unique identifier for the object.
  declare id: CreationOptional<string>;
  declare payment_method_id: string;

  declare active: boolean;
  declare livemode: boolean;
  declare locked: boolean;
  declare is_base_currency: boolean;

  declare name: string;
  declare description: string;
  declare logo: string;
  declare symbol: string;
  declare decimal: number;
  declare maximum_precision?: number;

  declare minimum_payment_amount: string;
  declare maximum_payment_amount: string;

  // Contract address to interact with
  declare contract?: string;
  declare metadata: Record<string, any>;

  declare created_at: CreationOptional<Date>;
  declare updated_at: CreationOptional<Date>;
  declare vault_config?: VaultConfig;
  declare type: LiteralUnion<'standard' | 'credit', string>;
  declare recharge_config?: RechargeConfig;
  declare token_config?: Record<string, any> | null;

  public static readonly GENESIS_ATTRIBUTES = {
    id: {
      type: DataTypes.STRING(15),
      primaryKey: true,
      allowNull: false,
      defaultValue: nextId,
    },
    active: {
      type: DataTypes.BOOLEAN,
      allowNull: false,
    },
    livemode: {
      type: DataTypes.BOOLEAN,
      allowNull: false,
    },
    locked: {
      type: DataTypes.BOOLEAN,
      defaultValue: false,
    },
    is_base_currency: {
      type: DataTypes.BOOLEAN,
      defaultValue: false,
    },
    payment_method_id: {
      type: DataTypes.STRING(30),
      allowNull: false,
    },
    name: {
      type: DataTypes.STRING(64),
      allowNull: false,
    },
    description: {
      type: DataTypes.STRING(512),
      allowNull: true,
    },
    logo: {
      type: DataTypes.STRING(512),
      allowNull: false,
    },
    symbol: {
      type: DataTypes.STRING(16),
      allowNull: false,
    },
    decimal: {
      type: DataTypes.NUMBER,
      defaultValue: 2,
    },
    maximum_precision: {
      type: DataTypes.NUMBER,
      allowNull: true,
      defaultValue: 6,
    },
    minimum_payment_amount: {
      type: DataTypes.STRING(32),
      defaultValue: '0',
    },
    maximum_payment_amount: {
      type: DataTypes.STRING(32),
      defaultValue: '0',
    },
    contract: {
      type: DataTypes.STRING(256),
      allowNull: true,
    },
    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,
    },
    vault_config: {
      type: DataTypes.JSON,
      allowNull: true,
    },
  };

  public static initialize(sequelize: any) {
    this.init(
      {
        ...PaymentCurrency.GENESIS_ATTRIBUTES,
        type: {
          type: DataTypes.ENUM('standard', 'credit'),
          defaultValue: 'standard',
          allowNull: false,
        },
        recharge_config: {
          type: DataTypes.JSON,
          allowNull: true,
        },
        token_config: {
          type: DataTypes.JSON,
          allowNull: true,
        },
      },
      {
        sequelize,
        modelName: 'PaymentCurrency',
        tableName: 'payment_currencies',
        createdAt: 'created_at',
        updatedAt: 'updated_at',
        defaultScope: {
          attributes: { exclude: ['vault_config', 'recharge_config'] },
        },
        scopes: {
          withVaultConfig: {
            attributes: { include: ['vault_config'] },
          },
          withRechargeConfig: {
            attributes: { include: ['recharge_config'] },
          },
        },
      }
    );
  }

  public static associate(models: any) {
    this.hasOne(models.PaymentMethod, {
      sourceKey: 'payment_method_id',
      foreignKey: 'id',
      as: 'payment_method',
    });
  }

  public static findByPkOrSymbol(id: string, options: FindOptions<PaymentCurrency> = {}) {
    return this.findOne({
      where: { [Op.or]: [{ id }, { symbol: id }] },
      ...options,
    });
  }

  public async isLocked(): Promise<boolean> {
    const { PaymentMethod } = this.sequelize.models;
    const method = (await PaymentMethod!.findByPk(this.payment_method_id)) as any;
    return this.locked || method?.default_currency_id === this.id;
  }

  public async isUsed(): Promise<boolean> {
    const { Price } = this.sequelize.models;
    const price = await Price!.findOne({
      where: {
        currency_id: this.id,
      },
    });
    if (price) {
      return true;
    }
    // @ts-ignore
    const [{ count }] = await this.sequelize.query(
      `SELECT count(p.id) AS count 
       FROM prices AS p 
       JOIN json_each(p.currency_options) AS option 
       ON json_extract(option.value, '$.currency_id') = ?`,
      {
        replacements: [this.id],
        type: QueryTypes.SELECT,
      }
    );
    return count > 0;
  }
  public static async createForMeter(
    meter: any,
    paymentMethodId: string,
    tokenConfig?: Record<string, any>,
    options?: { decimal?: number }
  ) {
    const existingCurrency = await this.findOne({
      where: {
        type: 'credit',
        symbol: meter.unit,
        livemode: meter.livemode || false,
      },
    });

    if (existingCurrency) {
      return existingCurrency;
    }

    const decimal = options?.decimal ?? 10;
    const currency = await this.create({
      type: 'credit',
      payment_method_id: paymentMethodId,
      name: meter.name,
      description: `Credit for ${meter.unit}`,
      symbol: meter.unit,
      logo: getUrl('/methods/arcblock.png'),
      decimal,
      maximum_precision: decimal,
      minimum_payment_amount: fromTokenToUnit('0.01', decimal).toString(),
      maximum_payment_amount: fromTokenToUnit('100000000', decimal).toString(),
      active: true,
      livemode: meter.livemode || false,
      is_base_currency: false,
      locked: false,
      metadata: {
        meter_id: meter.id,
        meter_event_name: meter.event_name,
        created_by_meter: true,
      },
      token_config: tokenConfig,
    });

    return currency;
  }

  public isCredit(): boolean {
    return PaymentCurrency.isCredit(this);
  }

  public isOnChainCredit(): boolean {
    return PaymentCurrency.isOnChainCredit(this);
  }

  // Static version that works with plain objects
  public static isCredit(currency: { type?: string | null }): boolean {
    return currency.type === 'credit';
  }

  // Static version that works with plain objects
  public static isOnChainCredit(currency: { type?: string | null; token_config?: any }): boolean {
    return PaymentCurrency.isCredit(currency) && !!currency.token_config;
  }
}

export type TPaymentCurrency = InferAttributes<PaymentCurrency>;
