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

import {
  getModrinthVersion,
  isVersionCompatible,
  pickPrimaryFile,
  resolveModrinthLoader,
} from '@hexahost/catalog';
import { prisma } from '@hexahost/database';

import { sendAgentRequest } from '../agent-bridge';
import { logger } from '../logger';

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

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

const ADDON_TIMEOUT_MS = 10 * 60 * 1000;

export async function processInstallAddonJob(job: Job): Promise<{ status: string }> {
  if (job.name !== 'install-addon') {
    return { status: 'ignored' };
  }

  const { addonId, serverId } = installAddonJobSchema.parse(job.data);
  const addon = await prisma.installedAddon.findUniqueOrThrow({
    where: { id: addonId },
  });
  const server = await prisma.gameServer.findUniqueOrThrow({
    where: { id: serverId },
  });

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

  await prisma.installedAddon.update({
    where: { id: addonId },
    data: { status: 'INSTALLING' },
  });

  try {
    const version = await getModrinthVersion(addon.versionId);
    const loader = resolveModrinthLoader(server.softwareFamily);

    if (!loader || !isVersionCompatible(version, server.minecraftVersion, loader)) {
      throw new Error('Selected version is not compatible with this server');
    }

    const file = pickPrimaryFile(version);
    if (!file) {
      throw new Error('No installable file found for this version');
    }

    const response = await sendAgentRequest(
      server.nodeId,
      'server.addon.install',
      {
        serverId,
        generation: server.version,
        downloadUrl: file.url,
        relativePath: addon.filePath,
        sha512: file.hashes['sha512'] ?? addon.fileSha512 ?? '',
      },
      ADDON_TIMEOUT_MS,
    );

    if (!response.success) {
      throw new Error(response.errorMessage ?? 'Addon install failed');
    }

    await prisma.installedAddon.update({
      where: { id: addonId },
      data: {
        status: 'INSTALLED',
        installedAt: new Date(),
        fileSha512: file.hashes['sha512'] ?? addon.fileSha512,
      },
    });

    return { status: 'installed' };
  } catch (error) {
    const message = error instanceof Error ? error.message : 'Addon install failed';
    await prisma.installedAddon.update({
      where: { id: addonId },
      data: {
        status: 'FAILED',
        failureReason: message,
      },
    });
    throw error;
  }
}

export async function processRemoveAddonJob(job: Job): Promise<{ status: string }> {
  if (job.name !== 'remove-addon') {
    return { status: 'ignored' };
  }

  const { addonId, serverId } = removeAddonJobSchema.parse(job.data);
  const addon = await prisma.installedAddon.findUniqueOrThrow({
    where: { id: addonId },
  });
  const server = await prisma.gameServer.findUniqueOrThrow({
    where: { id: serverId },
  });

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

  await prisma.installedAddon.update({
    where: { id: addonId },
    data: { status: 'REMOVING' },
  });

  try {
    const response = await sendAgentRequest(
      server.nodeId,
      'server.addon.remove',
      {
        serverId,
        generation: server.version,
        relativePath: addon.filePath,
      },
      ADDON_TIMEOUT_MS,
    );

    if (!response.success) {
      throw new Error(response.errorMessage ?? 'Addon remove failed');
    }

    await prisma.installedAddon.update({
      where: { id: addonId },
      data: {
        status: 'REMOVED',
      },
    });

    return { status: 'removed' };
  } catch (error) {
    const message = error instanceof Error ? error.message : 'Addon remove failed';
    await prisma.installedAddon.update({
      where: { id: addonId },
      data: {
        status: 'FAILED',
        failureReason: message,
      },
    });
    throw error;
  }
}

export async function processAddonJob(job: Job): Promise<{ status: string }> {
  switch (job.name) {
    case 'install-addon':
      return processInstallAddonJob(job);
    case 'remove-addon':
      return processRemoveAddonJob(job);
    default:
      logger.warn({ jobName: job.name }, 'Unknown addon job');
      return { status: 'ignored' };
  }
}
