import db from '../config/database';
import { logger } from '../utils/logger';
import { UserAssetService } from './UserAssetService';
import {
  PointsRecord,
  PointsOperationInput,
  PointsQueryOptions,
  PointsSourceType,
  PointsStatus,
} from '../models/UserAsset';

/**
 * PointsService - 积分管理引擎
 * 
 * [BE-UA002] 积分管理引擎
 * @description 管理用户积分的增加、消耗、冻结、过期等操作
 * 
 * 功能定位：
 * - 积分增减操作
 * - 积分冻结/解冻
 * - 积分过期处理
 * - 积分记录查询
 * 
 * 安全约束：
 * - 所有操作必须携带五元组追踪信息
 * - 积分余额不足时拒绝操作
 * 
 * @author AI-Backend-7
 * @taskId BE-UA002
 */
export class PointsService {
  private static readonly TABLE_NAME = 'cf_points_record';

  /**
   * 初始化数据库表
   * 遵循幂等性原则：使用 hasTable 前置校验
   */
  static async initTable(): Promise<void> {
    const hasTable = await db.schema.hasTable(this.TABLE_NAME);
    if (!hasTable) {
      logger.info(`[PointsService] Creating ${this.TABLE_NAME} table...`);
      await db.schema.createTable(this.TABLE_NAME, (table) => {
        table.string('id', 36).primary();
        table.string('tenant_id', 64).notNullable().index();
        table.string('shop_id', 64).notNullable().index();
        table.string('user_id', 64).notNullable().index();
        table.string('task_id', 64);
        table.string('trace_id', 64).notNullable();
        table.enum('business_type', ['TOC', 'TOB']).notNullable();
        table.integer('points').notNullable();
        table.integer('balance').notNullable();
        table.enum('source_type', [
          'PURCHASE', 'REVIEW', 'REFERRAL', 'SIGN_IN',
          'PROMOTION', 'ADMIN_ADJUST', 'EXPIRED', 'REDEEM'
        ]).notNullable();
        table.string('source_id', 64);
        table.string('description', 255).notNullable();
        table.enum('status', ['PENDING', 'CONFIRMED', 'EXPIRED', 'REDEEMED', 'CANCELLED']).defaultTo('CONFIRMED').notNullable();
        table.timestamp('expired_at');
        table.timestamp('created_at').defaultTo(db.fn.now());

        table.index(['tenant_id', 'user_id'], 'idx_points_user');
        table.index(['source_type', 'status'], 'idx_points_source');
        table.index(['created_at'], 'idx_points_time');
      });
      logger.info(`[PointsService] Table ${this.TABLE_NAME} created`);
    }
  }

  /**
   * 增加积分
   * @param input 积分操作输入
   * @returns 积分记录ID
   */
  static async addPoints(input: PointsOperationInput): Promise<string> {
    if (input.points <= 0) {
      throw new Error('Points must be positive for add operation');
    }

    const asset = await UserAssetService.getOrCreate({
      tenantId: input.tenantId,
      shopId: input.shopId,
      userId: input.userId,
      traceId: input.traceId,
    });

    const id = this.generateId();
    const newBalance = asset.availablePoints + input.points;

    await db(this.TABLE_NAME).insert({
      id,
      tenant_id: input.tenantId,
      shop_id: input.shopId,
      user_id: input.userId,
      task_id: input.taskId || null,
      trace_id: input.traceId,
      business_type: input.businessType,
      points: input.points,
      balance: newBalance,
      source_type: input.sourceType,
      source_id: input.sourceId || null,
      description: input.description,
      status: 'CONFIRMED',
      expired_at: input.expiredAt || null,
      created_at: new Date(),
    });

    await UserAssetService.updatePoints(
      input.tenantId,
      input.shopId,
      input.userId,
      input.points,
      0,
      input.traceId
    );

    logger.info(`[PointsService] Points added: userId=${input.userId}, points=${input.points}, source=${input.sourceType}, traceId=${input.traceId}`);

    return id;
  }

