/**
 * Zulip账号关联数据访问层
 * 
 * 功能描述：
 * - 提供Zulip账号关联数据的CRUD操作
 * - 封装复杂查询逻辑和数据库交互
 * - 实现数据访问层的业务逻辑抽象
 * - 支持事务操作确保数据一致性
 * 
 * 职责分离：
 * - 数据访问：负责所有数据库操作和查询
 * - 事务管理：处理需要原子性的复合操作
 * - 查询优化：提供高效的数据库查询方法
 * 
 * 最近修改：
 * - 2026-01-07: 代码规范优化 - 使用统一的常量文件，提高代码质量
 * - 2026-01-07: 代码规范优化 - 完善文件头注释和方法三级注释
 * - 2026-01-07: 功能新增 - 添加事务支持防止并发竞态条件
 * - 2026-01-07: 性能优化 - 优化查询语句添加LIMIT限制
 * - 2026-01-07: 功能新增 - 新增existsByGameUserId方法
 * 
 * @author angjustinl
 * @version 1.1.1
 * @since 2025-01-05
 * @lastModified 2026-01-07
 */

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, FindOptionsWhere, DataSource } from 'typeorm';
import { ZulipAccounts } from './zulip_accounts.entity';
import {
  DEFAULT_VERIFICATION_INTERVAL,
  DEFAULT_MAX_RETRY_COUNT,
  VERIFICATION_QUERY_LIMIT,
  ERROR_ACCOUNTS_QUERY_LIMIT,
} from './zulip_accounts.constants';
import {
  CreateZulipAccountData,
  UpdateZulipAccountData,
  ZulipAccountQueryOptions,
  StatusStatistics,
  IZulipAccountsRepository,
} from './zulip_accounts.types';

// 保持向后兼容的类型别名
export type CreateZulipAccountDto = CreateZulipAccountData;
export type UpdateZulipAccountDto = UpdateZulipAccountData;
export { ZulipAccountQueryOptions };

@Injectable()
export class ZulipAccountsRepository implements IZulipAccountsRepository {
  constructor(
    @InjectRepository(ZulipAccounts)
    private readonly repository: Repository<ZulipAccounts>,
    private readonly dataSource: DataSource,
  ) {}

  /**
   * 创建新的Zulip账号关联（带事务支持）
   * 
   * 业务逻辑：
   * 1. 开启数据库事务确保原子性
   * 2. 检查游戏用户ID是否已存在关联
   * 3. 检查Zulip用户ID是否已被使用
   * 4. 检查Zulip邮箱是否已被使用
   * 5. 创建新的关联记录并保存
   * 6. 提交事务或回滚
   * 
   * @param createDto 创建数据
   * @returns Promise<ZulipAccounts> 创建的关联记录
   * @throws Error 当唯一性约束冲突时
   * 
   * @example
   * ```typescript
   * const account = await repository.create({
   *   gameUserId: BigInt(12345),
   *   zulipUserId: 67890,
   *   zulipEmail: 'user@example.com',
   *   zulipFullName: '用户名',
   *   zulipApiKeyEncrypted: 'encrypted_key'
   * });
   * ```
   */
  async create(createDto: CreateZulipAccountDto): Promise<ZulipAccounts> {
    return await this.dataSource.transaction(async manager => {
      // 在事务中检查唯一性约束
      const existingByGameUser = await manager.findOne(ZulipAccounts, {
        where: { gameUserId: createDto.gameUserId }
      });
      if (existingByGameUser) {
        throw new Error(`Game user ${createDto.gameUserId} already has a Zulip account`);
      }

      const existingByZulipUser = await manager.findOne(ZulipAccounts, {
        where: { zulipUserId: createDto.zulipUserId }
      });
      if (existingByZulipUser) {
        throw new Error(`Zulip user ${createDto.zulipUserId} is already linked`);
      }

      const existingByEmail = await manager.findOne(ZulipAccounts, {
        where: { zulipEmail: createDto.zulipEmail }
      });
      if (existingByEmail) {
        throw new Error(`Zulip email ${createDto.zulipEmail} is already linked`);
      }

      // 创建实体
      const zulipAccount = manager.create(ZulipAccounts, createDto);
      return await manager.save(zulipAccount);
    });
  }

