import { redis } from '../config/redis';
import { logger } from '../utils/logger';
import { VOTE_KEYS, TICKET_TYPES, VOTE_CONFIG, type TicketType } from '@gala/shared/constants';
import { readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

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

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

interface CastVoteResult {
  success: boolean;
  error?: string;
  message?: string;
  program_id?: string;
  ticket_type?: string;
  new_count?: number;
  revoked?: boolean;
  revoked_program?: string;
  is_duplicate?: boolean;
}

interface RevokeVoteResult {
  success: boolean;
  error?: string;
  message?: string;
  revoked_program?: string;
  ticket_type?: string;
}

interface UserVoteStatus {
  userId: string;
  tickets: Record<TicketType, string | null>;
  usedTickets: number;
  remainingTickets: number;
}

interface ProgramRanking {
  programId: string;
  voteCount: number;
  rank: number;
}

// ============================================================================
// Lua Script Management
// ============================================================================

const luaScripts = {
  castVote: readFileSync(join(__dirname, '../lua/cast_vote.lua'), 'utf-8'),
  revokeVote: readFileSync(join(__dirname, '../lua/revoke_vote.lua'), 'utf-8'),
};

let scriptShas: Record<string, string> = {};

export async function loadVotingScripts(): Promise<void> {
  try {
    const [castVoteSha, revokeVoteSha] = await Promise.all([
      redis.script('LOAD', luaScripts.castVote),
      redis.script('LOAD', luaScripts.revokeVote),
    ]);

    scriptShas = {
      castVote: castVoteSha as string,
      revokeVote: revokeVoteSha as string,
    };

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

// ============================================================================
// VotingEngine Class
// ============================================================================

export class VotingEngine {
  /**
   * Cast a vote: assign a ticket to a program
   *
   * Rules enforced atomically:
   * - Each ticket can only go to ONE program
   * - Each program can only receive ONE ticket from each user
   * - If ticket was used before, old vote is revoked first
   */
  async castVote(
    userId: string,
    programId: string,
    ticketType: TicketType
  ): Promise<CastVoteResult> {
    // Validate ticket type
    if (!TICKET_TYPES.includes(ticketType)) {
      return {
        success: false,
        error: 'INVALID_TICKET_TYPE',
        message: `Invalid ticket type: ${ticketType}`,
      };
    }

    const timestamp = Date.now().toString();

    const keys = [
      VOTE_KEYS.userTickets(userId),
      VOTE_KEYS.userPrograms(userId),
      VOTE_KEYS.count(programId, ticketType),
      VOTE_KEYS.leaderboard(ticketType),
      VOTE_KEYS.programVoters(programId),
      VOTE_KEYS.syncQueue,
      VOTE_KEYS.lock(userId),
    ];

    const args = [
      userId,
      programId,
      ticketType,
      timestamp,
      VOTE_CONFIG.LOCK_TTL_MS.toString(),
    ];

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

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

      if (result.success) {
        logger.info({
          userId,
          programId,
          ticketType,
          revoked: result.revoked,
        }, 'Vote cast successfully');
      }

      return result;
    } catch (error) {
      logger.error({ error, userId, programId, ticketType }, 'Cast vote error');
      return {
        success: false,
        error: 'INTERNAL_ERROR',
        message: 'Failed to cast vote',
      };
    }
  }

  /**
   * Revoke a vote: remove ticket assignment
   */
  async revokeVote(userId: string, ticketType: TicketType): Promise<RevokeVoteResult> {
    if (!TICKET_TYPES.includes(ticketType)) {
      return { success: false, error: 'INVALID_TICKET_TYPE' };
    }

    const timestamp = Date.now().toString();
    const keys = [
      VOTE_KEYS.userTickets(userId),
      VOTE_KEYS.userPrograms(userId),
      VOTE_KEYS.syncQueue,
      VOTE_KEYS.lock(userId),
    ];
    const args = [userId, ticketType, timestamp, VOTE_CONFIG.LOCK_TTL_MS.toString()];

    try {
      const resultJson = await redis.evalsha(
        scriptShas.revokeVote,
        keys.length,
        ...keys,
        ...args
      );
      return JSON.parse(resultJson as string) as RevokeVoteResult;
    } catch (error) {
      logger.error({ error, userId, ticketType }, 'Revoke vote error');
      return { success: false, error: 'INTERNAL_ERROR' };
    }
  }

  /**
   * Get user's current vote status
   */
  async getUserVoteStatus(userId: string): Promise<UserVoteStatus> {
    const ticketsData = await redis.hgetall(VOTE_KEYS.userTickets(userId));

    const tickets: Record<TicketType, string | null> = {} as any;
    let usedTickets = 0;

    for (const t of TICKET_TYPES) {
      tickets[t] = ticketsData[t] || null;
      if (ticketsData[t]) usedTickets++;
    }

    return {
      userId,
      tickets,
      usedTickets,
      remainingTickets: VOTE_CONFIG.MAX_TICKETS - usedTickets,
    };
  }

  /**
   * Get leaderboard for a ticket type
   */
  async getLeaderboard(ticketType: TicketType, limit = 10): Promise<ProgramRanking[]> {
    const key = VOTE_KEYS.leaderboard(ticketType);
    const data = await redis.zrevrange(key, 0, limit - 1, 'WITHSCORES');

    const rankings: ProgramRanking[] = [];
    for (let i = 0; i < data.length; i += 2) {
      rankings.push({
        programId: data[i],
        voteCount: parseInt(data[i + 1], 10),
        rank: Math.floor(i / 2) + 1,
      });
    }
    return rankings;
  }
}

export const votingEngine = new VotingEngine();