  /**
   * 扣减积分
   * @param input 积分操作输入
   * @returns 积分记录ID
   */
  static async deductPoints(input: PointsOperationInput): Promise<string> {
    if (input.points <= 0) {
      throw new Error('Points must be positive for deduct operation');
    }

    const asset = await UserAssetService.getByUserId(input.tenantId, input.shopId, input.userId);
    if (!asset || asset.availablePoints < input.points) {
      throw new Error(`Insufficient points: userId=${input.userId}, available=${asset?.availablePoints || 0}, required=${input.points}`);
    }

    const id = this.generateId();
    const newBalance = asset.availablePoints - input.points;

    await db(this.TABLE_NAME).insert({
      id,
      tenant_id: input.tenantId,
      shop_id: input.shopId,
      user_id: input.userId,
      task_id: input.taskId || null,
      trace_id: input.traceId,
      business_type: input.businessType,
      points: -input.points,
      balance: newBalance,
      source_type: input.sourceType,
      source_id: input.sourceId || null,
      description: input.description,
      status: 'CONFIRMED',
      created_at: new Date(),
    });

    await UserAssetService.updatePoints(
      input.tenantId,
      input.shopId,
      input.userId,
      -input.points,
      0,
      input.traceId
    );

    logger.info(`[PointsService] Points deducted: userId=${input.userId}, points=${input.points}, source=${input.sourceType}, traceId=${input.traceId}`);

    return id;
  }

  /**
   * 冻结积分
   * @param input 积分操作输入
   * @returns 冻结记录ID
   */
  static async freezePoints(input: PointsOperationInput): Promise<string> {
    if (input.points <= 0) {
      throw new Error('Points must be positive for freeze operation');
    }

    const asset = await UserAssetService.getByUserId(input.tenantId, input.shopId, input.userId);
    if (!asset || asset.availablePoints < input.points) {
      throw new Error(`Insufficient points to freeze: userId=${input.userId}, available=${asset?.availablePoints || 0}, required=${input.points}`);
    }

    const id = this.generateId();

    await db(this.TABLE_NAME).insert({
      id,
      tenant_id: input.tenantId,
      shop_id: input.shopId,
      user_id: input.userId,
      task_id: input.taskId || null,
      trace_id: input.traceId,
      business_type: input.businessType,
      points: 0,
      balance: asset.availablePoints,
      source_type: input.sourceType,
      source_id: input.sourceId || null,
      description: `冻结积分: ${input.description}`,
      status: 'PENDING',
      created_at: new Date(),
    });

    await UserAssetService.freezePoints(
      input.tenantId,
      input.shopId,
      input.userId,
      input.points,
      input.traceId
    );

    logger.info(`[PointsService] Points frozen: userId=${input.userId}, points=${input.points}, traceId=${input.traceId}`);

    return id;
  }

  /**
   * 解冻积分
   * @param input 积分操作输入
   * @returns 解冻记录ID
   */
  static async unfreezePoints(input: PointsOperationInput): Promise<string> {
    if (input.points <= 0) {
      throw new Error('Points must be positive for unfreeze operation');
    }

    const asset = await UserAssetService.getByUserId(input.tenantId, input.shopId, input.userId);
    if (!asset || asset.frozenPoints < input.points) {
      throw new Error(`Insufficient frozen points: userId=${input.userId}, frozen=${asset?.frozenPoints || 0}, required=${input.points}`);
    }

    const id = this.generateId();

    await db(this.TABLE_NAME).insert({
      id,
      tenant_id: input.tenantId,
      shop_id: input.shopId,
      user_id: input.userId,
      task_id: input.taskId || null,
      trace_id: input.traceId,
      business_type: input.businessType,
      points: 0,
      balance: asset.availablePoints,
      source_type: input.sourceType,
      source_id: input.sourceId || null,
      description: `解冻积分: ${input.description}`,
      status: 'CONFIRMED',
      created_at: new Date(),
    });

    await UserAssetService.unfreezePoints(
      input.tenantId,
      input.shopId,
      input.userId,
      input.points,
      input.traceId
    );

    logger.info(`[PointsService] Points unfrozen: userId=${input.userId}, points=${input.points}, traceId=${input.traceId}`);

    return id;
  }

