import { readValidatedBody } from 'h3';
import { parseCidr } from 'cidr-tools';
import { stringifyIp } from 'ip-bigint';
import { z } from 'zod';

import Database from '#server/utils/Database';
import { defineSetupEventHandler } from '#server/utils/handler';
import { nextIPFromUsedAddresses } from '#server/utils/ip';
import { FileSchema, validateZod } from '#server/utils/types';

export default defineSetupEventHandler('migrate', async ({ event }) => {
  const { file } = await readValidatedBody(
    event,
    validateZod(FileSchema, event)
  );

  const schema = z.object({
    server: z.object({
      privateKey: z.string(),
      publicKey: z.string(),
      // only used for cidr
      address: z.string(),
    }),
    clients: z.record(
      z.string(),
      z.object({
        // not used, breaks compatibility with older versions
        id: z.string().optional(),
        name: z.string(),
        address: z.string(),
        privateKey: z.string(),
        publicKey: z.string(),
        preSharedKey: z.string(),
        createdAt: z.string(),
        updatedAt: z.string(),
        enabled: z.boolean(),
      })
    ),
  });
  const res = await schema.safeParseAsync(JSON.parse(file));
  if (!res.success) {
    throw new Error('Invalid Config');
  }

  const oldConfig = res.data;

  await Database.interfaces.updateKeyPair(
    oldConfig.server.privateKey,
    oldConfig.server.publicKey
  );

  const ipv4Cidr = parseCidr(oldConfig.server.address + '/24');
  const ipv6Cidr = parseCidr('fdcc:ad94:bacf:61a4::cafe:0/112');

  await Database.interfaces.updateCidr({
    ipv4Cidr:
      stringifyIp({ number: ipv4Cidr.start, version: 4 }) +
      `/${ipv4Cidr.prefix}`,
    ipv6Cidr: ipv6Cidr.cidr,
  });
  const ipv6Addresses = new Set(
    (await Database.clients.getAll()).map((client) => client.ipv6Address)
  );

  for (const clientId in oldConfig.clients) {
    const clientConfig = oldConfig.clients[clientId];

    if (!clientConfig) {
      continue;
    }

    const ipv6Address = nextIPFromUsedAddresses(6, ipv6Cidr, ipv6Addresses);

    await Database.clients.createFromExisting({
      ...clientConfig,
      ipv4Address: clientConfig.address,
      ipv6Address,
    });
    ipv6Addresses.add(ipv6Address);
  }

  await Database.general.setSetupStep(0);
  return { success: true };
});
