import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model, Transaction } from 'sequelize';
import type { LiteralUnion } from 'type-fest';

import { createEvent } from '../../libs/audit';
import { createIdGenerator } from '../../libs/util';
import { QuoteMetadata, QuoteStatus } from './types';

export const nextPriceQuoteId = createIdGenerator('pq', 24);

/**
 * Create Quote Input (Final Freeze Architecture)
 *
 * Quote is created only at Submit time, directly as 'used' status.
 * @see Intent: blocklets/core/ai/intent/20260112-dynamic-price.md
 */
export interface CreateQuoteInput {
  price_id: string;
  session_id?: string;
  invoice_id?: string;
  idempotency_key: string;
  base_currency?: string;
  base_amount: string;
  target_currency_id: string;
  rate_currency_symbol: string;
  exchange_rate: string;
  quoted_amount: string;
  rate_provider_id: string;
  rate_provider_name: string;
  rate_timestamp_ms: number;
  slippage_percent?: number;
  max_payable_token?: string;
  min_acceptable_rate?: string;
  metadata?: QuoteMetadata;
}

export class PriceQuote extends Model<InferAttributes<PriceQuote>, InferCreationAttributes<PriceQuote>> {
  declare id: CreationOptional<string>;
  declare price_id: string;
  declare session_id?: string;
  declare invoice_id?: string;
  declare idempotency_key: string;

  // Base currency (always USD in P0)
  declare base_currency: string;
  declare base_amount: string;

  // Target currency and rate
  declare target_currency_id: string;
  declare rate_currency_symbol: string;
  declare exchange_rate: string; // USD per token
  declare quoted_amount: string; // in smallest unit

  // Rate provider info
  declare rate_provider_id: string;
  declare rate_provider_name: string;
  declare rate_timestamp_ms: number;

  // Lifecycle
  declare expires_at: number; // Unix timestamp
  declare status: CreationOptional<LiteralUnion<QuoteStatus, string>>;

  // Metadata
  declare metadata: QuoteMetadata | null;

  // Slippage snapshot (derived when quote is used)
  declare slippage_percent?: number;
  declare max_payable_token?: string;
  declare min_acceptable_rate?: string;
  declare slippage_derived_at_ms?: number;

  declare created_at: CreationOptional<Date>;

