import { promises as fs } from 'fs';
import path from 'path';
import { randomUUID } from 'crypto';

const dataDir = path.resolve(process.cwd(), 'data');
const dataFile = path.join(dataDir, 'servers.json');

async function ensureStore() {
  await fs.mkdir(dataDir, { recursive: true });
  try {
    await fs.access(dataFile);
  } catch {
    await fs.writeFile(dataFile, '[]', 'utf-8');
  }
}

export async function readServers() {
  await ensureStore();
  const raw = await fs.readFile(dataFile, 'utf-8');
  try {
    return JSON.parse(raw);
  } catch {
    return [];
  }
}

async function writeServers(servers) {
  await ensureStore();
  await fs.writeFile(dataFile, JSON.stringify(servers, null, 2), 'utf-8');
}

export async function addServer(payload) {
  const servers = await readServers();
  const timestamp = new Date().toISOString();
  const server = {
    id: randomUUID(),
    createdAt: timestamp,
    updatedAt: timestamp,
    position: servers.length,
    span: 1,
    poolsCollapsed: false,
    ...payload
  };
  servers.push(server);
  await writeServers(servers);
  return server;
}

export async function updateServer(id, patch) {
  const servers = await readServers();
  const index = servers.findIndex((server) => server.id === id);
  if (index === -1) {
    return null;
  }
  const timestamp = new Date().toISOString();
  const updated = {
    ...servers[index],
    ...patch,
    updatedAt: timestamp
  };
  servers[index] = updated;
  await writeServers(servers);
  return updated;
}

export async function getServer(id) {
  const servers = await readServers();
  return servers.find((server) => server.id === id) || null;
}

export async function reorderServers(order) {
  const servers = await readServers();
  const normalizedOrder = Array.isArray(order)
    ? order.map((id) => String(id))
    : [];
  const seen = new Set();
  const duplicates = normalizedOrder.filter((id) => {
    if (seen.has(id)) {
      return true;
    }
    seen.add(id);
    return false;
  });
  if (duplicates.length > 0) {
    throw new Error('Duplicate server ids in order payload.');
  }

  const serverMap = new Map(servers.map((server) => [server.id, server]));
  const unknown = normalizedOrder.filter((id) => !serverMap.has(id));
  if (unknown.length > 0) {
    throw new Error(`Unknown server id(s): ${unknown.join(', ')}`);
  }

  const ordered = normalizedOrder.map((id) => serverMap.get(id));
  const orderSet = new Set(normalizedOrder);
  const remaining = servers.filter((server) => !orderSet.has(server.id));
  const next = [...ordered, ...remaining];
  const timestamp = new Date().toISOString();

  const updated = next.map((server, index) => ({
    ...server,
    position: index,
    updatedAt: server.position === index ? server.updatedAt : timestamp
  }));

  await writeServers(updated);
  return updated;
}

const clampSpan = (value) => {
  const parsed = Number(value);
  if (!Number.isFinite(parsed)) {
    return null;
  }
  const rounded = Math.round(parsed);
  return Math.min(4, Math.max(1, rounded));
};

export async function applyServerLayout({ order, sizes }) {
  const servers = await readServers();
  let next = servers;
  let timestamp = new Date().toISOString();

  if (Array.isArray(order) && order.length > 0) {
    const normalizedOrder = order.map((id) => String(id));
    const seen = new Set();
    const duplicates = normalizedOrder.filter((id) => {
      if (seen.has(id)) {
        return true;
      }
      seen.add(id);
      return false;
    });
    if (duplicates.length > 0) {
      throw new Error('Duplicate server ids in order payload.');
    }

    const serverMap = new Map(next.map((server) => [server.id, server]));
    const unknown = normalizedOrder.filter((id) => !serverMap.has(id));
    if (unknown.length > 0) {
      throw new Error(`Unknown server id(s): ${unknown.join(', ')}`);
    }

    const ordered = normalizedOrder.map((id) => serverMap.get(id));
    const orderSet = new Set(normalizedOrder);
    const remaining = next.filter((server) => !orderSet.has(server.id));
    next = [...ordered, ...remaining].map((server, index) => ({
      ...server,
      position: index,
      updatedAt: server.position === index ? server.updatedAt : timestamp
    }));
  }

  if (Array.isArray(sizes) && sizes.length > 0) {
    const sizeMap = new Map();
    sizes.forEach((entry) => {
      if (!entry?.id) {
        return;
      }
      const span = clampSpan(entry.span);
      if (span !== null) {
        sizeMap.set(String(entry.id), span);
      }
    });

    if (sizeMap.size > 0) {
      next = next.map((server) => {
        if (!sizeMap.has(server.id)) {
          return server;
        }
        const span = sizeMap.get(server.id);
        if (server.span === span) {
          return server;
        }
        return {
          ...server,
          span,
          updatedAt: timestamp
        };
      });
    }
  }

  await writeServers(next);
  return next;
}

export async function deleteServer(id) {
  const servers = await readServers();
  const remaining = servers.filter((server) => server.id !== id);
  if (remaining.length === servers.length) {
    return null;
  }
  const timestamp = new Date().toISOString();
  const updated = remaining.map((server, index) => ({
    ...server,
    position: index,
    updatedAt: server.position === index ? server.updatedAt : timestamp
  }));
  await writeServers(updated);
  return { id };
}

export async function setPoolsCollapsed(id, collapsed) {
  const servers = await readServers();
  const index = servers.findIndex((server) => server.id === id);
  if (index === -1) {
    return null;
  }
  const updated = {
    ...servers[index],
    poolsCollapsed: Boolean(collapsed),
    updatedAt: new Date().toISOString()
  };
  servers[index] = updated;
  await writeServers(servers);
  return updated;
}
