import { redis } from '../config/redis';
import { logger } from '../utils/logger';
import {
  GENERATION_TAGS,
  ZODIAC_TAGS,
  DRAW_KEYS,
  type UserTag,
  type GenerationTag,
  type ZodiacTag,
} from '@gala/shared/constants';

// ============================================================================
// Types
// ============================================================================

interface UserWithTags {
  userId: string;
  birthDate: string;
  tags: UserTag[];
}

interface DrawResult {
  success: boolean;
  winners?: WinnerInfo[];
  error?: string;
  poolSize?: number;
  requestedCount?: number;
}

interface WinnerInfo {
  userId: string;
  drawnAt: number;
  round: number;
  poolTag: string;
}

// ============================================================================
// Tag Calculation
// ============================================================================

/**
 * Calculate user tags based on birth date
 *
 * Generation Tags:
 * - TAG_6070: 1960 <= Year <= 1979
 * - TAG_80: 1980 <= Year <= 1989
 * - TAG_90: Year >= 1990
 *
 * Zodiac Tag (Year of the Horse):
 * - (Year - 1906) % 12 === 0
 * - Matches: 1966, 1978, 1990, 2002, 2014...
 */
export function calculateUserTags(birthDate: string): UserTag[] {
  const tags: UserTag[] = [];

  // Parse birth year
  const year = parseInt(birthDate.substring(0, 4), 10);
  if (isNaN(year)) {
    logger.warn({ birthDate }, 'Invalid birth date format');
    return tags;
  }

  // Generation tag
  if (year >= 1960 && year <= 1979) {
    tags.push(GENERATION_TAGS.TAG_6070);
  } else if (year >= 1980 && year <= 1989) {
    tags.push(GENERATION_TAGS.TAG_80);
  } else if (year >= 1990) {
    tags.push(GENERATION_TAGS.TAG_90);
  }

  // Zodiac tag: Year of the Horse
  // Horse years: 1906, 1918, 1930, 1942, 1954, 1966, 1978, 1990, 2002, 2014...
  if ((year - 1906) % 12 === 0) {
    tags.push(ZODIAC_TAGS.HORSE);
  }

  return tags;
}

// ============================================================================
// DrawEngine Class
// ============================================================================

export class DrawEngine {
  /**
   * Initialize pools from user data
   * Call this when importing users or on server start
   */
  async initializePools(users: Array<{ userId: string; birthDate: string }>): Promise<void> {
    const pipeline = redis.pipeline();

    // Clear existing pools
    const poolKeys = [
      DRAW_KEYS.pool(GENERATION_TAGS.TAG_6070),
      DRAW_KEYS.pool(GENERATION_TAGS.TAG_80),
      DRAW_KEYS.pool(GENERATION_TAGS.TAG_90),
      DRAW_KEYS.pool(ZODIAC_TAGS.HORSE),
      DRAW_KEYS.allPool,
    ];
    for (const key of poolKeys) {
      pipeline.del(key);
    }

    // Add users to pools
    for (const user of users) {
      const tags = calculateUserTags(user.birthDate);

      // Add to all pool
      pipeline.sadd(DRAW_KEYS.allPool, user.userId);

      // Add to tag-specific pools
      for (const tag of tags) {
        pipeline.sadd(DRAW_KEYS.pool(tag), user.userId);
      }
    }

    await pipeline.exec();
    logger.info({ userCount: users.length }, 'Draw pools initialized');
  }

  /**
   * Draw winners from a pool
   *
   * @param poolTag - The pool to draw from (e.g., '6070', '80', '90', 'horse')
   * @param count - Number of winners to draw
   * @param round - Current round number (for tracking)
   * @param excludePreviousWinners - Whether to exclude users who already won
   */
  async drawWinner(
    poolTag: string,
    count: number,
    round: number,
    excludePreviousWinners = true
  ): Promise<DrawResult> {
    const poolKey = DRAW_KEYS.pool(poolTag);

    // Step 1: Get eligible candidates (pool - winners - excluded)
    let eligibleKey = poolKey;

    if (excludePreviousWinners) {
      // Create temp key with eligible users
      eligibleKey = `draw:temp:eligible:${Date.now()}`;

      await redis.sdiffstore(
        eligibleKey,
        poolKey,
        DRAW_KEYS.winners,
        DRAW_KEYS.excluded
      );
    }

    // Step 2: Check pool size
    const poolSize = await redis.scard(eligibleKey);

    if (poolSize === 0) {
      if (eligibleKey !== poolKey) await redis.del(eligibleKey);
      return {
        success: false,
        error: 'EMPTY_POOL',
        poolSize: 0,
        requestedCount: count,
      };
    }

    const actualCount = Math.min(count, poolSize);

    // Step 3: Random draw using SRANDMEMBER
    const winners = await redis.srandmember(eligibleKey, actualCount);

    if (eligibleKey !== poolKey) await redis.del(eligibleKey);

    if (!winners || winners.length === 0) {
      return { success: false, error: 'DRAW_FAILED' };
    }

    // Step 4: Record winners
    const winnerList = Array.isArray(winners) ? winners : [winners];
    const timestamp = Date.now();

    const pipeline = redis.pipeline();
    const winnerInfos: WinnerInfo[] = [];

    for (const odrawId of winnerList) {
      // Add to global winners set
      pipeline.sadd(DRAW_KEYS.winners, odrawId);

      // Add to round-specific winners
      pipeline.sadd(DRAW_KEYS.roundWinners(round), odrawId);

      // Record in history
      const record = JSON.stringify({
        odrawId,
        round,
        poolTag,
        drawnAt: timestamp,
      });
      pipeline.rpush(DRAW_KEYS.history, record);

      winnerInfos.push({
        userId: odrawId,
        drawnAt: timestamp,
        round,
        poolTag,
      });
    }

    await pipeline.exec();

    logger.info({
      poolTag,
      round,
      requestedCount: count,
      actualCount: winnerList.length,
      winners: winnerList,
    }, 'Draw completed');

    return {
      success: true,
      winners: winnerInfos,
      poolSize,
      requestedCount: count,
    };
  }
}

export const drawEngine = new DrawEngine();
