import { randomBytes } from "node:crypto";

const platformGroups = {
  superadmin: "nodedc:superadmin",
  launcherAdmin: "nodedc:launcher:admin",
  launcherUser: "nodedc:launcher:user",
  taskManagerAdmin: "nodedc:taskmanager:admin",
  taskManagerUser: "nodedc:taskmanager:user",
  engineAdmin: "nodedc_admin",
  engineEditor: "nodedc_editor",
  engineViewer: "nodedc_viewer",
};
const engineServiceSlugs = new Set(["nodedc", "engine", "nodedc-engine"]);
const publicPoolClientId = "client_public_pool";
const publicPoolClient = {
  id: publicPoolClientId,
  status: "active",
};

export function createAuthentikSyncClient({ baseUrl, token }) {
  const normalizedBaseUrl = String(baseUrl || "").replace(/\/$/, "");

  function isConfigured() {
    return Boolean(normalizedBaseUrl && token);
  }

  async function provisionUser({ data, userId, password, generatePassword = false }) {
    ensureConfigured();

    const user = findById(data.users, userId, "user");
    const requiredGroups = resolveRequiredGroups(data, user);
    const groups = await ensureGroups(requiredGroups);
    const existingUser = await findUserByIdOrEmail(user.authentikUserId, user.email);
    const temporaryPassword = password || (generatePassword && !existingUser ? generatePasswordValue() : null);
    const payload = {
      username: user.email.toLowerCase(),
      email: user.email.toLowerCase(),
      name: user.name,
      is_active: user.globalStatus === "active",
      type: "internal",
      groups: groups.map((group) => group.pk),
      attributes: {
        nodedc_user_id: user.id,
        nodedc_source: "launcher-control-plane",
        picture: user.avatarUrl || undefined,
        avatar_url: user.avatarUrl || undefined,
      },
    };
    const authentikUser = existingUser
      ? await requestJson(`/api/v3/core/users/${encodeURIComponent(existingUser.pk)}/`, {
          method: "PATCH",
          body: JSON.stringify(payload),
        })
      : await requestJson("/api/v3/core/users/", {
          method: "POST",
          body: JSON.stringify(payload),
        });

    if (temporaryPassword) {
      await setPassword(authentikUser.pk, temporaryPassword);
    }

    return {
      authentikUserId: String(authentikUser.uuid || authentikUser.uid || authentikUser.pk),
      authentikPk: authentikUser.pk,
      email: authentikUser.email,
      name: authentikUser.name,
      groups: requiredGroups,
      created: !existingUser,
      temporaryPassword,
    };
  }

  async function deleteUser({ data, userId }) {
    ensureConfigured();

    const user = findById(data.users, userId, "user");
    const existingUser = await findUserByIdOrEmail(user.authentikUserId, user.email);

    if (!existingUser) {
      return {
        deleted: false,
        email: user.email,
        authentikUserId: user.authentikUserId ?? null,
        authentikPk: null,
      };
    }

    await requestJson(`/api/v3/core/users/${encodeURIComponent(existingUser.pk)}/`, { method: "DELETE" });

    return {
      deleted: true,
      email: user.email,
      authentikUserId: user.authentikUserId ?? null,
      authentikPk: existingUser.pk,
    };
  }

  async function findUserByIdOrEmail(authentikUserId, email) {
    if (authentikUserId) {
      const payload = await requestJson(`/api/v3/core/users/?search=${encodeURIComponent(authentikUserId)}`);
      const users = Array.isArray(payload.results) ? payload.results : [];
      const existingUser = users.find((user) => {
        const identifiers = [user.uuid, user.uid, user.pk].map((value) => String(value || ""));
        return identifiers.includes(String(authentikUserId));
      });

      if (existingUser) {
        return existingUser;
      }
    }

    const payload = await requestJson(`/api/v3/core/users/?search=${encodeURIComponent(email)}`);
    const users = Array.isArray(payload.results) ? payload.results : [];
    return users.find((user) => String(user.email || "").toLowerCase() === email.toLowerCase()) ?? null;
  }

  async function ensureGroups(groupNames) {
    const groups = [];

    for (const groupName of groupNames) {
      groups.push(await ensureGroup(groupName));
    }

    return groups;
  }

  async function ensureGroup(groupName) {
    const payload = await requestJson(`/api/v3/core/groups/?search=${encodeURIComponent(groupName)}`);
    const groups = Array.isArray(payload.results) ? payload.results : [];
    const existingGroup = groups.find((group) => group.name === groupName);

    if (existingGroup) {
      return existingGroup;
    }

    return requestJson("/api/v3/core/groups/", {
      method: "POST",
      body: JSON.stringify({
        name: groupName,
        is_superuser: false,
        attributes: {
          nodedc_source: "launcher-control-plane",
        },
      }),
    });
  }

  async function setPassword(userPk, password) {
    await requestJson(`/api/v3/core/users/${encodeURIComponent(userPk)}/set_password/`, {
      method: "POST",
      body: JSON.stringify({ password }),
    });
  }

  async function requestJson(path, init = {}) {
    ensureConfigured();

    const headers = new Headers(init.headers);
    headers.set("Authorization", `Bearer ${token}`);
    headers.set("Accept", "application/json");

    if (init.body && !headers.has("Content-Type")) {
      headers.set("Content-Type", "application/json");
    }

    const response = await fetch(`${normalizedBaseUrl}${path}`, {
      ...init,
      headers,
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`Authentik API ${path} failed: HTTP ${response.status} ${errorText}`);
    }

    return response.status === 204 ? null : response.json();
  }

  function ensureConfigured() {
    if (!isConfigured()) {
      throw new Error("Authentik API is not configured. Set AUTHENTIK_BOOTSTRAP_TOKEN or NODEDC_AUTHENTIK_SERVICE_TOKEN server-side.");
    }
  }

  return {
    deleteUser,
    isConfigured,
    provisionUser,
  };
}

