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

import { createEvent } from '../../libs/audit';
import { createIdGenerator } from '../../libs/util';
import type {
  AfterPayment,
  CustomField,
  DonationSettings,
  LineItem,
  NftMintSettings,
  PaymentIntentData,
  SubscriptionData,
} from './types';

export const nextPaymentLinkId = createIdGenerator('plink', 24);

// @link https://stripe.com/docs/api/payment_links/payment_links
export class PaymentLink extends Model<InferAttributes<PaymentLink>, InferCreationAttributes<PaymentLink>> {
  // Unique identifier for the object.
  declare id: CreationOptional<string>;

  // Whether the PaymentLink is currently available for purchase.
  declare active: boolean;
  declare livemode: boolean;

  declare name: string;

  // The line items representing what is being sold. Each line item represents an item being sold. Up to 20 line items are supported.
  declare line_items: LineItem[];

  // Payment currency id
  declare currency_id: string;

  // The specified behavior after the purchase is complete.
  declare after_completion?: AfterPayment;

  // Enables user redeemable promotion codes.
  declare allow_promotion_codes: boolean;

  declare consent_collection?: {
    promotions?: LiteralUnion<'auto' | 'none', string>;
    terms_of_service?: LiteralUnion<'required' | 'none', string>;
  };

  // Collect additional information from your customer using custom fields. Up to 2 fields are supported.
  declare custom_fields: CustomField[];
  declare custom_text?: {
    shipping_address?: string;
    submit?: string;
  };

  declare customer_creation: LiteralUnion<'if_required' | 'always', string>;

  // Generate a post-purchase Invoice for one-time payments.
  declare invoice_creation?: {
    enabled: boolean;
    invoice_data?: any;
  };

  // The list of payment method types that customers can use
  declare payment_method_types?: string[];

  // Controls phone number collection settings during checkout.
  declare phone_number_collection?: {
    enabled: boolean;
  };

  // Configuration for collecting the customer’s billing address.
  declare billing_address_collection?: LiteralUnion<'auto' | 'required', string>;

  // Describes the type of transaction being performed in order to customize relevant text
  declare submit_type: LiteralUnion<'auto' | 'book' | 'donate' | 'pay', string>;

  // When creating a subscription, the specified configuration data will be used.
  declare subscription_data?: SubscriptionData;

  declare nft_mint_settings?: NftMintSettings;

  declare cross_sell_behavior?: LiteralUnion<'auto' | 'required', string>;

  // how to split the payment from this link
  declare donation_settings?: DonationSettings;

  declare metadata?: Record<string, any>;

  declare payment_intent_data?: PaymentIntentData;

  declare enable_subscription_grouping?: boolean;

  // A lookup key used to retrieve payment links dynamically from a static string
  declare lookup_key: string | null;

  // TODO: following fields not supported
  // application_fee_amount
  // application_fee_percent
  // transfer_data
  // automatic_tax
  // tax_id_collection
  // consent_collection
  // on_behalf_of
  // payment_method_collection
  // shipping_address_collection
  // shipping_options

  declare created_at: CreationOptional<Date>;

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

  declare updated_at: CreationOptional<Date>;

  public static readonly GENESIS_ATTRIBUTES = {
    id: {
      type: DataTypes.STRING(40),
      primaryKey: true,
      allowNull: false,
      defaultValue: nextPaymentLinkId,
    },
    active: {
      type: DataTypes.BOOLEAN,
      allowNull: false,
    },
    livemode: {
      type: DataTypes.BOOLEAN,
      allowNull: false,
    },
    line_items: {
      type: DataTypes.JSON,
      defaultValue: [],
    },
    name: {
      type: DataTypes.STRING(255),
      allowNull: true,
    },
    currency_id: {
      type: DataTypes.STRING(16),
      allowNull: true,
    },
    after_completion: {
      type: DataTypes.JSON,
      defaultValue: {
        type: 'hosted_confirmation',
      },
    },
    allow_promotion_codes: {
      type: DataTypes.BOOLEAN,
      defaultValue: false,
    },
    consent_collection: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    custom_fields: {
      type: DataTypes.JSON,
      defaultValue: [],
    },
    custom_text: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    customer_creation: {
      type: DataTypes.ENUM('if_required', 'always'),
      allowNull: false,
    },
    invoice_creation: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    payment_method_types: {
      type: DataTypes.JSON,
      defaultValue: [],
    },
    phone_number_collection: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    billing_address_collection: {
      type: DataTypes.ENUM('auto', 'required'),
      defaultValue: 'auto',
      allowNull: false,
    },
    submit_type: {
      type: DataTypes.ENUM('auto', 'book', 'donate', 'pay'),
      allowNull: false,
    },
    subscription_data: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    metadata: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    created_at: {
      type: DataTypes.DATE,
      defaultValue: DataTypes.NOW,
      allowNull: false,
    },
    created_via: {
      type: DataTypes.ENUM('api', 'dashboard', 'portal'),
    },
    updated_at: {
      type: DataTypes.DATE,
      defaultValue: DataTypes.NOW,
      allowNull: false,
    },
  };

  public static initialize(sequelize: any) {
    this.init(
      {
        ...PaymentLink.GENESIS_ATTRIBUTES,
        nft_mint_settings: {
          type: DataTypes.JSON,
          allowNull: true,
        },
        cross_sell_behavior: {
          type: DataTypes.ENUM('auto', 'required'),
          defaultValue: 'auto',
        },
        donation_settings: {
          type: DataTypes.JSON,
          allowNull: true,
        },
        enable_subscription_grouping: {
          type: DataTypes.BOOLEAN,
          defaultValue: false,
        },
        lookup_key: {
          type: DataTypes.STRING(128),
          allowNull: true,
        },
      },
      {
        sequelize,
        modelName: 'PaymentLink',
        tableName: 'payment_links',
        createdAt: 'created_at',
        updatedAt: 'updated_at',
        hooks: {
          afterCreate: (model: PaymentLink, options) => {
            createEvent('PaymentLink', 'payment_link.created', model, options).catch(console.error);
          },
          afterUpdate: (model: PaymentLink, options) => {
            createEvent('PaymentLink', 'payment_link.updated', model, options).catch(console.error);
          },
          afterDestroy: (model: PaymentLink, options) => {
            createEvent('PaymentLink', 'payment_link.deleted', model, options).catch(console.error);
          },
        },
      }
    );
  }

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

  public static async insert(paymentLink: InferCreationAttributes<PaymentLink>) {
    if (paymentLink.lookup_key) {
      const exist = await this.count({ where: { lookup_key: paymentLink.lookup_key } });
      if (exist) {
        throw new Error('lookup_key already exists');
      }
    }

    return this.create(paymentLink);
  }

  public static findByPkOrLookupKey(id: string, options: any = {}) {
    if (!id) {
      throw new Error('id is required');
    }
    return this.findOne({
      where: { [Op.or]: [{ id }, { lookup_key: id }] },
      ...options,
    });
  }
}

export type TPaymentLink = InferAttributes<PaymentLink>;
