import { Server as HttpServer } from 'http';
import { Server, Socket } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { redis } from '../config/redis';
import { config } from '../config';
import { logger } from '../utils/logger';
import { voteService } from '../services/vote.service';
import { votingEngine } from '../services/voting.engine';
import { adminService } from '../services/admin.service';
import { SOCKET_EVENTS, SOCKET_ROOMS, TICKET_TYPES, type TicketType } from '@gala/shared/constants';
import type {
  ServerToClientEvents,
  ClientToServerEvents,
  InterServerEvents,
  SocketData,
  VoteSubmitPayload,
  JoinPayload,
  AckCallback,
  VoteCategory,
  ConnectionAckPayload,
  PhaseChangePayload,
  VotingControlPayload,
  LotteryControlPayload,
  EmergencyResetPayload,
  MusicControlPayload,
  AdminState,
} from '@gala/shared/types';

export type GalaSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
export type GalaServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;

let io: GalaServer;

/**
 * Initialize Socket.io server
 */
export async function initializeSocket(httpServer: HttpServer): Promise<GalaServer> {
  io = new Server(httpServer, {
    cors: {
      origin: config.corsOrigins,
      credentials: true,
    },
    pingTimeout: config.socket.pingTimeout,
    pingInterval: config.socket.pingInterval,
    maxHttpBufferSize: config.socket.maxHttpBufferSize,
  });

  // Set up Redis adapter for horizontal scaling
  const pubClient = redis.duplicate();
  const subClient = redis.duplicate();

  await Promise.all([pubClient.connect(), subClient.connect()]);

  io.adapter(createAdapter(pubClient, subClient));

  // Initialize admin service
  await adminService.initialize();

  // Connection handler
  io.on('connection', handleConnection);

  logger.info('Socket.io server initialized');

  return io;
}

/**
 * Handle new socket connection
 */
function handleConnection(socket: GalaSocket): void {
  logger.info({ socketId: socket.id }, 'New socket connection');

  // Join event
  socket.on(SOCKET_EVENTS.CONNECTION_JOIN, (data: JoinPayload, callback: AckCallback<ConnectionAckPayload>) => {
    handleJoin(socket, data, callback);
  });

  // Vote submit event
  socket.on(SOCKET_EVENTS.VOTE_SUBMIT, (data: VoteSubmitPayload, callback: AckCallback<{ newCount: number }>) => {
    handleVoteSubmit(socket, data, callback);
  });

  // Ping event (custom heartbeat)
  socket.on(SOCKET_EVENTS.CONNECTION_PING, () => {
    socket.emit(SOCKET_EVENTS.CONNECTION_PONG as any);
  });

  // Sync request
  socket.on(SOCKET_EVENTS.SYNC_REQUEST, () => {
    handleSyncRequest(socket);
  });

  // Admin events
  socket.on(SOCKET_EVENTS.ADMIN_PHASE_CHANGE as any, (data: PhaseChangePayload, callback: AckCallback<void>) => {
    handlePhaseChange(socket, data, callback);
  });

  socket.on(SOCKET_EVENTS.ADMIN_VOTING_CONTROL as any, (data: VotingControlPayload, callback: AckCallback<void>) => {
    handleVotingControl(socket, data, callback);
  });

  socket.on(SOCKET_EVENTS.ADMIN_LOTTERY_CONTROL as any, (data: LotteryControlPayload, callback: AckCallback<any>) => {
    handleLotteryControl(socket, data, callback);
  });

  socket.on(SOCKET_EVENTS.ADMIN_MUSIC_CONTROL as any, (data: MusicControlPayload, callback: AckCallback<void>) => {
    handleMusicControl(socket, data, callback);
  });

  socket.on(SOCKET_EVENTS.ADMIN_EMERGENCY_RESET as any, (data: EmergencyResetPayload, callback: AckCallback<void>) => {
    handleEmergencyReset(socket, data, callback);
  });

  socket.on(SOCKET_EVENTS.ADMIN_STATE_SYNC as any, () => {
    handleAdminStateSync(socket);
  });

  // Program voting control events
  socket.on('admin:start_program' as any, (data: { programId: string }, callback: AckCallback<void>) => {
    handleStartProgram(socket, data, callback);
  });

  socket.on('admin:next_program' as any, (data: any, callback: AckCallback<void>) => {
    handleNextProgram(socket, callback);
  });

  socket.on('admin:toggle_late_catch' as any, (data: { enabled: boolean }, callback: AckCallback<void>) => {
    handleToggleLateCatch(socket, data, callback);
  });

  // Disconnect handler
  socket.on('disconnect', (reason) => {
    handleDisconnect(socket, reason);
  });

  // Error handler
  socket.on('error', (error) => {
    logger.error({ socketId: socket.id, error }, 'Socket error');
  });
}

