import db from '../config/database';
import { logger } from '../utils/logger';
import {
  UserAsset,
  UserAssetCreateInput,
  UserAssetQueryOptions,
  MemberLevel,
} from '../models/UserAsset';

/**
 * UserAssetService - 用户资产计算服务
 * 
 * [BE-UA001] 用户资产计算接口
 * @description 计算和管理用户资产数据，包括积分、消费、会员等级等
 * 
 * 功能定位：
 * - 用户资产统计与计算
 * - 资产数据查询
 * - 资产汇总报表
 * 
 * 安全约束：
 * - 所有操作必须携带五元组追踪信息
 * - 租户数据隔离
 * 
 * @author AI-Backend-7
 * @taskId BE-UA001
 */
export class UserAssetService {
  private static readonly TABLE_NAME = 'cf_user_asset';

  /**
   * 初始化数据库表
   * 遵循幂等性原则：使用 hasTable 前置校验
   */
  static async initTable(): Promise<void> {
    const hasTable = await db.schema.hasTable(this.TABLE_NAME);
    if (!hasTable) {
      logger.info(`[UserAssetService] 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.integer('total_points').defaultTo(0).notNullable();
        table.integer('available_points').defaultTo(0).notNullable();
        table.integer('frozen_points').defaultTo(0).notNullable();
        table.decimal('total_spent', 12, 2).defaultTo(0).notNullable();
        table.integer('total_orders').defaultTo(0).notNullable();
        table.enum('member_level', ['BRONZE', 'SILVER', 'GOLD', 'PLATINUM', 'DIAMOND']).defaultTo('BRONZE').notNullable();
        table.integer('member_score').defaultTo(0).notNullable();
        table.decimal('cashback_balance', 10, 2).defaultTo(0).notNullable();
        table.integer('coupon_count').defaultTo(0).notNullable();
        table.timestamp('last_active_at');
        table.timestamp('created_at').defaultTo(db.fn.now());
        table.timestamp('updated_at').defaultTo(db.fn.now());

        table.unique(['tenant_id', 'shop_id', 'user_id'], 'uk_asset_user');
        table.index(['tenant_id', 'member_level'], 'idx_asset_level');
        table.index(['available_points'], 'idx_asset_points');
      });
      logger.info(`[UserAssetService] Table ${this.TABLE_NAME} created`);
    }
  }

  /**
   * 获取或创建用户资产
   * @param input 创建输入
   * @returns 用户资产
   */
  static async getOrCreate(input: UserAssetCreateInput): Promise<UserAsset> {
    let asset = await this.getByUserId(input.tenantId, input.shopId, input.userId);

    if (!asset) {
      const id = this.generateId();
      const now = new Date();

      await db(this.TABLE_NAME).insert({
        id,
        tenant_id: input.tenantId,
        shop_id: input.shopId,
        user_id: input.userId,
        total_points: 0,
        available_points: 0,
        frozen_points: 0,
        total_spent: 0,
        total_orders: 0,
        member_level: 'BRONZE',
        member_score: 0,
        cashback_balance: 0,
        coupon_count: 0,
        last_active_at: now,
        created_at: now,
        updated_at: now,
      });

      asset = await this.getByUserId(input.tenantId, input.shopId, input.userId);
      logger.info(`[UserAssetService] User asset created: userId=${input.userId}, tenantId=${input.tenantId}, traceId=${input.traceId}`);
    }

    return asset!;
  }

  /**
   * 根据用户ID获取资产
   * @param tenantId 租户ID
   * @param shopId 店铺ID
   * @param userId 用户ID
   * @returns 用户资产
   */
  static async getByUserId(tenantId: string, shopId: string, userId: string): Promise<UserAsset | null> {
    const row = await db(this.TABLE_NAME)
      .where({ tenant_id: tenantId, shop_id: shopId, user_id: userId })
      .first();

    if (!row) return null;

    return this.mapRowToAsset(row);
  }

  /**
   * 根据ID获取资产
   * @param tenantId 租户ID
   * @param id 资产ID
   * @returns 用户资产
   */
  static async getById(tenantId: string, id: string): Promise<UserAsset | null> {
    const row = await db(this.TABLE_NAME)
      .where({ id, tenant_id: tenantId })
      .first();

    if (!row) return null;

    return this.mapRowToAsset(row);
  }

  /**
   * 查询用户资产列表
   * @param options 查询选项
   * @returns 资产列表和总数
   */
  static async query(
    options: UserAssetQueryOptions
  ): Promise<{ data: UserAsset[]; 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.memberLevel) {
      query = query.where('member_level', options.memberLevel);
    }
    if (options.minPoints !== undefined) {
      query = query.where('available_points', '>=', options.minPoints);
    }
    if (options.maxPoints !== undefined) {
      query = query.where('available_points', '<=', options.maxPoints);
    }

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

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

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

    return { data, total };
  }

  /**
   * 更新用户消费统计
   * @param tenantId 租户ID
   * @param shopId 店铺ID
   * @param userId 用户ID
   * @param amount 消费金额
   * @param traceId 追踪ID
   */
  static async updateSpending(
    tenantId: string,
    shopId: string,
    userId: string,
    amount: number,
    traceId: string
  ): Promise<void> {
    await db(this.TABLE_NAME)
      .where({ tenant_id: tenantId, shop_id: shopId, user_id: userId })
      .increment('total_spent', amount)
      .increment('total_orders', 1)
      .increment('member_score', Math.floor(amount))
      .update({
        last_active_at: new Date(),
        updated_at: new Date(),
      });

    await this.recalculateMemberLevel(tenantId, shopId, userId);

    logger.info(`[UserAssetService] Spending updated: userId=${userId}, amount=${amount}, traceId=${traceId}`);
  }

  /**
   * 更新积分余额
   * @param tenantId 租户ID
   * @param shopId 店铺ID
   * @param userId 用户ID
   * @param pointsDelta 积分变化（正数增加，负数减少）
   * @param frozenDelta 冻结积分变化
   * @param traceId 追踪ID
   */
  static async updatePoints(
    tenantId: string,
    shopId: string,
    userId: string,
    pointsDelta: number,
    frozenDelta: number,
    traceId: string
  ): Promise<void> {
    const asset = await this.getByUserId(tenantId, shopId, userId);
    if (!asset) {
      throw new Error(`User asset not found: userId=${userId}`);
    }

    const newAvailable = asset.availablePoints + pointsDelta;
    const newFrozen = asset.frozenPoints + frozenDelta;
    const newTotal = newAvailable + newFrozen;

    if (newAvailable < 0) {
      throw new Error(`Insufficient available points: userId=${userId}, available=${asset.availablePoints}, required=${Math.abs(pointsDelta)}`);
    }

    await db(this.TABLE_NAME)
      .where({ tenant_id: tenantId, shop_id: shopId, user_id: userId })
      .update({
        total_points: newTotal,
        available_points: newAvailable,
        frozen_points: newFrozen,
        updated_at: new Date(),
      });

    logger.info(`[UserAssetService] Points updated: userId=${userId}, delta=${pointsDelta}, frozenDelta=${frozenDelta}, traceId=${traceId}`);
  }

  /**
   * 更新返现余额
   * @param tenantId 租户ID
   * @param shopId 店铺ID
   * @param userId 用户ID
   * @param amount 金额变化
   * @param traceId 追踪ID
   */
  static async updateCashback(
    tenantId: string,
    shopId: string,
    userId: string,
    amount: number,
    traceId: string
  ): Promise<void> {
    await db(this.TABLE_NAME)
      .where({ tenant_id: tenantId, shop_id: shopId, user_id: userId })
      .increment('cashback_balance', amount)
      .update({
        updated_at: new Date(),
      });

    logger.info(`[UserAssetService] Cashback updated: userId=${userId}, amount=${amount}, traceId=${traceId}`);
  }

  /**
   * 更新优惠券数量
   * @param tenantId 租户ID
   * @param shopId 店铺ID
   * @param userId 用户ID
   * @param delta 数量变化
   * @param traceId 追踪ID
   */
  static async updateCouponCount(
    tenantId: string,
    shopId: string,
    userId: string,
    delta: number,
    traceId: string
  ): Promise<void> {
    await db(this.TABLE_NAME)
      .where({ tenant_id: tenantId, shop_id: shopId, user_id: userId })
      .increment('coupon_count', delta)
      .update({
        updated_at: new Date(),
      });

    logger.info(`[UserAssetService] Coupon count updated: userId=${userId}, delta=${delta}, traceId=${traceId}`);
  }

  /**
   * 重新计算会员等级
   * @param tenantId 租户ID
   * @param shopId 店铺ID
   * @param userId 用户ID
   */
  static async recalculateMemberLevel(
    tenantId: string,
    shopId: string,
    userId: string
  ): Promise<void> {
    const asset = await this.getByUserId(tenantId, shopId, userId);
    if (!asset) return;

    const newLevel = this.calculateLevel(asset.memberScore);

    if (newLevel !== asset.memberLevel) {
      await db(this.TABLE_NAME)
        .where({ tenant_id: tenantId, shop_id: shopId, user_id: userId })
        .update({
          member_level: newLevel,
          updated_at: new Date(),
        });

      logger.info(`[UserAssetService] Member level updated: userId=${userId}, oldLevel=${asset.memberLevel}, newLevel=${newLevel}`);
    }
  }

  /**
   * 根据积分计算会员等级
   * @param score 会员积分
   * @returns 会员等级
   */
  static calculateLevel(score: number): MemberLevel {
    if (score >= 10000) return 'DIAMOND';
    if (score >= 5000) return 'PLATINUM';
    if (score >= 2000) return 'GOLD';
    if (score >= 500) return 'SILVER';
    return 'BRONZE';
  }

  /**
   * 获取资产统计汇总
   * @param tenantId 租户ID
   * @param shopId 店铺ID（可选）
   * @returns 统计汇总
   */
  static async getStatistics(
    tenantId: string,
    shopId?: string
  ): Promise<{
    totalUsers: number;
    totalPoints: number;
    totalSpent: number;
    totalCashback: number;
    levelDistribution: Record<MemberLevel, number>;
  }> {
    let query = db(this.TABLE_NAME).where('tenant_id', tenantId);
    if (shopId) {
      query = query.where('shop_id', shopId);
    }

    const rows = await query;

    const totalUsers = rows.length;
    const totalPoints = rows.reduce((sum, row) => sum + Number(row.total_points), 0);
    const totalSpent = rows.reduce((sum, row) => sum + Number(row.total_spent), 0);
    const totalCashback = rows.reduce((sum, row) => sum + Number(row.cashback_balance), 0);

    const levelDistribution: Record<MemberLevel, number> = {
      BRONZE: 0,
      SILVER: 0,
      GOLD: 0,
      PLATINUM: 0,
      DIAMOND: 0,
    };

    for (const row of rows) {
      levelDistribution[row.member_level as MemberLevel]++;
    }

    return {
      totalUsers,
      totalPoints,
      totalSpent,
      totalCashback,
      levelDistribution,
    };
  }

  /**
   * 批量获取用户资产
   * @param tenantId 租户ID
   * @param shopId 店铺ID
   * @param userIds 用户ID列表
   * @returns 用户资产映射
   */
  static async batchGetByUserIds(
    tenantId: string,
    shopId: string,
    userIds: string[]
  ): Promise<Record<string, UserAsset>> {
    const rows = await db(this.TABLE_NAME)
      .where('tenant_id', tenantId)
      .where('shop_id', shopId)
      .whereIn('user_id', userIds);

    const result: Record<string, UserAsset> = {};
    for (const row of rows) {
      result[row.user_id] = this.mapRowToAsset(row);
    }

    return result;
  }

  /**
   * 冻结积分
   * @param tenantId 租户ID
   * @param shopId 店铺ID
   * @param userId 用户ID
   * @param points 冻结积分数量
   * @param traceId 追踪ID
   */
  static async freezePoints(
    tenantId: string,
    shopId: string,
    userId: string,
    points: number,
    traceId: string
  ): Promise<void> {
    const asset = await this.getByUserId(tenantId, shopId, userId);
    if (!asset || asset.availablePoints < points) {
      throw new Error(`Insufficient available points to freeze: userId=${userId}, available=${asset?.availablePoints || 0}, required=${points}`);
    }

    await db(this.TABLE_NAME)
      .where({ tenant_id: tenantId, shop_id: shopId, user_id: userId })
      .update({
        available_points: asset.availablePoints - points,
        frozen_points: asset.frozenPoints + points,
        updated_at: new Date(),
      });

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

  /**
   * 解冻积分
   * @param tenantId 租户ID
   * @param shopId 店铺ID
   * @param userId 用户ID
   * @param points 解冻积分数量
   * @param traceId 追踪ID
   */
  static async unfreezePoints(
    tenantId: string,
    shopId: string,
    userId: string,
    points: number,
    traceId: string
  ): Promise<void> {
    const asset = await this.getByUserId(tenantId, shopId, userId);
    if (!asset || asset.frozenPoints < points) {
      throw new Error(`Insufficient frozen points to unfreeze: userId=${userId}, frozen=${asset?.frozenPoints || 0}, required=${points}`);
    }

    await db(this.TABLE_NAME)
      .where({ tenant_id: tenantId, shop_id: shopId, user_id: userId })
      .update({
        available_points: asset.availablePoints + points,
        frozen_points: asset.frozenPoints - points,
        updated_at: new Date(),
      });

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

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

  /**
   * 将数据库行映射为资产对象
   */
  private static mapRowToAsset(row: Record<string, any>): UserAsset {
    return {
      id: row.id,
      tenantId: row.tenant_id,
      shopId: row.shop_id,
      userId: row.user_id,
      totalPoints: Number(row.total_points),
      availablePoints: Number(row.available_points),
      frozenPoints: Number(row.frozen_points),
      totalSpent: Number(row.total_spent),
      totalOrders: Number(row.total_orders),
      memberLevel: row.member_level,
      memberScore: Number(row.member_score),
      cashbackBalance: Number(row.cashback_balance),
      couponCount: Number(row.coupon_count),
      lastActiveAt: row.last_active_at,
      createdAt: row.created_at,
      updatedAt: row.updated_at,
    };
  }
}
