import { prisma } from '@hexahost/database';
import type { GameNode, GameServer } from '@hexahost/database';

import { createDnsProvider, resolveDnsProviderName } from './providers';
import type { DnsRecordSpec } from './providers';
import { buildJoinSlug } from './slug';

export function getPlayDomain(): string {
  return process.env['GAME_BASE_DOMAIN'] ?? 'play.example.net';
}

export function getEdgePublicHost(): string {
  return process.env['EDGE_PUBLIC_HOST'] ?? '127.0.0.1';
}

export function getEdgeListenPort(): number {
  const raw = process.env['EDGE_LISTEN_PORT'] ?? '25565';
  const port = Number.parseInt(raw, 10);
  return Number.isFinite(port) ? port : 25565;
}

function joinHostname(slug: string, playDomain = getPlayDomain()): string {
  return `${slug}.${playDomain}`;
}

function initialDnsStatus(): 'PENDING' | 'ACTIVE' {
  return resolveDnsProviderName() === 'rfc2136' ? 'PENDING' : 'ACTIVE';
}

async function upsertDnsRecord(input: {
  serverId: string;
  fqdn: string;
  recordType: string;
  target: string;
  port?: number;
  srvPriority?: number;
  srvWeight?: number;
}): Promise<void> {
  const status = initialDnsStatus();

  await prisma.serverDnsRecord.upsert({
    where: {
      serverId_recordType_fqdn: {
        serverId: input.serverId,
        recordType: input.recordType,
        fqdn: input.fqdn,
      },
    },
    create: {
      serverId: input.serverId,
      fqdn: input.fqdn,
      recordType: input.recordType,
      target: input.target,
      port: input.port,
      srvPriority: input.srvPriority,
      srvWeight: input.srvWeight,
      status,
      lastSyncedAt: status === 'ACTIVE' ? new Date() : null,
    },
    update: {
      target: input.target,
      port: input.port,
      srvPriority: input.srvPriority,
      srvWeight: input.srvWeight,
      status,
      lastSyncedAt: status === 'ACTIVE' ? new Date() : null,
      lastError: null,
    },
  });
}

function toDnsRecordSpec(record: {
  serverId: string;
  fqdn: string;
  recordType: string;
  target: string;
  port: number | null;
  srvPriority: number | null;
  srvWeight: number | null;
}): DnsRecordSpec {
  return {
    serverId: record.serverId,
    fqdn: record.fqdn,
    recordType: record.recordType as 'A' | 'SRV',
    target: record.target,
    port: record.port ?? undefined,
    srvPriority: record.srvPriority ?? undefined,
    srvWeight: record.srvWeight ?? undefined,
  };
}

export async function pushPendingDnsRecords(limit = 50): Promise<number> {
  const provider = createDnsProvider();
  const pending = await prisma.serverDnsRecord.findMany({
    where: { status: 'PENDING' },
    take: limit,
    orderBy: { updatedAt: 'asc' },
  });

  let synced = 0;

  for (const record of pending) {
    try {
      await provider.syncRecord(toDnsRecordSpec(record));
      await prisma.serverDnsRecord.update({
        where: { id: record.id },
        data: {
          status: 'ACTIVE',
          lastSyncedAt: new Date(),
          lastError: null,
        },
      });
      synced += 1;
    } catch (error) {
      await prisma.serverDnsRecord.update({
        where: { id: record.id },
        data: {
          status: 'FAILED',
          lastError: error instanceof Error ? error.message : 'DNS sync failed',
        },
      });
    }
  }

  return synced;
}

export async function syncServerDnsRecords(
  server: GameServer & { node: GameNode | null },
  playDomain = getPlayDomain(),
): Promise<void> {
  if (!server.joinSlug) {
    return;
  }

  const fqdn = joinHostname(server.joinSlug, playDomain);
  const edgeTarget = getEdgePublicHost();
  const edgePort = getEdgeListenPort();

  await upsertDnsRecord({
    serverId: server.id,
    fqdn,
    recordType: 'A',
    target: edgeTarget,
  });

  if (server.edition === 'JAVA') {
    await upsertDnsRecord({
      serverId: server.id,
      fqdn: `_minecraft._tcp.${fqdn}`,
      recordType: 'SRV',
      target: fqdn,
      port: edgePort,
      srvPriority: 0,
      srvWeight: 5,
    });
  }

  if (resolveDnsProviderName() === 'local') {
    return;
  }

  await pushPendingDnsRecords(10);
}

async function ensureUniqueSlug(serverId: string, serverName: string): Promise<string> {
  let slug = buildJoinSlug(serverName, serverId);
  let attempt = 0;

  while (
    await prisma.gameServer.findFirst({
      where: { joinSlug: slug, NOT: { id: serverId } },
      select: { id: true },
    })
  ) {
    attempt += 1;
    slug = `${buildJoinSlug(serverName, serverId)}-${attempt}`;
  }

  return slug;
}

export async function assignJoinAddress(
  serverId: string,
  options?: { playDomain?: string },
): Promise<{ joinSlug: string; joinHostname: string } | null> {
  const playDomain = options?.playDomain ?? getPlayDomain();

  const server = await prisma.gameServer.findUnique({
    where: { id: serverId },
    include: { node: true },
  });

  if (!server || !server.nodeId || server.hostPort === null) {
    return null;
  }

  const joinSlug = server.joinSlug ?? (await ensureUniqueSlug(serverId, server.name));

  const updated =
    server.joinSlug === joinSlug
      ? server
      : await prisma.gameServer.update({
          where: { id: serverId },
          data: { joinSlug },
          include: { node: true },
        });

  await syncServerDnsRecords(updated, playDomain);

  return {
    joinSlug,
    joinHostname: joinHostname(joinSlug, playDomain),
  };
}

export function formatJoinAddress(
  joinSlug: string | null,
  edition: GameServer['edition'],
  playDomain = getPlayDomain(),
): string | null {
  if (!joinSlug) {
    return null;
  }

  const hostname = joinHostname(joinSlug, playDomain);
  return edition === 'JAVA' ? hostname : `${hostname}:${getEdgeListenPort()}`;
}