/**
 * Handle user join
 */
async function handleJoin(
  socket: GalaSocket,
  data: JoinPayload,
  callback: AckCallback<ConnectionAckPayload>
): Promise<void> {
  try {
    const { userId, userName, role, department } = data;

    // Store user data in socket
    socket.data.userId = userId;
    socket.data.userName = userName;
    socket.data.department = department || '未知部门';
    socket.data.role = role;
    socket.data.connectedAt = new Date();
    socket.data.sessionId = socket.id;

    // Join appropriate rooms
    await socket.join(SOCKET_ROOMS.ALL);

    if (role === 'user') {
      await socket.join(SOCKET_ROOMS.MOBILE_USERS);
    } else if (role === 'screen') {
      await socket.join(SOCKET_ROOMS.SCREEN_DISPLAY);
    } else if (role === 'admin') {
      await socket.join(SOCKET_ROOMS.ADMIN);
    }

    // Get user's voted categories
    const votedCategories = await voteService.getUserVotedCategories(userId);

    logger.info({ socketId: socket.id, userId, userName, role }, 'User joined');

    // Broadcast user count update
    const userCount = await getUserCount();
    io.to(SOCKET_ROOMS.ALL).emit(SOCKET_EVENTS.CONNECTION_USERS_COUNT as any, userCount);

    callback({
      success: true,
      data: {
        sessionId: socket.id,
        serverTime: Date.now(),
        reconnected: false,
      },
    });
  } catch (error) {
    logger.error({ socketId: socket.id, error }, 'Join error');
    callback({
      success: false,
      error: 'INTERNAL_ERROR',
      message: 'Failed to join',
    });
  }
}

/**
 * Handle vote submission via WebSocket
 * Supports both new TicketType system (7-ticket) and legacy VoteCategory system
 */
async function handleVoteSubmit(
  socket: GalaSocket,
  data: VoteSubmitPayload,
  callback: AckCallback<{ newCount: number }>
): Promise<void> {
  const userId = socket.data.userId;

  if (!userId) {
    callback({
      success: false,
      error: 'UNAUTHORIZED',
      message: 'Not authenticated',
    });
    return;
  }

  // Check if voting is open
  const votingStatus = adminService.getVotingStatus();
  if (!votingStatus.isOpen) {
    callback({
      success: false,
      error: 'VOTING_CLOSED',
      message: votingStatus.reason,
    });
    return;
  }

  try {
    // Check if this is a TicketType (new 7-ticket system) or VoteCategory (legacy)
    const category = data.category as string;
    const isTicketType = TICKET_TYPES.includes(category as TicketType);

    if (isTicketType) {
      // Use new voting engine for 7-ticket system
      const result = await votingEngine.castVote(
        userId,
        data.candidateId,
        category as TicketType
      );

      if (!result.success) {
        callback({
          success: false,
          error: result.error as any,
          message: result.message,
        });
        return;
      }

      // Add a vote stamp for the big screen display
      const stampResult = await adminService.addVoteStamp(
        data.candidateId,
        socket.data.userName || '匿名用户',
        socket.data.department || '未知部门',
        category
      );

      // Broadcast vote update to all clients with stamp info
      io.to(SOCKET_ROOMS.ALL).emit(SOCKET_EVENTS.VOTE_UPDATED as any, {
        candidateId: data.candidateId,
        category: category,
        totalVotes: result.new_count!,
        delta: 1,
        stamp: stampResult.stamp, // Pass the stamp info for animation
      });

      callback({
        success: true,
        data: {
          newCount: result.new_count!,
        },
      });
    } else {
      // Use legacy vote service for VoteCategory system
      const result = await voteService.submitVote(
        userId,
        data.category as VoteCategory,
        data.candidateId,
        data.localId
      );

      if (!result.success) {
        callback({
          success: false,
          error: result.error as any,
          message: result.message,
        });
        return;
      }

      // Add a vote stamp for the big screen display (legacy category system)
      const stampResult = await adminService.addVoteStamp(
        data.candidateId,
        socket.data.userName || '匿名用户',
        socket.data.department || '未知部门',
        data.category as string
      );

      // Broadcast vote update to all clients with stamp info
      io.to(SOCKET_ROOMS.ALL).emit(SOCKET_EVENTS.VOTE_UPDATED as any, {
        candidateId: data.candidateId,
        category: data.category,
        totalVotes: result.new_count!,
        delta: 1,
        stamp: stampResult.stamp, // Include stamp for big screen
      });

      callback({
        success: true,
        data: {
          newCount: result.new_count!,
        },
      });
    }
  } catch (error) {
    logger.error({ socketId: socket.id, userId, error }, 'Vote submit error');
    callback({
      success: false,
      error: 'INTERNAL_ERROR',
      message: 'Failed to submit vote',
    });
  }
}

