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

import { BN } from '@ocap/util';
import { createEvent } from '../../libs/audit';
import { createIdGenerator } from '../../libs/util';
import { GroupedBN, GroupedStrList, MeterEventPayload, MeterEventStatus, SourceData } from './types';
import { Customer } from './customer';
import { Subscription, type TSubscription } from './subscription';
import { Meter, type TMeter } from './meter';
import { PaymentCurrency, TPaymentCurrency } from './payment-currency';

export const nextMeterEventId = createIdGenerator('mevt', 14);

type TMeterEventExpanded = MeterEvent & {
  customer: Customer;
  subscription?: TSubscription;
  meter?: TMeter;
  paymentCurrency: TPaymentCurrency;
};

export class MeterEvent extends Model<InferAttributes<MeterEvent>, InferCreationAttributes<MeterEvent>> {
  declare id: CreationOptional<string>;
  declare event_name: string;
  declare payload: MeterEventPayload;
  declare identifier: string; // 防重复的唯一标识
  declare timestamp: number;
  declare livemode: boolean;
  declare status: MeterEventStatus;
  declare processed_at?: number; // 处理时间
  declare attempt_count: number; // 重试次数
  declare next_attempt?: number; // 下次尝试时间
  declare credit_consumed: string; // 已消费的credit数量
  declare credit_pending: string; // 待消费的credit数量（债务）
  declare metadata?: Record<string, any>;
  declare source_data?: SourceData;

  // 审计字段
  declare created_by?: string;
  declare created_via: LiteralUnion<'api' | 'dashboard' | 'webhook' | 'batch', string>;

  declare created_at: CreationOptional<Date>;
  declare updated_at: CreationOptional<Date>;

  public static readonly GENESIS_ATTRIBUTES = {
    id: {
      type: DataTypes.STRING(18),
      primaryKey: true,
      allowNull: false,
      defaultValue: nextMeterEventId,
    },
    event_name: {
      type: DataTypes.STRING(128),
      allowNull: false,
    },
    timestamp: {
      type: DataTypes.INTEGER,
      allowNull: false,
    },
    payload: {
      type: DataTypes.JSON,
      allowNull: false,
      validate: {
        hasRequiredFields(value: any) {
          if (!value || typeof value !== 'object') {
            throw new Error('Payload must be an object');
          }
          if (!value.customer_id || !value.value) {
            throw new Error('Payload must contain customer_id, value');
          }
        },
      },
    },
    identifier: {
      type: DataTypes.STRING(255),
      allowNull: false,
      unique: true, // 确保幂等性
    },
    livemode: {
      type: DataTypes.BOOLEAN,
      allowNull: false,
    },
    status: {
      type: DataTypes.ENUM('pending', 'processing', 'requires_action', 'requires_capture', 'completed', 'canceled'),
      defaultValue: 'pending',
      allowNull: false,
    },
    attempt_count: {
      type: DataTypes.INTEGER,
      defaultValue: 0,
      allowNull: false,
    },
    next_attempt: {
      type: DataTypes.INTEGER,
      allowNull: true,
    },
    processed_at: {
      type: DataTypes.INTEGER,
      allowNull: true,
    },
    credit_consumed: {
      type: DataTypes.STRING(40),
      defaultValue: '0',
      allowNull: false,
    },
    credit_pending: {
      type: DataTypes.STRING(40),
      defaultValue: '0',
      allowNull: false,
    },
    metadata: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    created_by: {
      type: DataTypes.STRING(40),
      allowNull: true,
    },
    created_via: {
      type: DataTypes.ENUM('api', 'dashboard', 'webhook', 'batch'),
      allowNull: false,
    },
    created_at: {
      type: DataTypes.DATE,
      defaultValue: DataTypes.NOW,
      allowNull: false,
    },
    updated_at: {
      type: DataTypes.DATE,
      defaultValue: DataTypes.NOW,
      allowNull: false,
    },
  };

  // 标记为成功完成
  public async markAsCompleted(): Promise<void> {
    this.status = 'completed';
    this.processed_at = Math.floor(Date.now() / 1000);
    this.next_attempt = undefined;
    // 清除错误信息
    if (this.metadata?.last_error) {
      this.metadata = {
        ...this.metadata,
        last_error: undefined,
        failed_at: undefined,
      };
    }
    await this.save();
  }

