import { useEffect, useState } from "react";
import { ApiError } from "../../api/client";
import {
  PERMISSIONS,
  ROLE_DEFAULT_PERMISSIONS,
  ROLES,
} from "../../api/types";
import type { Permission, Role, UserAdmin } from "../../api/types";
import { Button } from "../../components/ui/Button";
import { Field, Input } from "../../components/ui/Input";
import { Modal } from "../../components/ui/Modal";
import { useToast } from "../../components/ui/toast-context";
import { roleLabel } from "../../components/ui/status";
import { useUpdateUser } from "./hooks";
import { PasswordField } from "./PasswordField";
import { PermissionsField } from "./PermissionsField";
import { scorePassword } from "./password";

interface EditUserModalProps {
  /** The user being edited, or null when the dialog is closed. */
  user: UserAdmin | null;
  /** True when `user` is the signed-in admin (drives self-lockout guards). */
  isSelf: boolean;
  onClose: () => void;
}

const ADMIN_PERMS_NOTE =
  "Admins implicitly hold every permission, so there is nothing to choose here.";

function isRole(value: string): value is Role {
  return (ROLES as readonly string[]).includes(value);
}

/**
 * Edit-user form: change role, permissions, and active status, and optionally
 * set a new password (blank = leave unchanged — the password is never returned,
 * so it cannot be pre-filled and an empty field means "no change").
 *
 * Self-lockout guards (defense-in-depth + UX): when editing your own account
 * the "Disabled" control is locked (you cannot disable yourself) and you cannot
 * demote yourself out of `admin`. The server independently rejects a self-
 * disable; it does NOT currently block a self-role-demotion, so this client
 * guard is the only thing preventing an admin from accidentally locking
 * themselves out of the admin plane.
 *
 * Credential handling: the new-password value lives only in component state and
 * is wiped on every open/close; it is never logged.
 */