/**
 * Handle sync request
 */
async function handleSyncRequest(socket: GalaSocket): Promise<void> {
  const userId = socket.data.userId;

  if (!userId) {
    return;
  }

  try {
    const votedCategories = await voteService.getUserVotedCategories(userId);

    socket.emit(SOCKET_EVENTS.SYNC_STATE as any, {
      votes: {}, // TODO: Include current vote counts
      userVotedCategories: votedCategories,
    });
  } catch (error) {
    logger.error({ socketId: socket.id, userId, error }, 'Sync request error');
  }
}

/**
 * Handle disconnect
 */
function handleDisconnect(socket: GalaSocket, reason: string): void {
  logger.info(
    {
      socketId: socket.id,
      userId: socket.data.userId,
      reason,
    },
    'Socket disconnected'
  );

  // Broadcast updated user count
  getUserCount().then((count) => {
    io.to(SOCKET_ROOMS.ALL).emit(SOCKET_EVENTS.CONNECTION_USERS_COUNT as any, count);
  });
}

/**
 * Get current connected user count
 */
async function getUserCount(): Promise<number> {
  const sockets = await io.in(SOCKET_ROOMS.MOBILE_USERS).fetchSockets();
  return sockets.length;
}

/**
 * Get Socket.io server instance
 */
export function getIO(): GalaServer {
  if (!io) {
    throw new Error('Socket.io not initialized');
  }
  return io;
}

// ============================================================================
// Admin Event Handlers
// ============================================================================

/**
 * Handle system phase change
 */
async function handlePhaseChange(
  socket: GalaSocket,
  data: PhaseChangePayload,
  callback: AckCallback<void>
): Promise<void> {
  if (socket.data.role !== 'admin') {
    callback({ success: false, error: 'UNAUTHORIZED', message: 'Admin access required' });
    return;
  }

  const result = await adminService.setPhase(data.phase);
  if (result.success) {
    // Broadcast state change to all clients
    io.to(SOCKET_ROOMS.ALL).emit(SOCKET_EVENTS.ADMIN_STATE_SYNC as any, adminService.getState());
    callback({ success: true });
  } else {
    callback({ success: false, message: result.message });
  }
}

/**
 * Handle voting control
 */
async function handleVotingControl(
  socket: GalaSocket,
  data: VotingControlPayload,
  callback: AckCallback<void>
): Promise<void> {
  if (socket.data.role !== 'admin') {
    callback({ success: false, error: 'UNAUTHORIZED', message: 'Admin access required' });
    return;
  }

  const result = await adminService.controlVoting(data.action);
  if (result.success) {
    // Broadcast state change to all clients
    io.to(SOCKET_ROOMS.ALL).emit(SOCKET_EVENTS.ADMIN_STATE_SYNC as any, adminService.getState());
    callback({ success: true });
  } else {
    callback({ success: false, message: result.message });
  }
}

/**
 * Handle lottery control
 */
