import security from '@blocklet/sdk/lib/security';
import cloneDeep from 'lodash/cloneDeep';
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';

export const nextExchangeRateProviderId = createIdGenerator('erp', 24);

export class ExchangeRateProvider extends Model<
  InferAttributes<ExchangeRateProvider>,
  InferCreationAttributes<ExchangeRateProvider>
> {
  declare id: CreationOptional<string>;
  declare name: string;
  declare type: CreationOptional<LiteralUnion<'token-data' | 'coingecko' | 'coinmarketcap', string>>;
  declare enabled: CreationOptional<boolean>;
  declare priority: CreationOptional<number>;
  declare status: CreationOptional<LiteralUnion<'active' | 'degraded' | 'paused' | 'inactive', string>>;
  declare paused_reason: string | null;
  declare config: Record<string, any> | null;
  declare last_success_at: Date | null;
  declare last_failure_at: Date | null;
  declare failure_count: CreationOptional<number>;
  declare created_at: CreationOptional<Date>;
  declare updated_at: CreationOptional<Date>;

  public static initialize(sequelize: any) {
    this.init(
      {
        id: {
          type: DataTypes.STRING(32),
          primaryKey: true,
          allowNull: false,
          defaultValue: nextExchangeRateProviderId,
        },
        name: {
          type: DataTypes.STRING(64),
          allowNull: false,
          unique: true,
        },
        type: {
          type: DataTypes.STRING(32),
          allowNull: false,
          defaultValue: 'token-data',
        },
        enabled: {
          type: DataTypes.BOOLEAN,
          allowNull: false,
          defaultValue: true,
        },
        priority: {
          type: DataTypes.INTEGER,
          allowNull: false,
          defaultValue: 1,
        },
        status: {
          type: DataTypes.ENUM('active', 'degraded', 'paused', 'inactive'),
          allowNull: false,
          defaultValue: 'active',
        },
        paused_reason: {
          type: DataTypes.STRING(512),
          allowNull: true,
        },
        config: {
          type: DataTypes.JSON,
          allowNull: true,
        },
        last_success_at: {
          type: DataTypes.DATE,
          allowNull: true,
        },
        last_failure_at: {
          type: DataTypes.DATE,
          allowNull: true,
        },
        failure_count: {
          type: DataTypes.INTEGER,
          allowNull: false,
          defaultValue: 0,
        },
        created_at: {
          type: DataTypes.DATE,
          defaultValue: DataTypes.NOW,
          allowNull: false,
        },
        updated_at: {
          type: DataTypes.DATE,
          defaultValue: DataTypes.NOW,
          allowNull: false,
        },
      },
      {
        sequelize,
        modelName: 'ExchangeRateProvider',
        tableName: 'exchange_rate_providers',
        createdAt: 'created_at',
        updatedAt: 'updated_at',
        hooks: {
          afterCreate: (model: ExchangeRateProvider, options) => {
            createEvent('ExchangeRateProvider', 'exchange_rate_provider.created', model, options).catch(console.error);
          },
          afterUpdate: (model: ExchangeRateProvider, options) => {
            createEvent('ExchangeRateProvider', 'exchange_rate_provider.updated', model, options).catch(console.error);
          },
          afterDestroy: (model: ExchangeRateProvider, options) => {
            createEvent('ExchangeRateProvider', 'exchange_rate_provider.deleted', model, options).catch(console.error);
          },
        },
      }
    );
  }

  public static getActiveProviders() {
    return this.findAll({
      where: { enabled: true, status: { [Op.ne]: 'paused' } },
      order: [['priority', 'ASC']],
    });
  }

  public async recordSuccess() {
    await this.update({
      last_success_at: new Date(),
      failure_count: 0,
      status: 'active',
    });
  }

  public async recordFailure(reason?: string) {
    const newFailureCount = this.failure_count + 1;
    const updates: any = {
      last_failure_at: new Date(),
      failure_count: newFailureCount,
    };

    // Auto-pause after 5 consecutive failures
    // BUT: Never pause the last remaining provider to prevent total service outage
    if (newFailureCount >= 5) {
      // Check if there are other active providers
      const otherActiveProviders = await ExchangeRateProvider.count({
        where: {
          id: { [Op.ne]: this.id },
          enabled: true,
          status: { [Op.ne]: 'paused' },
        },
      });

      // Only pause if there are other providers available
      if (otherActiveProviders > 0) {
        updates.status = 'paused';
        updates.paused_reason = reason || 'Too many consecutive failures';
      } else {
        // Keep as degraded instead of pausing to maintain service availability
        updates.status = 'degraded';
        updates.paused_reason = `${reason || 'Multiple failures'} (last provider, not paused to maintain service)`;
      }
    } else {
      updates.status = 'degraded';
    }

    await this.update(updates);
  }

  /**
   * Encrypt sensitive config data (API keys)
   * Should be called before saving to database
   */
  public static encryptConfig(config: Record<string, any> | null): Record<string, any> | null {
    if (!config) return null;

    const tmp = cloneDeep(config);

    // Encrypt API key if present
    if (tmp.api_key && typeof tmp.api_key === 'string') {
      tmp.api_key = security.encrypt(tmp.api_key);
    }

    return tmp;
  }

  /**
   * Decrypt sensitive config data (API keys)
   * Should be called after reading from database
   */
  public static decryptConfig(config: Record<string, any> | null): Record<string, any> | null {
    if (!config) return null;

    const tmp = cloneDeep(config);

    // Decrypt API key if present
    if (tmp.api_key && typeof tmp.api_key === 'string') {
      try {
        tmp.api_key = security.decrypt(tmp.api_key);
      } catch (error) {
        // If decryption fails, the api_key might already be decrypted or invalid
        // Keep the original value
      }
    }

    return tmp;
  }

  /**
   * Mask sensitive config data for display
   * Returns config with API key partially masked
   */
  public static maskConfig(config: Record<string, any> | null): Record<string, any> | null {
    if (!config) return null;

    const tmp = cloneDeep(config);

    // Mask API key if present (show first 4 and last 4 characters)
    if (tmp.api_key && typeof tmp.api_key === 'string') {
      const key = tmp.api_key;
      if (key.length > 8) {
        tmp.api_key = `${key.substring(0, 4)}${'*'.repeat(key.length - 8)}${key.substring(key.length - 4)}`;
      } else {
        tmp.api_key = '*'.repeat(key.length);
      }
    }

    return tmp;
  }
}

export type TExchangeRateProvider = InferAttributes<ExchangeRateProvider>;
