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

import { createCustomEvent, createEvent, createStatusEvent } from '../../libs/audit';
import dayjs from '../../libs/dayjs';
import { CHECKOUT_SESSION_TTL, createIdGenerator } from '../../libs/util';
import type {
  CurrencyConversion,
  CustomField,
  CustomerDetail,
  InvoiceData,
  LineItem,
  NftMintDetails,
  NftMintSettings,
  PaymentDetails,
  PaymentIntentData,
  ServiceAction,
  SubscriptionData,
} from './types';
import { Invoice } from './invoice';
import { Subscription } from './subscription';
import { PaymentIntent } from './payment-intent';

export const nextCheckoutSessionId = createIdGenerator('cs', 58);

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

  // ID of the payment currency
  declare currency_id: string;

  // ID of the invoice created by the Checkout Session, if it exists.
  declare invoice_id?: string;

  // customer id for the Checkout Session, if it exists.
  declare customer_id?: string;
  declare customer_did?: string;

  // The ID of the Payment Link that created this Session.
  declare payment_link_id?: string;

  // The ID of the subscription for Checkout Sessions in subscription mode.
  declare subscription_id?: string;

  // New fields for subscription grouping
  declare enable_subscription_grouping?: boolean;
  declare subscription_groups?: Record<string, string>;
  declare success_subscription_count?: number;

  // The ID of the original expired Checkout Session that triggered the recovery flow.
  declare recovered_from?: string;

  // The ID of the PaymentIntent for Checkout Sessions in payment mode.
  declare payment_intent_id?: string;

  // The ID of the SetupIntent for Checkout Sessions in setup mode.
  declare setup_intent_id?: string;

  // Pass subscription if the Checkout Session includes at least one recurring item.
  declare mode: LiteralUnion<'payment' | 'setup' | 'subscription', string>;

  declare status: LiteralUnion<'complete' | 'open' | 'expired', string>;
  declare payment_status: LiteralUnion<'paid' | 'unpaid' | 'no_payment_required', string>;

  declare line_items: LineItem[];

  // Total of all items before discounts or taxes are applied.
  declare amount_subtotal: string;
  // Total of all items after discounts and taxes are applied.
  declare amount_total: string;
  // User slippage percent (e.g., 0.5 means 0.5%)
  declare slippage_percent?: number;
  // Tax and discount details for the computed total amount.
  declare total_details?: {
    amount_discount?: string;
    amount_shipping?: string;
    amount_tax?: string;
    breakdown?: {
      discounts?: {
        amount?: string;
        discount?: string;
      }[];
      taxes?: {
        amount?: string;
        rate?: string;
      }[];
    };
  };

  declare url?: string;
  declare cancel_url?: string;
  declare success_url?: string;

  declare currency_conversion?: CurrencyConversion;

  // customer_email field on creation is stored here?
  declare customer_details?: CustomerDetail;
  declare customer_creation: LiteralUnion<'if_required' | 'always', string>;
  declare customer_update?: {
    address: LiteralUnion<'auto' | 'never', string>;
    name: LiteralUnion<'auto' | 'never', string>;
    shipping: LiteralUnion<'auto' | 'never', string>;
  };

  // When set, provides configuration for actions to take if this Checkout Session expires
  declare after_expiration?: {
    recovery: {
      enabled: boolean;
      allow_promotion_codes?: boolean;
    };
  };

  declare allow_promotion_codes?: boolean;

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

  declare custom_fields?: CustomField[];
  declare custom_text?: {
    shipping_address?: string;
    submit?: string;
  };

  // By default, this value is 24 hours from creation.
  declare expires_at: number;

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

  // 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;
  };

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

  declare metadata?: Record<string, any>;

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

  // When creating a subscription, the specified configuration data will be used.
  declare subscription_data?: SubscriptionData & {
    service_actions?: ServiceAction[];
    billing_cycle_anchor?: number;
    metadata?: Record<string, any>;
    proration_behavior?: LiteralUnion<'create_prorations' | 'none', string>;
    trial_end?: number;
    trial_settings?: {
      end_behavior: {
        missing_payment_method: LiteralUnion<'cancel' | 'pause' | 'create_invoice', string>;
      };
    };
    days_until_due?: number;
    days_until_cancel?: number;
    no_stake?: boolean;
  };

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

  // A subset of parameters to be passed to PaymentIntent creation for Checkout Sessions in payment mode.
  declare payment_intent_data?: PaymentIntentData;

  // Whether should we mint an nft to the customer on checkout complete
  // Will use setting from payment method
  declare nft_mint_status: LiteralUnion<'disabled' | 'pending' | 'minted' | 'sent' | 'error', string>;
  declare nft_mint_settings?: NftMintSettings;
  declare nft_mint_details?: NftMintDetails;

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

  // FIXME: Only exist on creation
  declare discounts?: Array<{
    coupon?: string;
    promotion_code?: string;
    discount_amount?: string;
    verification_method?: string;
    verification_data?: Record<string, any>;
  }>;
  declare fulfillment_status?: LiteralUnion<
    | 'pending'
    | 'processing'
    | 'completed'
    | 'failed'
    | 'cancelled'
    | 'max_retries_exceeded'
    | 'return_requested'
    | 'sent'
    | 'returning'
    | 'returned',
    string
  >;
  declare vendor_info?: Array<{
    vendor_id: string;
    vendor_key: string;
    vendor_type: string;
    order_id: string;
    status:
      | 'pending'
      | 'processing'
      | 'completed'
      | 'failed'
      | 'cancelled'
      | 'max_retries_exceeded'
      | 'return_requested'
      | 'sent'
      | 'returned';
    service_url?: string;
    app_url?: string;
    error_message?: string;
    amount: string;

    attempts?: number;
    lastAttemptAt?: string;
    returnRetry?: number;
    completedAt?: string;
    commissionAmount?: string;

    returnRequest?: {
      reason: string;
      requestedAt: string;
      status: 'pending' | 'accepted' | 'rejected';
      returnDetails?: string;
    };
  }>;

  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(64),
      primaryKey: true,
      allowNull: false,
      defaultValue: nextCheckoutSessionId,
    },
    livemode: {
      type: DataTypes.BOOLEAN,
      allowNull: false,
    },
    client_reference_id: {
      type: DataTypes.STRING(128),
      allowNull: true,
    },
    currency_id: {
      type: DataTypes.STRING(15),
      allowNull: false,
    },
    invoice_id: {
      type: DataTypes.STRING(30),
      allowNull: true,
    },
    customer_id: {
      type: DataTypes.STRING(30),
      allowNull: true,
    },
    customer_did: {
      type: DataTypes.STRING(40),
      allowNull: true,
    },
    payment_link_id: {
      type: DataTypes.STRING(30),
      allowNull: true,
    },
    subscription_id: {
      type: DataTypes.STRING(30),
      allowNull: true,
    },
    recovered_from: {
      type: DataTypes.STRING(30),
      allowNull: true,
    },
    payment_intent_id: {
      type: DataTypes.STRING(30),
      allowNull: true,
    },
    mode: {
      type: DataTypes.ENUM('payment', 'setup', 'subscription'),
      allowNull: false,
    },
    status: {
      type: DataTypes.ENUM('complete', 'open', 'expired'),
      allowNull: false,
    },
    payment_status: {
      type: DataTypes.ENUM('paid', 'unpaid', 'no_payment_required'),
      allowNull: false,
    },
    line_items: {
      type: DataTypes.JSON,
      allowNull: false,
    },
    amount_subtotal: {
      type: DataTypes.STRING(32),
      allowNull: false,
    },
    amount_total: {
      type: DataTypes.STRING(32),
      allowNull: false,
    },
    slippage_percent: {
      type: DataTypes.DECIMAL(5, 2),
      allowNull: false,
      defaultValue: 0.5,
    },
    total_details: {
      type: DataTypes.JSON,
      allowNull: false,
    },
    url: {
      type: DataTypes.STRING(512),
      allowNull: true,
    },
    cancel_url: {
      type: DataTypes.STRING(512),
      allowNull: true,
    },
    success_url: {
      type: DataTypes.STRING(512),
      allowNull: true,
    },
    currency_conversion: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    after_expiration: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    allow_promotion_codes: {
      type: DataTypes.BOOLEAN,
      defaultValue: false,
    },
    consent_collection: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    consent: {
      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,
    },
    customer_update: {
      type: DataTypes.JSON,
      defaultValue: {
        address: 'never',
        name: 'never',
        shipping: 'never',
      },
    },
    expires_at: {
      type: DataTypes.INTEGER,
      allowNull: true,
    },
    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,
    },
    payment_details: {
      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(
      {
        ...CheckoutSession.GENESIS_ATTRIBUTES,
        discounts: {
          type: DataTypes.JSON,
          allowNull: true,
        },
        payment_intent_data: {
          type: DataTypes.JSON,
          allowNull: true,
        },
        nft_mint_status: {
          type: DataTypes.ENUM('disabled', 'pending', 'minted', 'sent', 'error'),
          defaultValue: 'disabled',
          allowNull: false,
        },
        nft_mint_settings: {
          type: DataTypes.JSON,
          allowNull: true,
        },
        nft_mint_details: {
          type: DataTypes.JSON,
          allowNull: true,
        },
        cross_sell_behavior: {
          type: DataTypes.ENUM('auto', 'required'),
          defaultValue: 'auto',
        },
        setup_intent_id: {
          type: DataTypes.STRING(30),
          allowNull: true,
        },
        enable_subscription_grouping: {
          type: DataTypes.BOOLEAN,
          defaultValue: false,
        },
        subscription_groups: {
          type: DataTypes.JSON,
          allowNull: true,
        },
        success_subscription_count: {
          type: DataTypes.INTEGER,
          defaultValue: 0,
        },
        fulfillment_status: {
          type: DataTypes.STRING,
          allowNull: true,
        },
        vendor_info: {
          type: DataTypes.JSON,
          allowNull: true,
        },
      },
      {
        sequelize,
        modelName: 'CheckoutSession',
        tableName: 'checkout_sessions',
        createdAt: 'created_at',
        updatedAt: 'updated_at',
        hooks: {
          afterCreate: (model: CheckoutSession, options) => {
            createEvent('CheckoutSession', 'checkout.session.created', model, options).catch(console.error);
          },
          afterUpdate: (model: CheckoutSession, options) => {
            createStatusEvent(
              'CheckoutSession',
              'checkout.session',
              { complete: 'completed', expired: 'expired' },
              model,
              options
            ).catch(console.error);
            createCustomEvent(
              'CheckoutSession',
              'checkout.session',
              (current, previous) => {
                if (previous.nft_mint_status === 'pending' && current.nft_mint_status === 'minted') {
                  return 'nft_minted';
                }
                return '';
              },
              model,
              options
            ).catch(console.error);
          },
        },
      }
    );
  }

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

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

  public static cleanupExpiredSessions() {
    const now = dayjs().unix();
    return CheckoutSession.destroy({
      where: {
        status: 'expired',
        expires_at: {
          [Op.lt]: now - CHECKOUT_SESSION_TTL,
        },
      },
    });
  }

  public isImmutable() {
    return ['complete', 'expired'].includes(this.status);
  }
  public static async findBySubscriptionId(subscriptionId: string, options: FindOptions<CheckoutSession> = {}) {
    if (!subscriptionId) {
      return null;
    }
    let checkoutSession = await CheckoutSession.findOne({
      where: { subscription_id: subscriptionId },
      ...options,
    });
    if (!checkoutSession) {
      const subscription = await Subscription.findByPk(subscriptionId);
      if (subscription?.metadata?.checkout_session_id) {
        checkoutSession = await CheckoutSession.findByPk(subscription.metadata.checkout_session_id);
        return checkoutSession;
      }
      const invoice = await Invoice.findOne({
        // @ts-ignore
        where: {
          subscription_id: subscriptionId,
          checkout_session_id: {
            [Op.ne]: null,
          },
        },
        order: [['created_at', 'ASC']],
      });
      if (invoice) {
        checkoutSession = await CheckoutSession.findOne({
          where: { id: invoice.checkout_session_id },
          ...options,
        });
      }
    }
    return checkoutSession;
  }

  public static async findByPaymentIntentId(paymentIntentId: string): Promise<CheckoutSession | null> {
    try {
      // Method 1: Direct lookup by payment_intent_id
      let checkoutSession = await CheckoutSession.findOne({
        where: { payment_intent_id: paymentIntentId },
      });

      if (checkoutSession) {
        return checkoutSession;
      }

      // Method 2: Lookup via Invoice -> Subscription for original CheckoutSession
      // This typically occurs during subscription renewals
      const paymentIntent = await PaymentIntent.findByPk(paymentIntentId);
      if (!paymentIntent || !paymentIntent.invoice_id) {
        return null;
      }

      const invoice = await Invoice.findByPk(paymentIntent.invoice_id);
      if (!invoice || !invoice.subscription_id) {
        return null;
      }

      // Find original CheckoutSession through subscription
      checkoutSession = await CheckoutSession.findBySubscriptionId(invoice.subscription_id);
      if (checkoutSession) {
        return checkoutSession;
      }
      return null;
    } catch (error: any) {
      return null;
    }
  }

  public static async findByInvoiceId(invoiceId: string): Promise<CheckoutSession | null> {
    try {
      const invoice = await Invoice.findByPk(invoiceId);
      if (!invoice) {
        return null;
      }

      if (invoice.checkout_session_id) {
        return await CheckoutSession.findByPk(invoice.checkout_session_id);
      }

      if (!invoice.subscription_id) {
        return null;
      }

      // Find original CheckoutSession through subscription
      const checkoutSession = await CheckoutSession.findBySubscriptionId(invoice.subscription_id);
      return checkoutSession;
    } catch (error: any) {
      return null;
    }
  }
}

export type TCheckoutSession = InferAttributes<CheckoutSession>;