async function handleLotteryControl(
  socket: GalaSocket,
  data: LotteryControlPayload,
  callback: AckCallback<any>
): Promise<void> {
  if (socket.data.role !== 'admin') {
    callback({ success: false, error: 'UNAUTHORIZED', message: 'Admin access required' });
    return;
  }

  const result = await adminService.controlLottery(data.action, data.round);
  if (result.success) {
    // Broadcast state change to all clients
    io.to(SOCKET_ROOMS.ALL).emit(SOCKET_EVENTS.ADMIN_STATE_SYNC as any, adminService.getState());
    callback({ success: true, data: result.data });
  } else {
    callback({ success: false, message: result.message });
  }
}

/**
 * Handle music control
 */
async function handleMusicControl(
  socket: GalaSocket,
  data: MusicControlPayload,
  callback: AckCallback<void>
): Promise<void> {
  if (socket.data.role !== 'admin') {
    callback({ success: false, error: 'UNAUTHORIZED', message: 'Admin access required' });
    return;
  }

  const result = await adminService.controlMusic(data.action, data.track);
  if (result.success) {
    // Broadcast state change to all clients
    io.to(SOCKET_ROOMS.ALL).emit(SOCKET_EVENTS.ADMIN_STATE_SYNC as any, adminService.getState());
    callback({ success: true });
  } else {
    callback({ success: false, message: result.message });
  }
}

/**
 * Handle emergency reset
 */
async function handleEmergencyReset(
  socket: GalaSocket,
  data: EmergencyResetPayload,
  callback: AckCallback<void>
): Promise<void> {
  if (socket.data.role !== 'admin') {
    callback({ success: false, error: 'UNAUTHORIZED', message: 'Admin access required' });
    return;
  }

  const result = await adminService.emergencyReset(data.scope, data.confirmCode);
  if (result.success) {
    // Broadcast state change to all clients
    io.to(SOCKET_ROOMS.ALL).emit(SOCKET_EVENTS.ADMIN_STATE_SYNC as any, adminService.getState());
    callback({ success: true });
  } else {
    callback({ success: false, message: result.message });
  }
}

/**
 * Handle admin state sync request
 */
function handleAdminStateSync(socket: GalaSocket): void {
  socket.emit(SOCKET_EVENTS.ADMIN_STATE_SYNC as any, adminService.getState());
}

// ============================================================================
// Program Voting Control Handlers
// ============================================================================

/**
 * Handle start program voting
 */
async function handleStartProgram(
  socket: GalaSocket,
  data: { programId: string },
  callback: AckCallback<void>
): Promise<void> {
  if (socket.data.role !== 'admin') {
    callback({ success: false, error: 'UNAUTHORIZED', message: 'Admin access required' });
    return;
  }

  const result = await adminService.startProgramVoting(data.programId);
  if (result.success) {
    // Broadcast state change to all clients
    io.to(SOCKET_ROOMS.ALL).emit(SOCKET_EVENTS.ADMIN_STATE_SYNC as any, adminService.getState());
    callback({ success: true });
  } else {
    callback({ success: false, message: result.message });
  }
}

/**
 * Handle next program
 */
async function handleNextProgram(
  socket: GalaSocket,
  callback: AckCallback<void>
): Promise<void> {
  if (socket.data.role !== 'admin') {
    callback({ success: false, error: 'UNAUTHORIZED', message: 'Admin access required' });
    return;
  }

  const result = await adminService.nextProgram();
  if (result.success) {
    // Broadcast state change to all clients
    io.to(SOCKET_ROOMS.ALL).emit(SOCKET_EVENTS.ADMIN_STATE_SYNC as any, adminService.getState());
    callback({ success: true });
  } else {
    callback({ success: false, message: result.message });
  }
}

/**
 * Handle toggle late catch voting
 */
async function handleToggleLateCatch(
  socket: GalaSocket,
  data: { enabled: boolean },
  callback: AckCallback<void>
): Promise<void> {
  if (socket.data.role !== 'admin') {
    callback({ success: false, error: 'UNAUTHORIZED', message: 'Admin access required' });
    return;
  }

  const result = await adminService.toggleLateCatch(data.enabled);
  if (result.success) {
    // Broadcast state change to all clients
    io.to(SOCKET_ROOMS.ALL).emit(SOCKET_EVENTS.ADMIN_STATE_SYNC as any, adminService.getState());
    callback({ success: true });
  } else {
    callback({ success: false, message: result.message });
  }
}