export function EditUserModal({ user, isSelf, onClose }: EditUserModalProps) {
  const toast = useToast();
  const update = useUpdateUser();

  const [email, setEmail] = useState("");
  const [role, setRole] = useState<Role>("operator");
  const [enabled, setEnabled] = useState(true);
  const [permissions, setPermissions] = useState<Permission[]>([]);
  const [permsTouched, setPermsTouched] = useState(false);
  const [newPassword, setNewPassword] = useState("");
  const [submitted, setSubmitted] = useState(false);

  // Seed the form from the target user whenever the dialog opens for one.
  useEffect(() => {
    if (!user) return;
    setEmail(user.email ?? "");
    setRole(isRole(user.role) ? user.role : "operator");
    setEnabled(user.enabled);
    // Keep only strings that are real, server-recognized permissions; drop any
    // legacy/unknown value so the editor never re-submits something invalid.
    setPermissions(
      user.permissions.filter((p): p is Permission =>
        (PERMISSIONS as readonly string[]).includes(p),
      ),
    );
    setPermsTouched(false);
    setNewPassword("");
    setSubmitted(false);
    update.reset();
    // update is stable; reseed only when the target user changes.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [user]);

  function handleRoleChange(next: Role) {
    setRole(next);
    if (!permsTouched) setPermissions(ROLE_DEFAULT_PERMISSIONS[next]);
  }

  function handlePermsChange(next: Permission[]) {
    setPermsTouched(true);
    setPermissions(next);
  }

  // Self-lockout: an admin editing their own account may not demote out of
  // admin (would strip their own management access). Lock the non-admin options.
  const lockSelfDemotion = isSelf && user?.role === "admin";

  const passwordStrength = scorePassword(newPassword);
  // Password is optional on edit; only validate when the admin actually typed one.
  const passwordError =
    submitted && newPassword.length > 0 && !passwordStrength.meetsMinimum
      ? "Password must be at least 8 characters."
      : undefined;

  const canSubmit =
    newPassword.length === 0 || passwordStrength.meetsMinimum;

  function handleSubmit() {
    if (!user) return;
    setSubmitted(true);
    if (newPassword.length > 0 && !passwordStrength.meetsMinimum) return;

    // Belt-and-suspenders self-guards (the controls are already locked, but
    // never trust the rendered state for a security-relevant action).
    if (isSelf && !enabled) {
      toast.error("Cannot disable your own account");
      return;
    }
    if (lockSelfDemotion && role !== "admin") {
      toast.error("Cannot remove your own admin role");
      return;
    }

    const sendPermissions =
      role !== "admin" && permsTouched ? permissions : undefined;

    update.mutate(
      {
        id: user.id,
        body: {
          email: email.trim() ? email.trim() : null,
          role,
          enabled,
          password: newPassword.length > 0 ? newPassword : undefined,
        },
        permissions: sendPermissions,
      },
      {
        onSuccess: () => {
          toast.success(
            "User updated",
            newPassword.length > 0
              ? `${user.username}'s password was reset.`
              : undefined,
          );
          setNewPassword("");
          onClose();
        },
        onError: (err) => {
          // ApiError carries a server message; the hook also rejects with a
          // plain Error on partial failure ("User saved, but permissions
          // update failed ...") — surface that text rather than swallowing it.
          const detail =
            err instanceof ApiError || err instanceof Error
              ? err.message
              : "Unexpected error.";
          toast.error("Could not update user", detail);
        },
      },
    );
  }

  if (!user) return null;

  const roleSelectId = "edit-user-role";

  return (
    <Modal
      open={user != null}
      title={
        <>
          Edit user ·{" "}
          <span className="mono" style={{ fontWeight: 500 }}>
            {user.username}
          </span>
        </>
      }
      ariaLabel={`Edit user ${user.username}`}
      onClose={update.isPending ? () => {} : onClose}
      dismissable={!update.isPending}
      wide
      footer={
        <>
          <Button variant="ghost" onClick={onClose} disabled={update.isPending}>
            Cancel
          </Button>
          <Button
            variant="primary"
            onClick={handleSubmit}
            loading={update.isPending}
            disabled={!canSubmit && submitted}
          >
            Save changes
          </Button>
        </>
      }
    >
      <div className="userform">
        <Field label="Email (optional)" htmlFor="edit-user-email">
          <Input
            id="edit-user-email"
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            disabled={update.isPending}
            autoComplete="off"
            spellCheck={false}
          />
        </Field>

        <div className="field">
          <label className="field__label" htmlFor={roleSelectId}>
            Role
          </label>
          <select
            id={roleSelectId}
            className="select"
            value={role}
            disabled={update.isPending || lockSelfDemotion}
            onChange={(e) => handleRoleChange(e.target.value as Role)}
            aria-describedby={
              lockSelfDemotion ? "edit-user-role-lock" : undefined
            }
          >
            {ROLES.map((r) => (
              <option key={r} value={r}>
                {roleLabel(r)}
              </option>
            ))}
          </select>
          {lockSelfDemotion && (
            <p className="userform__note" id="edit-user-role-lock">
              You cannot remove your own admin role.
            </p>
          )}
        </div>

        <PermissionsField
          value={permissions}
          onChange={handlePermsChange}
          disabled={update.isPending}
          lockedNote={role === "admin" ? ADMIN_PERMS_NOTE : undefined}
        />

        <div className="field">
          <span className="field__label">Account status</span>
          <label className="optline">
            <input
              type="checkbox"
              checked={!enabled}
              disabled={update.isPending || isSelf}
              onChange={(e) => setEnabled(!e.target.checked)}
            />
            <span>
              Disable this account
              {isSelf ? (
                <em className="optline__note">
                  {" "}
                  (you cannot disable your own account)
                </em>
              ) : (
                <em className="userform__hint">
                  {" "}
                  (a disabled user cannot sign in)
                </em>
              )}
            </span>
          </label>
        </div>

        <PasswordField
          label="Reset password (optional)"
          value={newPassword}
          onChange={setNewPassword}
          disabled={update.isPending}
          error={passwordError}
          emptyHint="Leave blank to keep the current password."
        />
      </div>
    </Modal>
  );
}
