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

import { createEvent, createStatusEvent } from '../../libs/audit';
import { createIdGenerator } from '../../libs/util';
import type { PaymentDetails, PaymentError, PaymentMethodOptions } from './types';

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

// @link https://stripe.com/docs/api/setup_intents
// eslint-disable-next-line prettier/prettier
export class SetupIntent extends Model<InferAttributes<SetupIntent>, InferCreationAttributes<SetupIntent>> {
  // Unique identifier for the object.
  declare id: CreationOptional<string>;

  declare livemode: boolean;
  declare description: string;

  declare currency_id: string;
  declare customer_id: string;

  // The setup error encountered in the previous SetupIntent confirmation
  declare last_setup_error: PaymentError | null;

  declare metadata?: Record<string, any>;

  declare status: LiteralUnion<
    | 'requires_payment_method'
    | 'requires_confirmation'
    | 'requires_action'
    | 'processing'
    | 'requires_capture'
    | 'canceled'
    | 'succeeded',
    string
  >;

  declare usage: LiteralUnion<'on_session' | 'off_session', string>;

  declare canceled_at?: number;
  declare cancellation_reason?: LiteralUnion<'abandoned' | 'duplicate' | 'requested_by_customer', string>;

  declare flow_directions: LiteralUnion<'inbound' | 'outbound', string>[];

  declare payment_method_options?: PaymentMethodOptions;
  declare payment_method_types?: string[];
  declare payment_method_id: string;

  // 3rd party payment tx hash
  declare setup_details?: PaymentDetails;

  // TODO: following fields not supported yet
  // application
  // attach_to_self
  // client_secret
  // latest_attempt
  // mandate
  // next_action
  // on_behalf_of
  // single_use_mandate

  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,
    },
    currency_id: {
      type: DataTypes.STRING(16),
      allowNull: false,
    },
    customer_id: {
      type: DataTypes.STRING(30),
      allowNull: true,
    },
    description: {
      type: DataTypes.STRING(512),
      allowNull: true,
    },
    last_setup_error: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    last_attempt: {
      type: DataTypes.STRING(30),
      allowNull: true,
    },
    metadata: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    status: {
      type: DataTypes.ENUM(
        'requires_payment_method',
        'requires_confirmation',
        'requires_action',
        'processing',
        'requires_capture',
        'canceled',
        'succeeded'
      ),
      allowNull: false,
    },
    usage: {
      type: DataTypes.ENUM('on_session', 'off_session'),
      allowNull: false,
    },
    canceled_at: {
      type: DataTypes.INTEGER,
      allowNull: true,
    },
    cancellation_reason: {
      type: DataTypes.ENUM('abandoned', 'duplicate', 'requested_by_customer'),
      allowNull: true,
    },
    flow_directions: {
      type: DataTypes.JSON,
      defaultValue: ['inbound', 'outbound'],
    },
    payment_method_types: {
      type: DataTypes.JSON,
      defaultValue: [],
    },
    payment_method_options: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    payment_method_id: {
      type: DataTypes.STRING(30),
      allowNull: false,
    },
    setup_details: {
      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(SetupIntent.GENESIS_ATTRIBUTES, {
      sequelize,
      modelName: 'SetupIntent',
      tableName: 'setup_intents',
      createdAt: 'created_at',
      updatedAt: 'updated_at',
      hooks: {
        afterCreate: (model: SetupIntent, options) => {
          createEvent('SetupIntent', 'setup_intent.created', model, options).catch(console.error);
        },
        afterUpdate: (model: SetupIntent, options) => {
          createStatusEvent(
            'SetupIntent',
            'setup_intent',
            {
              requires_action: 'requires_action',
              canceled: 'canceled',
              succeeded: 'succeeded',
            },
            model,
            options
          ).catch(console.error);
        },
      },
    });
  }

  public static associate(models: any) {
    this.hasOne(models.PaymentCurrency, {
      sourceKey: 'currency_id',
      foreignKey: 'id',
      as: 'paymentCurrency',
    });
    this.hasOne(models.PaymentMethod, {
      sourceKey: 'payment_method_id',
      foreignKey: 'id',
      as: 'paymentMethod',
    });
    this.hasOne(models.Customer, {
      sourceKey: 'customer_id',
      foreignKey: 'id',
      as: 'customer',
    });
  }

  public isImmutable() {
    return ['canceled', 'succeeded'].includes(this.status);
  }
}

export type TSetupIntent = InferAttributes<SetupIntent>;
