import { readFile, writeFile, mkdir } from "node:fs/promises";
import { join } from "node:path";
import { randomUUID } from "node:crypto";
import type { AppsRegistry, AppHost, AppCategory, AppEntry, AppIcon } from "./types";

const APPS_DIR = process.env.APPS_DIR || "/opt/teletraan/apps";
const REGISTRY_PATH = join(APPS_DIR, "registry.json");

// ─── Async Mutex for Write Safety ────────────────────────────────────
let writeQueue: Promise<void> = Promise.resolve();

function serialized<T>(fn: () => Promise<T>): Promise<T> {
  const result = writeQueue.then(fn);
  writeQueue = result.then(
    () => {},
    () => {}
  );
  return result;
}

// ─── Read / Write ────────────────────────────────────────────────────

export async function readAppsRegistry(): Promise<AppsRegistry> {
  try {
    const raw = await readFile(REGISTRY_PATH, "utf-8");
    return JSON.parse(raw) as AppsRegistry;
  } catch (err: unknown) {
    if (err instanceof SyntaxError) throw err; // corrupt file — don't silently drop data
    return { hosts: {}, categories: {}, apps: {}, viewOrders: {} };
  }
}

async function writeAppsRegistry(registry: AppsRegistry): Promise<void> {
  await mkdir(APPS_DIR, { recursive: true });
  await writeFile(REGISTRY_PATH, JSON.stringify(registry, null, 2));
}

// ─── View Order Helper ──────────────────────────────────────────────

function removeAppFromViewOrders(registry: AppsRegistry, app: AppEntry): void {
  const hostKey = `host:${app.hostId}`;
  const catKey = `category:${app.categoryId}`;
  if (registry.viewOrders[hostKey]) {
    registry.viewOrders[hostKey] = registry.viewOrders[hostKey].filter(id => id !== app.id);
  }
  if (registry.viewOrders[catKey]) {
    registry.viewOrders[catKey] = registry.viewOrders[catKey].filter(id => id !== app.id);
  }
}

// ─── Hosts CRUD ─────────────────────────────────────────────────────

export async function listHosts(): Promise<AppHost[]> {
  const registry = await readAppsRegistry();
  return Object.values(registry.hosts);
}

export async function getHost(id: string): Promise<AppHost | null> {
  const registry = await readAppsRegistry();
  return registry.hosts[id] || null;
}

export function addHost(data: { name: string; address: string; description?: string }): Promise<AppHost> {
  return serialized(async () => {
    const registry = await readAppsRegistry();
    const id = randomUUID();
    const host: AppHost = { id, ...data };
    registry.hosts[id] = host;
    await writeAppsRegistry(registry);
    return host;
  });
}

export function updateHost(id: string, updates: { name?: string; address?: string; description?: string }): Promise<AppHost> {
  return serialized(async () => {
    const registry = await readAppsRegistry();
    const host = registry.hosts[id];
    if (!host) throw new Error(`Host "${id}" not found`);
    if (updates.name !== undefined) host.name = updates.name;
    if (updates.address !== undefined) host.address = updates.address;
    if (updates.description !== undefined) host.description = updates.description;
    registry.hosts[id] = host;
    await writeAppsRegistry(registry);
    return host;
  });
}

export function deleteHost(id: string, cascade: boolean = false): Promise<{ deleted: boolean; affectedApps?: AppEntry[] }> {
  return serialized(async () => {
    const registry = await readAppsRegistry();
    const host = registry.hosts[id];
    if (!host) throw new Error(`Host "${id}" not found`);

    const affected = Object.values(registry.apps).filter(a => a.hostId === id);
    if (affected.length > 0 && !cascade) {
      return { deleted: false, affectedApps: affected };
    }

    if (cascade) {
      for (const app of affected) {
        removeAppFromViewOrders(registry, app);
        delete registry.apps[app.id];
      }
    }

    delete registry.hosts[id];
    delete registry.viewOrders[`host:${id}`];
    await writeAppsRegistry(registry);
    return { deleted: true };
  });
}

// ─── Categories CRUD ────────────────────────────────────────────────

export async function listCategories(): Promise<AppCategory[]> {
  const registry = await readAppsRegistry();
  return Object.values(registry.categories);
}

export async function getCategory(id: string): Promise<AppCategory | null> {
  const registry = await readAppsRegistry();
  return registry.categories[id] || null;
}

export function addCategory(data: { name: string; description?: string }): Promise<AppCategory> {
  return serialized(async () => {
    const registry = await readAppsRegistry();
    const id = randomUUID();
    const category: AppCategory = { id, ...data };
    registry.categories[id] = category;
    await writeAppsRegistry(registry);
    return category;
  });
}

export function updateCategory(id: string, updates: { name?: string; description?: string }): Promise<AppCategory> {
  return serialized(async () => {
    const registry = await readAppsRegistry();
    const category = registry.categories[id];
    if (!category) throw new Error(`Category "${id}" not found`);
    if (updates.name !== undefined) category.name = updates.name;
    if (updates.description !== undefined) category.description = updates.description;
    registry.categories[id] = category;
    await writeAppsRegistry(registry);
    return category;
  });
}