  // 标记为处理中
  public async markAsProcessing(): Promise<void> {
    this.status = 'processing';
    await this.save();
  }

  // 标记为需要重试 (参考 payment.ts 的重试机制)
  public async markAsRequiresCapture(errorMessage: string, nextAttemptTime: number): Promise<void> {
    this.status = 'requires_capture';
    this.attempt_count += 1;
    this.next_attempt = nextAttemptTime;
    this.metadata = {
      ...this.metadata,
      last_error: errorMessage,
      failed_at: Math.floor(Date.now() / 1000),
    };
    await this.save();
  }

  // 标记为需要手动处理
  public async markAsRequiresAction(errorMessage: string): Promise<void> {
    this.status = 'requires_action';
    this.metadata = {
      ...this.metadata,
      last_error: errorMessage,
      failed_at: Math.floor(Date.now() / 1000),
      requires_manual_intervention: true,
    };
    await this.save();
  }

  // 标记为取消
  public async markAsCanceled(reason: string): Promise<void> {
    this.status = 'canceled';
    this.metadata = {
      ...this.metadata,
      cancel_reason: reason,
      canceled_at: Math.floor(Date.now() / 1000),
    };
    await this.save();
  }

  // 获取客户ID
  public getCustomerId(): string {
    return this.payload.customer_id;
  }

  // 获取使用量
  public getValue(): string {
    return this.payload.value;
  }

  // 获取订阅ID
  public getSubscriptionId(): string | undefined {
    return this.payload.subscription_id;
  }

  public static initialize(sequelize: any) {
    this.init(
      {
        ...this.GENESIS_ATTRIBUTES,
        source_data: {
          type: DataTypes.JSON,
          allowNull: true,
        },
      },
      {
        sequelize,
        modelName: 'MeterEvent',
        tableName: 'meter_events',
        createdAt: 'created_at',
        updatedAt: 'updated_at',
        indexes: [{ fields: ['identifier'], unique: true }, { fields: ['status'] }, { fields: ['event_name'] }],
        hooks: {
          afterCreate: (model: MeterEvent, options) => {
            createEvent('MeterEvent', 'billing.meter_event.created', model, options).catch(console.error);
          },
        },
      }
    );
  }

  // 批量处理未处理的事件
  public static async processUnprocessedEvents(eventName?: string, limit: number = 100): Promise<MeterEvent[]> {
    const whereClause: any = {
      status: ['pending', 'requires_capture'],
    };
    if (eventName) {
      whereClause.event_name = eventName;
    }

    const events = await this.findAll({
      where: whereClause,
      order: [['created_at', 'ASC']],
      limit,
    });

    return events;
  }

  // 检查事件是否已存在（防重复）
  public static async isEventExists(identifier: string): Promise<boolean> {
    const event = await this.findOne({
      where: { identifier },
      attributes: ['id'],
    });
    return !!event;
  }

  // 获取事件统计
  public static async getEventStats(
    eventName: string,
    startTime?: Date,
    endTime?: Date
  ): Promise<{
    total_events: number;
    processed_events: number;
    pending_events: number;
    total_value: number;
  }> {
    const whereClause: any = { event_name: eventName };

    if (startTime || endTime) {
      whereClause.created_at = {};
      if (startTime) {
        whereClause.created_at[Op.gte] = startTime;
      }
      if (endTime) {
        whereClause.created_at[Op.lte] = endTime;
      }
    }

    const [totalEvents, processedEvents, totalValue] = await Promise.all([
      this.count({ where: whereClause }),
      this.count({ where: { ...whereClause, status: 'completed' } }),
      // @ts-ignore
      this.sequelize.query(
        `SELECT COALESCE(SUM(CAST(payload->>'value' AS DECIMAL)), 0) as total 
         FROM meter_events 
         WHERE event_name = :eventName 
         ${startTime ? 'AND created_at >= :startTime' : ''}
         ${endTime ? 'AND created_at <= :endTime' : ''}`,
        {
          replacements: {
            eventName,
            startTime: startTime?.toISOString(),
            endTime: endTime?.toISOString(),
          },
          type: QueryTypes.SELECT,
        }
      ),
    ]);

    return {
      total_events: totalEvents,
      processed_events: processedEvents,
      pending_events: totalEvents - processedEvents,
      total_value: (totalValue as any)?.[0]?.total || 0,
    };
  }