  /**
   * 根据游戏用户ID查找Zulip账号关联
   * 
   * 业务逻辑：
   * 1. 根据includeGameUser参数决定是否加载关联的游戏用户信息
   * 2. 构建查询条件，使用gameUserId作为查询键
   * 3. 执行数据库查询，返回匹配的记录或null
   * 4. 如果需要关联信息，通过relations参数加载
   * 
   * @param gameUserId 游戏用户ID，BigInt类型
   * @param includeGameUser 是否包含游戏用户信息，默认false
   * @returns Promise<ZulipAccounts | null> 关联记录或null
   * 
   * @example
   * ```typescript
   * const account = await repository.findByGameUserId(BigInt(12345), true);
   * if (account) {
   *   console.log('用户邮箱:', account.zulipEmail);
   *   console.log('游戏用户:', account.gameUser?.username);
   * }
   * ```
   */
  async findByGameUserId(gameUserId: bigint, includeGameUser: boolean = false): Promise<ZulipAccounts | null> {
    const relations = includeGameUser ? ['gameUser'] : [];
    
    return await this.repository.findOne({
      where: { gameUserId },
      relations,
    });
  }

  /**
   * 根据Zulip用户ID查找账号关联
   * 
   * 业务逻辑：
   * 1. 根据includeGameUser参数决定是否加载关联的游戏用户信息
   * 2. 构建查询条件，使用zulipUserId作为查询键
   * 3. 执行数据库查询，返回匹配的记录或null
   * 4. 如果需要关联信息，通过relations参数加载
   * 
   * @param zulipUserId Zulip用户ID，数字类型
   * @param includeGameUser 是否包含游戏用户信息，默认false
   * @returns Promise<ZulipAccounts | null> 关联记录或null
   * 
   * @example
   * ```typescript
   * const account = await repository.findByZulipUserId(67890, false);
   * if (account) {
   *   console.log('关联的游戏用户ID:', account.gameUserId.toString());
   * }
   * ```
   */
  async findByZulipUserId(zulipUserId: number, includeGameUser: boolean = false): Promise<ZulipAccounts | null> {
    const relations = includeGameUser ? ['gameUser'] : [];
    
    return await this.repository.findOne({
      where: { zulipUserId },
      relations,
    });
  }

  /**
   * 根据Zulip邮箱查找账号关联
   * 
   * @param zulipEmail Zulip邮箱
   * @param includeGameUser 是否包含游戏用户信息
   * @returns Promise<ZulipAccounts | null> 关联记录或null
   */
  async findByZulipEmail(zulipEmail: string, includeGameUser: boolean = false): Promise<ZulipAccounts | null> {
    const relations = includeGameUser ? ['gameUser'] : [];
    
    return await this.repository.findOne({
      where: { zulipEmail },
      relations,
    });
  }

  /**
   * 根据ID查找Zulip账号关联
   * 
   * @param id 关联记录ID
   * @param includeGameUser 是否包含游戏用户信息
   * @returns Promise<ZulipAccounts | null> 关联记录或null
   */
  async findById(id: bigint, includeGameUser: boolean = false): Promise<ZulipAccounts | null> {
    const relations = includeGameUser ? ['gameUser'] : [];
    
    return await this.repository.findOne({
      where: { id },
      relations,
    });
  }

  /**
   * 更新Zulip账号关联
   * 
   * @param id 关联记录ID
   * @param updateDto 更新数据
   * @returns Promise<ZulipAccounts | null> 更新后的记录或null
   */
  async update(id: bigint, updateDto: UpdateZulipAccountDto): Promise<ZulipAccounts | null> {
    const result = await this.repository.update({ id }, updateDto);
    if (result.affected === 0) {
      return null;
    }
    return await this.findById(id);
  }