  public static initialize(sequelize: any) {
    this.init(
      {
        id: {
          type: DataTypes.STRING(32),
          primaryKey: true,
          allowNull: false,
          defaultValue: nextPriceQuoteId,
        },
        price_id: {
          type: DataTypes.STRING(32),
          allowNull: false,
        },
        session_id: {
          type: DataTypes.STRING(32),
          allowNull: true,
        },
        invoice_id: {
          type: DataTypes.STRING(32),
          allowNull: true,
        },
        idempotency_key: {
          type: DataTypes.STRING(128),
          allowNull: false,
          unique: true,
        },
        base_currency: {
          type: DataTypes.STRING(8),
          allowNull: false,
          defaultValue: 'USD',
        },
        base_amount: {
          type: DataTypes.STRING(32),
          allowNull: false,
        },
        target_currency_id: {
          type: DataTypes.STRING(16),
          allowNull: false,
        },
        rate_currency_symbol: {
          type: DataTypes.STRING(16),
          allowNull: false,
        },
        exchange_rate: {
          type: DataTypes.STRING(32),
          allowNull: false,
        },
        quoted_amount: {
          type: DataTypes.STRING(64),
          allowNull: false,
        },
        rate_provider_id: {
          type: DataTypes.STRING(32),
          allowNull: false,
        },
        rate_provider_name: {
          type: DataTypes.STRING(64),
          allowNull: false,
        },
        rate_timestamp_ms: {
          type: DataTypes.BIGINT,
          allowNull: false,
        },
        expires_at: {
          type: DataTypes.INTEGER,
          allowNull: false,
        },
        status: {
          // Final Freeze: Only 3 statuses - used/paid/payment_failed
          type: DataTypes.ENUM('used', 'paid', 'payment_failed'),
          allowNull: false,
          defaultValue: 'used',
        },
        metadata: {
          type: DataTypes.JSON,
          allowNull: true,
        },
        slippage_percent: {
          type: DataTypes.DECIMAL(5, 2),
          allowNull: true,
        },
        max_payable_token: {
          type: DataTypes.STRING(64),
          allowNull: true,
        },
        min_acceptable_rate: {
          type: DataTypes.STRING(32),
          allowNull: true,
        },
        slippage_derived_at_ms: {
          type: DataTypes.BIGINT,
          allowNull: true,
        },
        created_at: {
          type: DataTypes.DATE,
          defaultValue: DataTypes.NOW,
          allowNull: false,
        },
      },
      {
        sequelize,
        modelName: 'PriceQuote',
        tableName: 'price_quotes',
        createdAt: 'created_at',
        updatedAt: false,
        hooks: {
          afterCreate: (model: PriceQuote, options) => {
            // Async event creation - don't block transaction
            if (options.transaction) {
              // Defer until after transaction commits
              options.transaction.afterCommit(() => {
                createEvent('PriceQuote', 'price_quote.created', model, options).catch(console.error);
              });
            } else {
              // No transaction, create immediately
              createEvent('PriceQuote', 'price_quote.created', model, options).catch(console.error);
            }
          },
          afterUpdate: (model: PriceQuote, options) => {
            // Async event creation - don't block transaction
            if (options.transaction) {
              // Defer until after transaction commits
              options.transaction.afterCommit(() => {
                createEvent('PriceQuote', 'price_quote.updated', model, options).catch(console.error);
              });
            } else {
              // No transaction, create immediately
              createEvent('PriceQuote', 'price_quote.updated', model, options).catch(console.error);
            }
          },
        },
      }
    );
  }

  public static associate(models: any) {
    this.belongsTo(models.Price, {
      foreignKey: 'price_id',
      as: 'price',
    });
  }

  /**
   * Check if Quote is usable for payment
   * Final Freeze: 'used' status means Quote is ready for payment
   */
  public isUsable(): boolean {
    return this.status === 'used';
  }

  /**
   * Check if Quote can be retried (payment_failed can be retried)
   */
  public canRetry(): boolean {
    return this.status === 'used' || this.status === 'payment_failed';
  }

  /**
   * Find Quote by idempotency key (for idempotent Submit)
   * Returns any Quote with the given key regardless of status
   */
  public static findByIdempotencyKey(idempotencyKey: string): Promise<PriceQuote | null> {
    return this.findOne({
      where: {
        idempotency_key: idempotencyKey,
      },
    });
  }

  /**
   * Create Quote directly as 'used' (Final Freeze architecture)
   *
   * This is the ONLY way to create Quotes in the new architecture.
   * Quotes are created at Submit time, never during Preview.
   */
  public static async createUsedQuote(input: CreateQuoteInput, transaction?: Transaction): Promise<PriceQuote> {
    const now = Math.floor(Date.now() / 1000);
    const quote = await this.create(
      {
        ...input,
        base_currency: input.base_currency || 'USD',
        status: 'used',
        // expires_at kept for backward compatibility but not used for validation
        expires_at: now + 180, // 3 minutes (flow SLA, not Quote validity)
        slippage_derived_at_ms: Date.now(),
      },
      { transaction }
    );
    return quote;
  }

  /**
   * Mark Quote as paid (payment completed successfully)
   * Idempotent: if already paid, returns silently
   */
  public async markAsPaid(transaction?: any): Promise<void> {
    if (this.status === 'paid') {
      return;
    }
    await this.update({ status: 'paid' }, { transaction });
  }

  /**
   * Mark Quote as payment_failed
   * Note: payment_failed does NOT mean Quote is invalid, can be retried
   */
  public async markAsPaymentFailed(transaction?: any): Promise<void> {
    await this.update({ status: 'payment_failed' }, { transaction });
  }
}

export type TPriceQuote = InferAttributes<PriceQuote>;
