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

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

const ACTIVE_SERVER_STATUSES = [
  'RUNNING',
  'STARTING',
  'STOPPING',
] as const;

export async function processNodeHealthTick(): Promise<void> {
  const cutoff = new Date(Date.now() - HEARTBEAT_STALE_MS);

  const staleNodes = await prisma.gameNode.findMany({
    where: {
      status: { in: ['ONLINE', 'DRAINING', 'MAINTENANCE'] },
      OR: [{ lastHeartbeatAt: null }, { lastHeartbeatAt: { lt: cutoff } }],
    },
    select: { id: true, name: true },
  });

  for (const node of staleNodes) {
    await markNodeUnreachable(node.id);
    logger.warn({ nodeId: node.id, name: node.name }, 'Node marked unreachable');
  }

  const drainingNodes = await prisma.gameNode.findMany({
    where: { status: 'DRAINING' },
    include: {
      allocations: {
        where: { status: 'ACTIVE' },
        select: { id: true },
      },
    },
  });

  for (const node of drainingNodes) {
    if (node.allocations.length === 0 && node.drainRequestedAt) {
      await prisma.gameNode.update({
        where: { id: node.id },
        data: {
          status: 'MAINTENANCE',
          drainRequestedAt: null,
        },
      });

      logger.info({ nodeId: node.id }, 'Drain complete; node set to maintenance');
    }
  }
}

async function markNodeUnreachable(nodeId: string): Promise<void> {
  await prisma.$transaction(async (tx) => {
    const node = await tx.gameNode.findUnique({ where: { id: nodeId } });
    if (!node || node.status === 'UNREACHABLE') {
      return;
    }

    await tx.gameNode.update({
      where: { id: nodeId },
      data: { status: 'UNREACHABLE' },
    });

    const affectedServers = await tx.gameServer.findMany({
      where: {
        nodeId,
        status: { in: [...ACTIVE_SERVER_STATUSES] },
      },
      select: { id: true, status: true },
    });

    for (const server of affectedServers) {
      await tx.gameServer.update({
        where: { id: server.id },
        data: { status: 'UNKNOWN', version: { increment: 1 } },
      });

      await tx.gameServerStateTransition.create({
        data: {
          gameServerId: server.id,
          fromStatus: server.status,
          toStatus: 'UNKNOWN',
          reason: 'Node heartbeat lost',
          errorCode: 'NODE_UNREACHABLE',
        },
      });
    }
  });
}

export async function reconcileNodeOnline(nodeId: string): Promise<void> {
  const node = await prisma.gameNode.findUnique({ where: { id: nodeId } });
  if (!node || node.status !== 'UNREACHABLE') {
    return;
  }

  await prisma.gameNode.update({
    where: { id: nodeId },
    data: { status: 'ONLINE' },
  });

  logger.info({ nodeId }, 'Node reconciled back to online');
}