  /**
   * 根据游戏用户ID更新Zulip账号关联
   * 
   * @param gameUserId 游戏用户ID
   * @param updateDto 更新数据
   * @returns Promise<ZulipAccounts | null> 更新后的记录或null
   */
  async updateByGameUserId(gameUserId: bigint, updateDto: UpdateZulipAccountDto): Promise<ZulipAccounts | null> {
    const result = await this.repository.update({ gameUserId }, updateDto);
    if (result.affected === 0) {
      return null;
    }
    return await this.findByGameUserId(gameUserId);
  }

  /**
   * 删除Zulip账号关联
   * 
   * @param id 关联记录ID
   * @returns Promise<boolean> 是否删除成功
   */
  async delete(id: bigint): Promise<boolean> {
    const result = await this.repository.delete({ id });
    return result.affected > 0;
  }

  /**
   * 根据游戏用户ID删除Zulip账号关联
   * 
   * @param gameUserId 游戏用户ID
   * @returns Promise<boolean> 是否删除成功
   */
  async deleteByGameUserId(gameUserId: bigint): Promise<boolean> {
    const result = await this.repository.delete({ gameUserId });
    return result.affected > 0;
  }

  /**
   * 查询多个Zulip账号关联
   * 
   * @param options 查询选项
   * @returns Promise<ZulipAccounts[]> 关联记录列表
   */
  async findMany(options: ZulipAccountQueryOptions = {}): Promise<ZulipAccounts[]> {
    const { includeGameUser, ...whereOptions } = options;
    const relations = includeGameUser ? ['gameUser'] : [];
    
    // 构建查询条件
    const where: FindOptionsWhere<ZulipAccounts> = {};
    if (whereOptions.gameUserId) where.gameUserId = whereOptions.gameUserId;
    if (whereOptions.zulipUserId) where.zulipUserId = whereOptions.zulipUserId;
    if (whereOptions.zulipEmail) where.zulipEmail = whereOptions.zulipEmail;
    if (whereOptions.status) where.status = whereOptions.status;

    return await this.repository.find({
      where,
      relations,
      order: { createdAt: 'DESC' },
    });
  }

  /**
   * 获取需要验证的账号列表（优化查询）
   * 
   * 业务逻辑：
   * 1. 计算验证截止时间（当前时间减去最大验证间隔）
   * 2. 查询状态为active的账号
   * 3. 筛选从未验证或验证时间超期的账号
   * 4. 按验证时间升序排序，NULL值优先
   * 5. 限制查询数量避免性能问题
   * 
   * @param maxAge 最大验证间隔（毫秒），默认24小时
   * @returns Promise<ZulipAccounts[]> 需要验证的账号列表
   * 
   * @example
   * ```typescript
   * const accounts = await repository.findAccountsNeedingVerification();
   * console.log(`需要验证的账号数量: ${accounts.length}`);
   * ```
   */
  async findAccountsNeedingVerification(maxAge: number = DEFAULT_VERIFICATION_INTERVAL): Promise<ZulipAccounts[]> {
    const cutoffTime = new Date(Date.now() - maxAge);
    
    return await this.repository
      .createQueryBuilder('za')
      .where('za.status = :status', { status: 'active' })
      .andWhere(
        '(za.last_verified_at IS NULL OR za.last_verified_at < :cutoffTime)',
        { cutoffTime }
      )
      .orderBy('za.last_verified_at', 'ASC', 'NULLS FIRST')
      .limit(VERIFICATION_QUERY_LIMIT) // 限制查询数量，避免性能问题
      .getMany();
  }