export function deleteCategory(id: string, cascade: boolean = false): Promise<{ deleted: boolean; affectedApps?: AppEntry[] }> {
  return serialized(async () => {
    const registry = await readAppsRegistry();
    const category = registry.categories[id];
    if (!category) throw new Error(`Category "${id}" not found`);

    const affected = Object.values(registry.apps).filter(a => a.categoryId === id);
    if (affected.length > 0 && !cascade) {
      return { deleted: false, affectedApps: affected };
    }

    if (cascade) {
      for (const app of affected) {
        removeAppFromViewOrders(registry, app);
        delete registry.apps[app.id];
      }
    }

    delete registry.categories[id];
    delete registry.viewOrders[`category:${id}`];
    await writeAppsRegistry(registry);
    return { deleted: true };
  });
}

// ─── Apps CRUD ──────────────────────────────────────────────────────

export async function listApps(filters?: { hostId?: string; categoryId?: string }): Promise<AppEntry[]> {
  const registry = await readAppsRegistry();
  let apps = Object.values(registry.apps);
  if (filters?.hostId) apps = apps.filter(a => a.hostId === filters.hostId);
  if (filters?.categoryId) apps = apps.filter(a => a.categoryId === filters.categoryId);
  return apps;
}

export async function getApp(id: string): Promise<AppEntry | null> {
  const registry = await readAppsRegistry();
  return registry.apps[id] || null;
}

export function addApp(data: {
  name: string;
  url: string;
  hostId: string;
  categoryId: string;
  description?: string;
  icon?: AppIcon;
  color?: string;
}): Promise<AppEntry> {
  return serialized(async () => {
    const registry = await readAppsRegistry();
    if (!registry.hosts[data.hostId]) throw new Error(`Host "${data.hostId}" not found`);
    if (!registry.categories[data.categoryId]) throw new Error(`Category "${data.categoryId}" not found`);

    const id = randomUUID();
    const now = new Date().toISOString();
    const app: AppEntry = {
      id,
      name: data.name,
      url: data.url,
      hostId: data.hostId,
      categoryId: data.categoryId,
      description: data.description,
      icon: data.icon || { type: "lucide", value: "box" },
      color: data.color || "#6366f1",
      createdAt: now,
      updatedAt: now,
    };

    registry.apps[id] = app;

    // Append to both view orders
    const hostKey = `host:${data.hostId}`;
    const catKey = `category:${data.categoryId}`;
    if (!registry.viewOrders[hostKey]) registry.viewOrders[hostKey] = [];
    if (!registry.viewOrders[catKey]) registry.viewOrders[catKey] = [];
    registry.viewOrders[hostKey].push(id);
    registry.viewOrders[catKey].push(id);

    await writeAppsRegistry(registry);
    return app;
  });
}

export function updateApp(id: string, updates: {
  name?: string;
  url?: string;
  hostId?: string;
  categoryId?: string;
  description?: string;
  icon?: AppIcon;
  color?: string;
}): Promise<AppEntry> {
  return serialized(async () => {
    const registry = await readAppsRegistry();
    const app = registry.apps[id];
    if (!app) throw new Error(`App "${id}" not found`);

    const oldHostId = app.hostId;
    const oldCategoryId = app.categoryId;

    if (updates.name !== undefined) app.name = updates.name;
    if (updates.url !== undefined) app.url = updates.url;
    if (updates.description !== undefined) app.description = updates.description;
    if (updates.icon !== undefined) app.icon = updates.icon;
    if (updates.color !== undefined) app.color = updates.color;

    if (updates.hostId !== undefined && updates.hostId !== oldHostId) {
      if (!registry.hosts[updates.hostId]) throw new Error(`Host "${updates.hostId}" not found`);
      app.hostId = updates.hostId;
      // Move between host view orders
      const oldKey = `host:${oldHostId}`;
      const newKey = `host:${updates.hostId}`;
      if (registry.viewOrders[oldKey]) {
        registry.viewOrders[oldKey] = registry.viewOrders[oldKey].filter(i => i !== id);
      }
      if (!registry.viewOrders[newKey]) registry.viewOrders[newKey] = [];
      registry.viewOrders[newKey].push(id);
    }

    if (updates.categoryId !== undefined && updates.categoryId !== oldCategoryId) {
      if (!registry.categories[updates.categoryId]) throw new Error(`Category "${updates.categoryId}" not found`);
      app.categoryId = updates.categoryId;
      // Move between category view orders
      const oldKey = `category:${oldCategoryId}`;
      const newKey = `category:${updates.categoryId}`;
      if (registry.viewOrders[oldKey]) {
        registry.viewOrders[oldKey] = registry.viewOrders[oldKey].filter(i => i !== id);
      }
      if (!registry.viewOrders[newKey]) registry.viewOrders[newKey] = [];
      registry.viewOrders[newKey].push(id);
    }

    app.updatedAt = new Date().toISOString();
    registry.apps[id] = app;
    await writeAppsRegistry(registry);
    return app;
  });
}

export function deleteApp(id: string): Promise<void> {
  return serialized(async () => {
    const registry = await readAppsRegistry();
    const app = registry.apps[id];
    if (!app) throw new Error(`App "${id}" not found`);

    removeAppFromViewOrders(registry, app);
    delete registry.apps[id];
    await writeAppsRegistry(registry);
  });
}

// ─── View Orders ────────────────────────────────────────────────────

export async function getViewOrder(viewKey: string): Promise<string[]> {
  const registry = await readAppsRegistry();
  return registry.viewOrders[viewKey] || [];
}

export function updateViewOrder(viewKey: string, order: string[]): Promise<void> {
  return serialized(async () => {
    const registry = await readAppsRegistry();
    registry.viewOrders[viewKey] = order;
    await writeAppsRegistry(registry);
  });
}