export function resolveRequiredGroups(data, user) {
  const groupNames = new Set();

  if (user.globalStatus !== "active") {
    return [];
  }

  groupNames.add(platformGroups.launcherUser);

  if (user.id === "user_root") {
    groupNames.add(platformGroups.superadmin);
    groupNames.add(platformGroups.launcherAdmin);
    groupNames.add(platformGroups.taskManagerAdmin);
    groupNames.add(platformGroups.taskManagerUser);
    addGroups(groupNames, resolveEngineRoleGroups("member"));
    return [...groupNames];
  }

  for (const client of getUserRuntimeClients(data, user.id)) {
    const membership = getRuntimeMembership(data, user.id, client.id);

    if (membership.status !== "active") {
      continue;
    }

    const userGroups = getUserGroups(data, user.id, client.id);

    for (const service of data.services) {
      const access = computeEffectiveAccess(data, { client, user, membership, userGroups, service });

      if (!access.allowed) {
        continue;
      }

      if (isEngineService(service)) {
        addGroups(groupNames, resolveEngineRoleGroups(access.appRole));
      } else if (service.slug === "task-manager") {
        groupNames.add(platformGroups.taskManagerUser);

        if (access.appRole === "admin" || access.appRole === "owner") {
          groupNames.add(platformGroups.taskManagerAdmin);
        }
      } else if (service.authentikGroupName) {
        groupNames.add(service.authentikGroupName);
      }
    }
  }

  return [...groupNames];
}

function isEngineService(service) {
  return (
    engineServiceSlugs.has(service.slug) ||
    engineServiceSlugs.has(service.authentikApplicationSlug) ||
    service.id === "service_nodedc"
  );
}

function resolveEngineRoleGroups(appRole) {
  if (appRole === "admin" || appRole === "owner") {
    return [platformGroups.engineAdmin];
  }

  if (appRole === "viewer") {
    return [platformGroups.engineViewer];
  }

  return [platformGroups.engineEditor];
}

function addGroups(target, groups) {
  for (const group of groups) {
    target.add(group);
  }
}

function getUserRuntimeClients(data, userId) {
  const clients = [...data.clients];
  const hasPublicPoolMembership = data.memberships.some(
    (membership) => membership.userId === userId && membership.clientId === publicPoolClientId
  );

  if (hasPublicPoolMembership) {
    clients.push(publicPoolClient);
  }

  return clients;
}

function generatePasswordValue() {
  return `NDC-${randomBytes(15).toString("base64url")}`;
}

function computeEffectiveAccess(data, { client, user, membership, userGroups, service }) {
  if (client.status === "suspended" || client.status === "expired") {
    return { allowed: false };
  }

  if (user.globalStatus === "blocked" || membership.status === "disabled") {
    return { allowed: false };
  }

  if (service.status === "disabled" || service.status === "hidden") {
    return { allowed: false };
  }

  const deny = data.exceptions.find(
    (exception) => exception.serviceId === service.id && exception.userId === user.id && exception.type === "deny"
  );

  if (deny) {
    return { allowed: false };
  }

  const allow = data.exceptions.find(
    (exception) => exception.serviceId === service.id && exception.userId === user.id && exception.type === "allow"
  );

  if (allow) {
    return { allowed: true };
  }

  const userGrant = data.grants.find(
    (grant) =>
      grant.serviceId === service.id &&
      grant.targetType === "user" &&
      grant.targetId === user.id &&
      grant.status === "active"
  );

  if (userGrant) {
    return { allowed: true, appRole: userGrant.appRole };
  }

  const groupIds = userGroups.map((group) => group.id);
  const groupGrant = data.grants.find(
    (grant) =>
      grant.serviceId === service.id &&
      grant.targetType === "group" &&
      groupIds.includes(grant.targetId) &&
      grant.status === "active"
  );

  if (groupGrant) {
    return { allowed: true, appRole: groupGrant.appRole };
  }

  const clientGrant = data.grants.find(
    (grant) =>
      grant.serviceId === service.id &&
      grant.targetType === "client" &&
      grant.targetId === client.id &&
      grant.status === "active"
  );

  if (clientGrant) {
    return { allowed: true, appRole: clientGrant.appRole };
  }

  return { allowed: false };
}

function getRuntimeMembership(data, userId, clientId) {
  return (
    data.memberships.find((membership) => membership.userId === userId && membership.clientId === clientId) ?? {
      id: `missing_${clientId}_${userId}`,
      clientId,
      userId,
      role: "member",
      status: "disabled",
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
    }
  );
}

function getUserGroups(data, userId, clientId) {
  return data.groups.filter((group) => group.clientId === clientId && group.memberIds.includes(userId));
}

function findById(items, id, label) {
  const item = items.find((candidate) => candidate.id === id);

  if (!item) {
    throw new Error(`Unknown ${label}: ${id}`);
  }

  return item;
}