  /**
   * 获取错误状态的账号列表（可重试的）
   * 
   * 业务逻辑：
   * 1. 查询状态为error的账号
   * 2. 筛选重试次数小于最大重试次数的账号
   * 3. 按更新时间升序排序，优先处理较早的错误
   * 4. 限制查询数量避免性能问题
   * 
   * @param maxRetryCount 最大重试次数，默认3次
   * @returns Promise<ZulipAccounts[]> 错误状态的账号列表
   * 
   * @example
   * ```typescript
   * const errorAccounts = await repository.findErrorAccounts(5);
   * console.log(`可重试的错误账号: ${errorAccounts.length}`);
   * ```
   */
  async findErrorAccounts(maxRetryCount: number = DEFAULT_MAX_RETRY_COUNT): Promise<ZulipAccounts[]> {
    return await this.repository
      .createQueryBuilder('za')
      .where('za.status = :status', { status: 'error' })
      .andWhere('za.retry_count < :maxRetryCount', { maxRetryCount })
      .orderBy('za.updated_at', 'ASC')
      .limit(ERROR_ACCOUNTS_QUERY_LIMIT) // 限制查询数量
      .getMany();
  }

  /**
   * 批量更新账号状态
   * 
   * @param ids 账号ID列表
   * @param status 新状态
   * @returns Promise<number> 更新的记录数
   */
  async batchUpdateStatus(ids: bigint[], status: 'active' | 'inactive' | 'suspended' | 'error'): Promise<number> {
    const result = await this.repository
      .createQueryBuilder()
      .update(ZulipAccounts)
      .set({ status })
      .whereInIds(ids)
      .execute();
    
    return result.affected || 0;
  }

  /**
   * 统计各状态的账号数量（优化查询）
   * 
   * @returns Promise<StatusStatistics> 状态统计
   */
  async getStatusStatistics(): Promise<StatusStatistics> {
    const result = await this.repository
      .createQueryBuilder('za')
      .select('za.status', 'status')
      .addSelect('COUNT(*)', 'count')
      .groupBy('za.status')
      .getRawMany();

    const statistics: StatusStatistics = {
      active: 0,
      inactive: 0,
      suspended: 0,
      error: 0,
    };

    result.forEach(row => {
      statistics[row.status] = parseInt(row.count, 10);
    });

    return statistics;
  }

  /**
   * 检查邮箱是否已存在
   * 
   * @param zulipEmail Zulip邮箱
   * @param excludeId 排除的记录ID（用于更新时检查）
   * @returns Promise<boolean> 是否已存在
   */
  async existsByEmail(zulipEmail: string, excludeId?: bigint): Promise<boolean> {
    const queryBuilder = this.repository
      .createQueryBuilder('za')
      .where('za.zulip_email = :zulipEmail', { zulipEmail });

    if (excludeId) {
      queryBuilder.andWhere('za.id != :excludeId', { excludeId });
    }

    const count = await queryBuilder.getCount();
    return count > 0;
  }

  /**
   * 检查Zulip用户ID是否已存在
   * 
   * @param zulipUserId Zulip用户ID
   * @param excludeId 排除的记录ID（用于更新时检查）
   * @returns Promise<boolean> 是否已存在
   */
  async existsByZulipUserId(zulipUserId: number, excludeId?: bigint): Promise<boolean> {
    const queryBuilder = this.repository
      .createQueryBuilder('za')
      .where('za.zulip_user_id = :zulipUserId', { zulipUserId });

    if (excludeId) {
      queryBuilder.andWhere('za.id != :excludeId', { excludeId });
    }

    const count = await queryBuilder.getCount();
    return count > 0;
  }

  /**
   * 检查游戏用户ID是否已存在
   * 
   * @param gameUserId 游戏用户ID
   * @param excludeId 排除的记录ID（用于更新时检查）
   * @returns Promise<boolean> 是否已存在
   */
  async existsByGameUserId(gameUserId: bigint, excludeId?: bigint): Promise<boolean> {
    const queryBuilder = this.repository
      .createQueryBuilder('za')
      .where('za.game_user_id = :gameUserId', { gameUserId });

    if (excludeId) {
      queryBuilder.andWhere('za.id != :excludeId', { excludeId });
    }

    const count = await queryBuilder.getCount();
    return count > 0;
  }
}