"use server";

import { randomInt } from "node:crypto";
import { revalidatePath } from "next/cache";
import { z } from "zod";

import { ActionError, runAction, type ActionResult } from "@/lib/actions";
import { requireActionAdmin } from "@/lib/auth/guards";
import { cmsMessage, cmsPaths, cmsRequest, field } from "@/lib/cms/api";
import { cleanText } from "@/lib/utils";

import {
  changePasswordSchema,
  createAdminUserSchema,
  profileSchema,
  roleSchema,
  type ChangePasswordInput,
  type CreateAdminUserInput,
  type ProfileInput,
} from "./schemas";

/*
 * Team and account changes go to the Laravel CMS (docs/laravel-blog-api.md
 * §8.1/§8.2), which checks passwords, rate limits, revokes tokens and keeps
 * at least one active admin.
 */

const idSchema = z.string().min(1).max(64);

// Letters and digits that can't be misread (no 0/O, 1/l/I), for passwords read aloud or retyped.
const PASSWORD_ALPHABET =
  "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";

/** 20 random characters in groups of five: about 117 bits, and easy to retype. */
function temporaryPassword() {
  const group = () =>
    Array.from(
      { length: 5 },
      () => PASSWORD_ALPHABET[randomInt(PASSWORD_ALPHABET.length)],
    ).join("");
  return Array.from({ length: 4 }, group).join("-");
}

/** The password Laravel reports it set, else the one this app proposed. */
function passwordFrom(body: unknown, proposed: string) {
  const data = field(body, "data") ?? body;
  const value =
    field<string>(data, "temporary_password") ??
    field<string>(body, "temporary_password");
  return typeof value === "string" && value ? value : proposed;
}

/** A cleaned name, or a field error when nothing printable is left. */
function cleanName(value: string) {
  const name = cleanText(value);
  if (!name) {
    throw new ActionError("Enter a name.", 400, { name: "Enter a name." });
  }
  return name;
}

/** The team actions never apply to the caller's own account. */
function otherMemberId(userId: string, adminId: string, verb: string) {
  const id = idSchema.parse(userId);
  if (id === adminId) {
    throw new ActionError(`You can't ${verb} your own account.`, 403);
  }
  return id;
}

// ---------------------------------------------------------------------------
// Your account
// ---------------------------------------------------------------------------

/** `PUT admin/auth/me` */
export async function updateProfile(
  input: ProfileInput,
): Promise<ActionResult> {
  return runAction(async () => {
    await requireActionAdmin();
    const values = profileSchema.parse(input);
    const name = cleanName(values.name);

    const body = await cmsRequest(cmsPaths.me, {
      method: "PUT",
      json: { name, email: values.email, avatarUrl: values.avatarUrl },
    });

    // The sidebar shows your name and avatar on every admin page.
    revalidatePath("/admin", "layout");
    return { ok: true, message: cmsMessage(body, "Profile saved.") };
  });
}

/** `PUT admin/auth/me/password`. Laravel checks the current password. */
export async function changePassword(
  input: ChangePasswordInput,
): Promise<ActionResult> {
  return runAction(async () => {
    await requireActionAdmin();
    const values = changePasswordSchema.parse(input);
    if (values.newPassword === values.currentPassword) {
      throw new ActionError("Choose a new password.", 400, {
        newPassword: "Use a password different from your current one.",
      });
    }

    const body = await cmsRequest(cmsPaths.password, {
      method: "PUT",
      json: {
        currentPassword: values.currentPassword,
        newPassword: values.newPassword,
      },
    });

    return { ok: true, message: cmsMessage(body, "Password updated.") };
  });
}

// ---------------------------------------------------------------------------
// Team (admins only)
// ---------------------------------------------------------------------------

/** `POST admin/users` with a generated temporary password. */
export async function createAdminUser(
  input: CreateAdminUserInput,
): Promise<ActionResult<{ temporaryPassword: string }>> {
  return runAction(async () => {
    await requireActionAdmin("ADMIN");
    const values = createAdminUserSchema.parse(input);
    const name = cleanName(values.name);

    const proposed = temporaryPassword();
    const body = await cmsRequest(cmsPaths.users, {
      method: "POST",
      json: {
        name,
        email: values.email,
        role: values.role,
        password: proposed,
      },
    });

    revalidatePath("/admin/settings");
    return {
      ok: true,
      message: cmsMessage(body, `${name} was added to the team.`),
      data: { temporaryPassword: passwordFrom(body, proposed) },
    };
  });
}

/** `PATCH admin/users/{id}/role` */
export async function changeAdminRole(
  userId: string,
  role: string,
): Promise<ActionResult> {
  return runAction(async () => {
    const admin = await requireActionAdmin("ADMIN");
    const nextRole = roleSchema.parse(role);
    const id = otherMemberId(userId, admin.id, "change the role of");

    const body = await cmsRequest(cmsPaths.userAction(id, "role"), {
      method: "PATCH",
      json: { role: nextRole },
    });

    revalidatePath("/admin/settings");
    return {
      ok: true,
      message: cmsMessage(
        body,
        `Role changed to ${nextRole === "ADMIN" ? "admin" : "editor"}.`,
      ),
    };
  });
}

/** `POST admin/users/{id}/reset-password`. Laravel signs them out. */
export async function resetAdminPassword(
  userId: string,
): Promise<ActionResult<{ temporaryPassword: string }>> {
  return runAction(async () => {
    const admin = await requireActionAdmin("ADMIN");
    const id = otherMemberId(userId, admin.id, "reset the password of");

    const proposed = temporaryPassword();
    const body = await cmsRequest(cmsPaths.userAction(id, "reset-password"), {
      method: "POST",
      json: { password: proposed },
    });

    revalidatePath("/admin/settings");
    return {
      ok: true,
      message: cmsMessage(body, "Password reset. They were signed out."),
      data: { temporaryPassword: passwordFrom(body, proposed) },
    };
  });
}

/** `PATCH admin/users/{id}/active` */
export async function setAdminUserActive(
  userId: string,
  active: boolean,
): Promise<ActionResult> {
  return runAction(async () => {
    const admin = await requireActionAdmin("ADMIN");
    const isActive = z.boolean().parse(active);
    const id = otherMemberId(
      userId,
      admin.id,
      isActive ? "reactivate" : "deactivate",
    );

    const body = await cmsRequest(cmsPaths.userAction(id, "active"), {
      method: "PATCH",
      json: { isActive },
    });

    revalidatePath("/admin/settings");
    return {
      ok: true,
      message: cmsMessage(
        body,
        isActive ? "They can sign in again." : "Deactivated and signed out.",
      ),
    };
  });
}

/** `DELETE admin/users/{id}`. Their articles and uploads stay. */
export async function deleteAdminUser(userId: string): Promise<ActionResult> {
  return runAction(async () => {
    const admin = await requireActionAdmin("ADMIN");
    const id = otherMemberId(userId, admin.id, "delete");

    const body = await cmsRequest(cmsPaths.user(id), { method: "DELETE" });

    revalidatePath("/admin/settings");
    return { ok: true, message: cmsMessage(body, "Removed from the team.") };
  });
}