  /**
   * 取消冻结并扣减积分
   * @param input 积分操作输入
   * @returns 记录ID
   */
  static async confirmDeductFromFrozen(input: PointsOperationInput): Promise<string> {
    if (input.points <= 0) {
      throw new Error('Points must be positive');
    }

    const asset = await UserAssetService.getByUserId(input.tenantId, input.shopId, input.userId);
    if (!asset || asset.frozenPoints < input.points) {
      throw new Error(`Insufficient frozen points: userId=${input.userId}, frozen=${asset?.frozenPoints || 0}, required=${input.points}`);
    }

    const id = this.generateId();

    await db(this.TABLE_NAME).insert({
      id,
      tenant_id: input.tenantId,
      shop_id: input.shopId,
      user_id: input.userId,
      task_id: input.taskId || null,
      trace_id: input.traceId,
      business_type: input.businessType,
      points: -input.points,
      balance: asset.availablePoints,
      source_type: input.sourceType,
      source_id: input.sourceId || null,
      description: `确认扣减冻结积分: ${input.description}`,
      status: 'CONFIRMED',
      created_at: new Date(),
    });

    await db('cf_user_asset')
      .where({ tenant_id: input.tenantId, shop_id: input.shopId, user_id: input.userId })
      .update({
        frozen_points: asset.frozenPoints - input.points,
        total_points: asset.totalPoints - input.points,
        updated_at: new Date(),
      });

    logger.info(`[PointsService] Frozen points deducted: userId=${input.userId}, points=${input.points}, traceId=${input.traceId}`);

    return id;
  }

  /**
   * 处理过期积分
   * @param tenantId 租户ID
   * @returns 处理的记录数
   */
  static async processExpiredPoints(tenantId: string): Promise<number> {
    const now = new Date();

    const expiredRecords = await db(this.TABLE_NAME)
      .where('tenant_id', tenantId)
      .where('status', 'CONFIRMED')
      .where('points', '>', 0)
      .whereNotNull('expired_at')
      .where('expired_at', '<', now);

    let processedCount = 0;

    for (const record of expiredRecords) {
      try {
        const asset = await UserAssetService.getByUserId(tenantId, record.shop_id, record.user_id);
        if (!asset || asset.availablePoints < record.points) {
          continue;
        }

        await db(this.TABLE_NAME).insert({
          id: this.generateId(),
          tenant_id: record.tenant_id,
          shop_id: record.shop_id,
          user_id: record.user_id,
          trace_id: `EXPIRE-${record.id}`,
          business_type: record.business_type,
          points: -record.points,
          balance: asset.availablePoints - record.points,
          source_type: 'EXPIRED',
          source_id: record.id,
          description: `积分过期: ${record.description}`,
          status: 'EXPIRED',
          created_at: new Date(),
        });

        await db(this.TABLE_NAME)
          .where({ id: record.id })
          .update({ status: 'EXPIRED' });

        await UserAssetService.updatePoints(
          tenantId,
          record.shop_id,
          record.user_id,
          -record.points,
          0,
          `EXPIRE-${record.id}`
        );

        processedCount++;
      } catch (error: any) {
        logger.error(`[PointsService] Failed to process expired points: recordId=${record.id}, error=${error.message}`);
      }
    }

    logger.info(`[PointsService] Expired points processed: count=${processedCount}`);

    return processedCount;
  }

  /**
   * 查询积分记录
   * @param options 查询选项
   * @returns 积分记录列表和总数
   */
  static async query(
    options: PointsQueryOptions
  ): Promise<{ data: PointsRecord[]; total: number }> {
    const { page = 1, pageSize = 20 } = options;
    const offset = (page - 1) * pageSize;

    let query = db(this.TABLE_NAME)
      .where('tenant_id', options.tenantId);

    if (options.shopId) {
      query = query.where('shop_id', options.shopId);
    }
    if (options.userId) {
      query = query.where('user_id', options.userId);
    }
    if (options.sourceType) {
      query = query.where('source_type', options.sourceType);
    }
    if (options.status) {
      query = query.where('status', options.status);
    }
    if (options.startDate) {
      query = query.where('created_at', '>=', options.startDate);
    }
    if (options.endDate) {
      query = query.where('created_at', '<=', options.endDate);
    }

    const totalQuery = query.clone();
    const [{ count }] = await totalQuery.count('* as count');
    const total = Number(count);

    const rows = await query
      .orderBy('created_at', 'desc')
      .limit(pageSize)
      .offset(offset);

    const data = rows.map(row => this.mapRowToRecord(row));

    return { data, total };
  }

