import { randomUUID } from 'node:crypto';

import { Job } from 'bullmq';
import { z } from 'zod';

import { prisma } from '@hexahost/database';

import { logger } from '../logger';
import { publishNodeCommand } from '../redis';
import { transitionServerStatus } from '../server-state';

const lifecycleJobSchema = z.object({
  serverId: z.string().uuid(),
});

async function loadServerForLifecycle(serverId: string) {
  const server = await prisma.gameServer.findUnique({
    where: { id: serverId },
  });

  if (!server) {
    throw new Error(`Server not found: ${serverId}`);
  }

  if (!server.nodeId) {
    throw new Error(`Server ${serverId} is not assigned to a node`);
  }

  return server;
}

async function publishLifecycleCommand(
  serverId: string,
  nodeId: string,
  type: 'server.start' | 'server.stop',
  generation: number,
): Promise<string> {
  const messageId = randomUUID();

  await publishNodeCommand({
    nodeId,
    envelope: {
      protocolVersion: 1,
      messageId,
      type,
      timestamp: new Date().toISOString(),
      payload: {
        serverId,
        generation,
        desiredGeneration: generation,
      },
    },
  });

  return messageId;
}

export async function processStartServerJob(job: Job): Promise<{ status: string }> {
  if (job.name !== 'start-server') {
    logger.warn({ jobName: job.name }, 'Unknown lifecycle job');
    return { status: 'ignored' };
  }

  const { serverId } = lifecycleJobSchema.parse(job.data);
  const server = await loadServerForLifecycle(serverId);

  if (server.status !== 'STOPPED') {
    throw new Error(`Cannot start server ${serverId} from status ${server.status}`);
  }

  const messageId = await publishLifecycleCommand(
    serverId,
    server.nodeId!,
    'server.start',
    server.version,
  );

  await transitionServerStatus(serverId, 'STARTING', {
    expectedFrom: 'STOPPED',
    reason: 'Start requested',
    correlationId: messageId,
  });

  logger.info({ serverId, messageId }, 'Server start command published');
  return { status: 'starting' };
}

export async function processStopServerJob(job: Job): Promise<{ status: string }> {
  if (job.name !== 'stop-server') {
    logger.warn({ jobName: job.name }, 'Unknown lifecycle job');
    return { status: 'ignored' };
  }

  const { serverId } = lifecycleJobSchema.parse(job.data);
  const server = await loadServerForLifecycle(serverId);

  if (server.status !== 'RUNNING') {
    throw new Error(`Cannot stop server ${serverId} from status ${server.status}`);
  }

  const messageId = await publishLifecycleCommand(
    serverId,
    server.nodeId!,
    'server.stop',
    server.version,
  );

  await transitionServerStatus(serverId, 'STOPPING', {
    expectedFrom: 'RUNNING',
    reason: 'Stop requested',
    correlationId: messageId,
  });

  logger.info({ serverId, messageId }, 'Server stop command published');
  return { status: 'stopping' };
}

export async function processLifecycleJob(job: Job): Promise<{ status: string }> {
  switch (job.name) {
    case 'start-server':
      return processStartServerJob(job);
    case 'stop-server':
      return processStopServerJob(job);
    default:
      logger.warn({ jobName: job.name }, 'Unknown lifecycle job name');
      return { status: 'ignored' };
  }
}