  public static async expand(
    events: MeterEvent[],
    livemode: boolean = true,
    options: { customer?: boolean; subscription?: boolean; meter?: boolean } = {}
  ): Promise<MeterEvent[]> {
    const { customer = true, subscription = true, meter = true } = options;

    let customers: Customer[] = [];
    let subscriptions: TSubscription[] = [];
    let meters: TMeter[] = [];

    if (customer) {
      const customerIds = events.map((x) => x.payload.customer_id);
      customers = await Customer.findAll({
        where: { [Op.or]: [{ id: { [Op.in]: customerIds } }, { did: { [Op.in]: customerIds } }] },
      });
    }
    if (subscription) {
      const subscriptionIds = events.map((x) => x.payload?.subscription_id).filter(Boolean) as string[];
      subscriptions = await Subscription.findAll({
        where: {
          id: {
            [Op.in]: subscriptionIds,
          },
        },
      });
    }
    if (meter) {
      const eventNames = events.map((x) => x.event_name);
      meters = await Meter.findAll({
        where: {
          event_name: {
            [Op.in]: eventNames,
          },
          livemode,
        },
      });
    }

    return events.map((x) => ({
      ...x.toJSON(),
      customer: customers.find((c) => c.id === x.payload.customer_id),
      subscription: subscriptions.find((s) => s.id === x.payload.subscription_id),
      meter: meters.find((m) => m.event_name === x.event_name),
    })) as TMeterEventExpanded[];
  }

  private static async _getPendingAmounts(
    where: WhereOptions<MeterEvent>,
    searchCurrencyId?: string
  ): Promise<[GroupedBN, GroupedStrList, MeterEvent[]]> {
    const events = await this.findAll({ where });
    const summary: GroupedBN = {};
    const detail: GroupedStrList = {};

    if (events.length === 0) {
      return [summary, detail, events];
    }

    const eventNames = [...new Set(events.map((e) => e.event_name))];
    const meters = await Meter.findAll({
      where: {
        event_name: {
          [Op.in]: eventNames,
        },
      },
    });
    const meterMap = new Map(meters.map((m) => [m.event_name, m]));

    const currencyIds = [...new Set(meters.map((m) => m.currency_id).filter((id): id is string => Boolean(id)))];
    const currencies =
      currencyIds.length > 0
        ? await PaymentCurrency.findAll({
            where: {
              id: {
                [Op.in]: currencyIds,
              },
            },
          })
        : [];
    const currencyMap = new Map(currencies.map((c) => [c.id, c]));

    events.forEach((event) => {
      const meter = meterMap.get(event.event_name);
      if (!meter || !meter.currency_id) {
        return;
      }
      const paymentCurrency = currencyMap.get(meter.currency_id);
      if (!paymentCurrency) {
        return;
      }
      const currencyId = meter.currency_id as string;
      if (searchCurrencyId && searchCurrencyId !== currencyId) {
        return;
      }
      if (!detail[currencyId]) {
        detail[currencyId] = [];
      }
      summary[currencyId] = new BN(summary[currencyId] || '0').add(new BN(event.credit_pending)).toString();
      detail[currencyId]!.push(event.id);
    });

    return [summary, detail, events];
  }

  public static getPendingAmounts({
    subscriptionId,
    livemode,
    currencyId,
    status,
    customerId,
  }: {
    subscriptionId?: string;
    livemode?: boolean;
    currencyId?: string;
    status?: MeterEventStatus | MeterEventStatus[];
    customerId?: string;
  }): Promise<[GroupedBN, GroupedStrList, MeterEvent[]]> {
    const where: any = {
      status: {
        [Op.in]: ['pending', 'requires_capture', 'requires_action'],
      },
      livemode: livemode ?? true,
    };
    if (status) {
      where.status = Array.isArray(status) ? { [Op.in]: status } : status;
    }

    if (subscriptionId) {
      where[Op.and] = where[Op.and] || [];
      where[Op.and].push(
        Sequelize.where(Sequelize.literal("json_extract(payload, '$.subscription_id')"), subscriptionId)
      );
    }
    if (customerId) {
      where[Op.and] = where[Op.and] || [];
      where[Op.and].push(Sequelize.where(Sequelize.literal("json_extract(payload, '$.customer_id')"), customerId));
    }

    return this._getPendingAmounts(where, currencyId);
  }
}

export type TMeterEvent = InferAttributes<MeterEvent>;
