import { redis } from '../config/redis';
import { config } from '../config';
import { logger } from '../utils/logger';
import { REDIS_KEYS } from '@gala/shared/constants';
import type { VoteCategory } from '@gala/shared/types';
import { readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

const __dirname = dirname(fileURLToPath(import.meta.url));

// Load Lua scripts
const luaScripts = {
  voteSubmit: readFileSync(join(__dirname, '../lua/vote_submit.lua'), 'utf-8'),
  getCategoryResults: readFileSync(join(__dirname, '../lua/get_category_results.lua'), 'utf-8'),
  checkUserVotes: readFileSync(join(__dirname, '../lua/check_user_votes.lua'), 'utf-8'),
};

// Script SHA cache
let scriptShas: Record<string, string> = {};

/**
 * Load Lua scripts into Redis
 */
export async function loadLuaScripts(): Promise<void> {
  try {
    const [voteSubmitSha, getCategoryResultsSha, checkUserVotesSha] = await Promise.all([
      redis.script('LOAD', luaScripts.voteSubmit),
      redis.script('LOAD', luaScripts.getCategoryResults),
      redis.script('LOAD', luaScripts.checkUserVotes),
    ]);

    scriptShas = {
      voteSubmit: voteSubmitSha as string,
      getCategoryResults: getCategoryResultsSha as string,
      checkUserVotes: checkUserVotesSha as string,
    };

    logger.info('Lua scripts loaded successfully');
  } catch (error) {
    logger.error({ error }, 'Failed to load Lua scripts');
    throw error;
  }
}

// ============================================================================
// Vote Result Types
// ============================================================================

interface VoteSubmitResult {
  success: boolean;
  error?: string;
  message?: string;
  candidate_id?: string;
  new_count?: number;
  user_total_votes?: number;
}

interface CategoryResult {
  candidate_id: string;
  vote_count: number;
}

// ============================================================================
// Vote Service
// ============================================================================

export class VoteService {
  /**
   * Submit a vote atomically
   */
  async submitVote(
    userId: string,
    category: VoteCategory,
    candidateId: string,
    localId: string
  ): Promise<VoteSubmitResult> {
    const timestamp = Date.now().toString();

    // Build Redis keys
    const keys = [
      `${REDIS_KEYS.VOTE_COUNT}:${category}`,
      `${REDIS_KEYS.USER_CATEGORIES}:${userId}:categories`,
      `${REDIS_KEYS.CATEGORY_VOTERS}:${category}:voters`,
      `${REDIS_KEYS.LEADERBOARD}:${category}`,
      REDIS_KEYS.SYNC_QUEUE,
      `${REDIS_KEYS.VOTE_LOCK}:${userId}:${category}`,
    ];

    const args = [
      candidateId,
      userId,
      category,
      timestamp,
      localId,
      config.voting.lockTtlMs.toString(),
      config.voting.maxVotesPerUser.toString(),
    ];

    try {
      const resultJson = await redis.evalsha(
        scriptShas.voteSubmit,
        keys.length,
        ...keys,
        ...args
      );

      const result = JSON.parse(resultJson as string) as VoteSubmitResult;

      if (result.success) {
        logger.info(
          { userId, category, candidateId, newCount: result.new_count },
          'Vote submitted successfully'
        );
      } else {
        logger.warn(
          { userId, category, candidateId, error: result.error },
          'Vote submission rejected'
        );
      }

      return result;
    } catch (error) {
      logger.error({ error, userId, category, candidateId }, 'Vote submission error');
      return {
        success: false,
        error: 'INTERNAL_ERROR',
        message: 'Failed to submit vote',
      };
    }
  }

  /**
   * Get real-time results for a category
   */
  async getCategoryResults(category: VoteCategory, limit = 10): Promise<CategoryResult[]> {
    const key = `${REDIS_KEYS.LEADERBOARD}:${category}`;

    try {
      const resultJson = await redis.evalsha(
        scriptShas.getCategoryResults,
        1,
        key,
        limit.toString()
      );

      return JSON.parse(resultJson as string) as CategoryResult[];
    } catch (error) {
      logger.error({ error, category }, 'Failed to get category results');
      return [];
    }
  }

  /**
   * Get all results for all categories
   */
  async getAllResults(categories: VoteCategory[]): Promise<Record<VoteCategory, CategoryResult[]>> {
    const pipeline = redis.pipeline();

    for (const category of categories) {
      const key = `${REDIS_KEYS.LEADERBOARD}:${category}`;
      pipeline.zrevrange(key, 0, -1, 'WITHSCORES');
    }

    const results = await pipeline.exec();
    const formatted: Record<string, CategoryResult[]> = {};

    categories.forEach((category, index) => {
      const [err, data] = results![index];
      if (!err && data) {
        const pairs = data as string[];
        const categoryResults: CategoryResult[] = [];
        for (let i = 0; i < pairs.length; i += 2) {
          categoryResults.push({
            candidate_id: pairs[i],
            vote_count: parseInt(pairs[i + 1], 10),
          });
        }
        formatted[category] = categoryResults;
      } else {
        formatted[category] = [];
      }
    });

    return formatted as Record<VoteCategory, CategoryResult[]>;
  }

  /**
   * Check which categories a user has voted in
   */
  async getUserVotedCategories(userId: string): Promise<VoteCategory[]> {
    const key = `${REDIS_KEYS.USER_CATEGORIES}:${userId}:categories`;

    try {
      const resultJson = await redis.evalsha(scriptShas.checkUserVotes, 1, key);
      return JSON.parse(resultJson as string) as VoteCategory[];
    } catch (error) {
      logger.error({ error, userId }, 'Failed to get user voted categories');
      return [];
    }
  }

  /**
   * Get vote count for a specific candidate
   */
  async getCandidateVoteCount(category: VoteCategory, candidateId: string): Promise<number> {
    const key = `${REDIS_KEYS.VOTE_COUNT}:${category}`;
    const count = await redis.hget(key, candidateId);
    return count ? parseInt(count, 10) : 0;
  }

  /**
   * Check if user has voted in a specific category
   */
  async hasUserVotedInCategory(userId: string, category: VoteCategory): Promise<boolean> {
    const key = `${REDIS_KEYS.CATEGORY_VOTERS}:${category}:voters`;
    const result = await redis.sismember(key, userId);
    return result === 1;
  }
}

export const voteService = new VoteService();