  /**
   * 获取用户积分流水
   * @param tenantId 租户ID
   * @param shopId 店铺ID
   * @param userId 用户ID
   * @param limit 限制条数
   * @returns 积分记录列表
   */
  static async getUserPointsHistory(
    tenantId: string,
    shopId: string,
    userId: string,
    limit: number = 20
  ): Promise<PointsRecord[]> {
    const rows = await db(this.TABLE_NAME)
      .where({ tenant_id: tenantId, shop_id: shopId, user_id: userId })
      .orderBy('created_at', 'desc')
      .limit(limit);

    return rows.map(row => this.mapRowToRecord(row));
  }

  /**
   * 获取积分统计
   * @param tenantId 租户ID
   * @param shopId 店铺ID（可选）
   * @param startDate 开始日期
   * @param endDate 结束日期
   * @returns 统计数据
   */
  static async getPointsStatistics(
    tenantId: string,
    shopId?: string,
    startDate?: Date,
    endDate?: Date
  ): Promise<{
    totalEarned: number;
    totalSpent: number;
    totalExpired: number;
    totalFrozen: number;
    bySource: Record<PointsSourceType, { earned: number; spent: number }>;
  }> {
    let query = db(this.TABLE_NAME)
      .where('tenant_id', tenantId)
      .where('status', 'CONFIRMED');

    if (shopId) {
      query = query.where('shop_id', shopId);
    }
    if (startDate) {
      query = query.where('created_at', '>=', startDate);
    }
    if (endDate) {
      query = query.where('created_at', '<=', endDate);
    }

    const rows = await query;

    let totalEarned = 0;
    let totalSpent = 0;
    let totalExpired = 0;
    let totalFrozen = 0;

    const bySource: Record<PointsSourceType, { earned: number; spent: number }> = {
      PURCHASE: { earned: 0, spent: 0 },
      REVIEW: { earned: 0, spent: 0 },
      REFERRAL: { earned: 0, spent: 0 },
      SIGN_IN: { earned: 0, spent: 0 },
      PROMOTION: { earned: 0, spent: 0 },
      ADMIN_ADJUST: { earned: 0, spent: 0 },
      EXPIRED: { earned: 0, spent: 0 },
      REDEEM: { earned: 0, spent: 0 },
    };

    for (const row of rows) {
      const points = Number(row.points);
      const sourceType = row.source_type as PointsSourceType;

      if (points > 0) {
        totalEarned += points;
        if (bySource[sourceType]) {
          bySource[sourceType].earned += points;
        }
      } else {
        totalSpent += Math.abs(points);
        if (bySource[sourceType]) {
          bySource[sourceType].spent += Math.abs(points);
        }
      }

      if (row.status === 'EXPIRED') {
        totalExpired += Math.abs(points);
      }
    }

    return {
      totalEarned,
      totalSpent,
      totalExpired,
      totalFrozen,
      bySource,
    };
  }

  /**
   * 管理员调整积分
   * @param input 积分操作输入
   * @param adminId 管理员ID
   * @returns 记录ID
   */
  static async adminAdjust(
    input: PointsOperationInput,
    adminId: string
  ): Promise<string> {
    const adjustedInput: PointsOperationInput = {
      ...input,
      sourceType: 'ADMIN_ADJUST',
      description: `[管理员${adminId}] ${input.description}`,
    };

    if (input.points > 0) {
      return await this.addPoints(adjustedInput);
    } else {
      return await this.deductPoints({
        ...adjustedInput,
        points: Math.abs(input.points),
      });
    }
  }

  /**
   * 生成唯一ID
   */
  private static generateId(): string {
    return `PTS-${Date.now()}-${Math.random().toString(36).substring(2, 10)}`;
  }

  /**
   * 将数据库行映射为记录对象
   */
  private static mapRowToRecord(row: Record<string, any>): PointsRecord {
    return {
      id: row.id,
      tenantId: row.tenant_id,
      shopId: row.shop_id,
      userId: row.user_id,
      taskId: row.task_id,
      traceId: row.trace_id,
      businessType: row.business_type,
      points: Number(row.points),
      balance: Number(row.balance),
      sourceType: row.source_type,
      sourceId: row.source_id,
      description: row.description,
      status: row.status,
      expiredAt: row.expired_at,
      createdAt: row.created_at,
    };
  }
}
