"use server";

import "server-only";
import { query, withTx } from "@/lib/db";
import {
  createUser as neonAuthCreateUser,
  requestPasswordReset as neonAuthRequestPasswordReset,
} from "@/lib/auth";
import { getAdminUser } from "@/lib/admin-permissions";

export type AdminUserRow = {
  id: string;
  user_id: string | null;
  display_name: string | null;
  email: string;
  phone_number: string | null;
  role: "platform_admin" | "brand_admin" | "store_employee" | "staff";
  brand_id: string | null;
  brand_name: string | null;
  can_manage_products: boolean;
  can_manage_stops: boolean;
  can_manage_orders: boolean;
  can_manage_pickup: boolean;
  can_manage_messages: boolean;
  can_manage_refunds: boolean;
  can_manage_users: boolean;
  can_manage_water_log: boolean;
  can_manage_reports: boolean;
  active: boolean;
  must_change_password: boolean;
  created_at: string;
  last_login: string | null;
};

export type CreateAdminUserInput = {
  email: string;
  password: string;
  role: "platform_admin" | "brand_admin" | "store_employee";
  brand_id: string | null;
  display_name?: string;
  phone_number?: string;
  flags: {
    can_manage_products?: boolean;
    can_manage_stops?: boolean;
    can_manage_orders?: boolean;
    can_manage_pickup?: boolean;
    can_manage_messages?: boolean;
    can_manage_refunds?: boolean;
    can_manage_users?: boolean;
    can_manage_water_log?: boolean;
    can_manage_reports?: boolean;
  };
  mustChangePassword?: boolean;
};

export type UpdateAdminUserInput = {
  id: string;
  role?: "platform_admin" | "brand_admin" | "store_employee";
  brand_id?: string | null;
  flags?: Partial<{
    can_manage_products: boolean;
    can_manage_stops: boolean;
    can_manage_orders: boolean;
    can_manage_pickup: boolean;
    can_manage_messages: boolean;
    can_manage_refunds: boolean;
    can_manage_users: boolean;
    can_manage_water_log: boolean;
    can_manage_reports: boolean;
  }>;
  active?: boolean;
  display_name?: string | null;
  phone_number?: string | null;
};

// ─── Row mapping ────────────────────────────────────────────────────────────
//
// `admin_users` schema (after migration 204 + 034 + 037):
//   id, user_id, display_name, email, phone_number, role, brand_id,
//   can_manage_<X> (BOOLEAN each), active, must_change_password,
//   created_at, last_login, raw_user_meta_data, auth_provider, auth_subject

function mapUserRow(row: Record<string, unknown>): AdminUserRow {
  return {
    id: String(row.id ?? ""),
    user_id: (row.user_id as string | null) ?? null,
    display_name: (row.display_name as string | null) ?? null,
    email: String(row.email ?? ""),
    phone_number: (row.phone_number as string | null) ?? null,
    role: (row.role as AdminUserRow["role"]) ?? "store_employee",
    brand_id: (row.brand_id as string | null) ?? null,
    brand_name: (row.brand_name as string | null) ?? null,
    can_manage_products: Boolean(row.can_manage_products ?? false),
    can_manage_stops: Boolean(row.can_manage_stops ?? false),
    can_manage_orders: Boolean(row.can_manage_orders ?? false),
    can_manage_pickup: Boolean(row.can_manage_pickup ?? false),
    can_manage_messages: Boolean(row.can_manage_messages ?? false),
    can_manage_refunds: Boolean(row.can_manage_refunds ?? false),
    can_manage_users: Boolean(row.can_manage_users ?? false),
    can_manage_water_log: Boolean(row.can_manage_water_log ?? false),
    can_manage_reports: Boolean(row.can_manage_reports ?? false),
    active: Boolean(row.active ?? true),
    must_change_password: Boolean(row.must_change_password ?? false),
    created_at: String(row.created_at ?? ""),
    last_login: (row.last_login as string | null) ?? null,
  };
}

// ─── Welcome email (best-effort) ────────────────────────────────────────────

async function sendWelcomeEmailSafe(input: {
  to: string;
  name: string;
  role: "platform_admin" | "brand_admin" | "store_employee";
  password: string;
  brandId?: string;
}): Promise<{ sent: boolean; error?: string }> {
  try {
    const { sendWelcomeEmail } = await import("@/lib/email-service");
    const { getBrandSettings } = await import("@/actions/brand-settings");
    const emailRole = input.role === "platform_admin" ? "brand_admin" : input.role;

    let logoUrl: string | null = null;
    let brandName = "Route Commerce";
    if (input.brandId) {
      try {
        const settings = await getBrandSettings(input.brandId);
        if (settings.success && settings.settings) {
          logoUrl = settings.settings.logo_url ?? null;
          brandName = settings.settings.brand_name ?? brandName;
        }
      } catch (brandErr) {
        console.warn(
          "[createAdminUser] Failed to load brand settings for welcome email:",
          brandErr,
        );
      }
    }

    const result = await sendWelcomeEmail({
      to: input.to,
      name: input.name,
      role: emailRole as "brand_admin" | "wholesale_buyer" | "store_employee",
      brandName,
      tempPassword: input.password,
      logoUrl: logoUrl ?? undefined,
    });
    return result.ok ? { sent: true } : { sent: false, error: result.error };
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    console.error("[createAdminUser] Welcome email failed (non-fatal):", msg);
    return { sent: false, error: msg };
  }
}

// ─── Public actions ─────────────────────────────────────────────────────────

export async function getAdminUsers(brandId?: string): Promise<{ users: AdminUserRow[]; error: string | null }> {
  try {
    const sql = brandId
      ? `SELECT au.id, au.user_id, au.display_name, au.email, au.phone_number,
               au.role, au.brand_id, b.name AS brand_name,
               au.can_manage_products, au.can_manage_stops, au.can_manage_orders,
               au.can_manage_pickup, au.can_manage_messages, au.can_manage_refunds,
               au.can_manage_users, au.can_manage_water_log, au.can_manage_reports,
               au.active, au.must_change_password, au.created_at, au.last_login
           FROM admin_users au
           LEFT JOIN brands b ON b.id = au.brand_id
           WHERE au.brand_id = $1
           ORDER BY au.created_at DESC`
      : `SELECT au.id, au.user_id, au.display_name, au.email, au.phone_number,
               au.role, au.brand_id, b.name AS brand_name,
               au.can_manage_products, au.can_manage_stops, au.can_manage_orders,
               au.can_manage_pickup, au.can_manage_messages, au.can_manage_refunds,
               au.can_manage_users, au.can_manage_water_log, au.can_manage_reports,
               au.active, au.must_change_password, au.created_at, au.last_login
           FROM admin_users au
           LEFT JOIN brands b ON b.id = au.brand_id
           ORDER BY au.created_at DESC`;
    const { rows } = await query<Record<string, unknown>>(sql, brandId ? [brandId] : []);
    return { users: rows.map(mapUserRow), error: null };
  } catch (err) {
    return { users: [], error: err instanceof Error ? err.message : String(err) };
  }
}

export type CreateAdminUserResult = {
  user: AdminUserRow | null;
  error: string | null;
  /**
   * The temporary password that was set on the Neon Auth account. Only
   * populated on success. The UI should display this once to the caller
   * and offer a copy-to-clipboard affordance — the password is never
   * stored in plaintext and cannot be retrieved again.
   */
  tempPassword?: string;
  /**
   * Whether the welcome email was dispatched. Best-effort — the user is
   * created even if email fails. False here means the caller should
   * share the password out-of-band.
   */
  emailSent?: boolean;
  /**
   * Set when `emailSent` is false. Helps the UI show a useful warning.
   */
  emailError?: string;
  /**
   * Which Neon Auth path was used to create the account. "admin" means
   * the caller's Neon Auth session has `role = 'admin'` and we used the
   * privileged `auth.admin.createUser` endpoint. "signup" means we had
   * to fall back to the public `/sign-up/email` endpoint (e.g. the
   * caller's Neon Auth user is not flagged as admin, or the admin
   * endpoint rejected the request). The choice is recorded for
   * auditability.
   */
  authPath?: "admin" | "signup";
};

/**
 * Create a new admin user. Steps:
 *
 *   1. Verify the caller is a `platform_admin`.
 *   2. Create the Neon Auth account (with the provided password) and
 *      link it to the local `admin_users` row via `user_id`. Tries the
 *      privileged `auth.admin.createUser` first; falls back to the
 *      public `/sign-up/email` + `emailVerified = true` pattern when
 *      the caller's Neon Auth session is not an admin (which is the
 *      common case in dev / when a brand admin's user was not
 *      provisioned as Neon Auth admin).
 *   3. Insert `admin_user_brands` link if a brand was selected.
 *   4. Send a welcome email with the temporary password (best-effort).
 *
 * The temp password is returned to the caller so the UI can show it
 * once — this is the only opportunity to share it with the new user,
 * since Neon Auth stores a hash, not the plaintext.
 */
export async function createAdminUser(
  input: CreateAdminUserInput,
): Promise<CreateAdminUserResult> {
  // 1. Authorization: only platform admins can mint new admin users.
  const caller = await getAdminUser();
  if (!caller) {
    return { user: null, error: "Not authenticated. Please sign in again." };
  }
  if (caller.role !== "platform_admin") {
    return {
      user: null,
      error: "Only platform admins can create new admin users.",
    };
  }

  const email = input.email.trim().toLowerCase();
  const password = input.password;
  const displayName = input.display_name ?? email.split("@")[0];
  const f = input.flags;

  let neonAuthUserId: string;
  let authPath: "admin" | "signup" = "admin";
  try {
    // 2. Create the Neon Auth account. Try the privileged admin
    // endpoint first.
    const adminResult = await neonAuthCreateUser({
      email,
      password,
      name: displayName,
    });

    if (adminResult.data?.user?.id) {
      neonAuthUserId = String(adminResult.data.user.id);
      authPath = "admin";
    } else if (adminResult.error) {
      // 2b. Fallback: public sign-up + flip emailVerified to true. The
      // admin endpoint requires the caller to have role='admin' in
      // Neon Auth, which is not always true (provision-admin.ts does
      // not set it). Falling back to sign-up is a safe, well-tested
      // path — see scripts/seed-admin.ts.
      const code = adminResult.error.code ?? "";
      const isAuthRequired =
        code === "FAILED_TO_CREATE_USER" ||
        code === "FORBIDDEN" ||
        code === "UNAUTHORIZED" ||
        code === "INTERNAL_SERVER_ERROR" ||
        code === "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL";

      if (code === "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL") {
        return {
          user: null,
          error: `A Neon Auth user with email ${email} already exists. Pick a different email or reset the existing user's password.`,
        };
      }

      if (!isAuthRequired) {
        return {
          user: null,
          error: `Neon Auth createUser failed: ${adminResult.error.message ?? code ?? "unknown error"}`,
        };
      }

      authPath = "signup";
      const signupResult = await signupFallbackCreate(email, password, displayName);
      if (!signupResult.userId) {
        return {
          user: null,
          error: `Could not create Neon Auth user via fallback sign-up: ${signupResult.error}`,
        };
      }
      neonAuthUserId = signupResult.userId;
    } else {
      return {
        user: null,
        error: "Neon Auth createUser returned neither data nor error.",
      };
    }
  } catch (err) {
    return {
      user: null,
      error: `Neon Auth error: ${err instanceof Error ? err.message : String(err)}`,
    };
  }

  // 3. Insert the local admin_users row + brand link in a transaction.
  //    We do this AFTER creating the Neon Auth user so a Neon Auth
  //    failure doesn't leave a dangling local row.
  let insertedRow: Record<string, unknown> | null = null;
  try {
    const result = await withTx(async (client) => {
      const ins = await client.query<Record<string, unknown>>(
        `INSERT INTO admin_users
           (user_id, display_name, email, phone_number, role, brand_id,
            can_manage_products, can_manage_stops, can_manage_orders,
            can_manage_pickup, can_manage_messages, can_manage_refunds,
            can_manage_users, can_manage_water_log, can_manage_reports,
            active, must_change_password, auth_provider, auth_subject)
         VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,true,$16,'neon_auth',$17)
         ON CONFLICT (user_id) DO UPDATE SET
           display_name        = EXCLUDED.display_name,
           email               = EXCLUDED.email,
           phone_number        = EXCLUDED.phone_number,
           role                = EXCLUDED.role,
           brand_id            = EXCLUDED.brand_id,
           can_manage_products = EXCLUDED.can_manage_products,
           can_manage_stops    = EXCLUDED.can_manage_stops,
           can_manage_orders   = EXCLUDED.can_manage_orders,
           can_manage_pickup   = EXCLUDED.can_manage_pickup,
           can_manage_messages = EXCLUDED.can_manage_messages,
           can_manage_refunds  = EXCLUDED.can_manage_refunds,
           can_manage_users    = EXCLUDED.can_manage_users,
           can_manage_water_log= EXCLUDED.can_manage_water_log,
           can_manage_reports  = EXCLUDED.can_manage_reports,
           active              = EXCLUDED.active,
           must_change_password= EXCLUDED.must_change_password,
           auth_provider       = EXCLUDED.auth_provider
         RETURNING id, user_id, display_name, email, phone_number, role, brand_id,
                   can_manage_products, can_manage_stops, can_manage_orders,
                   can_manage_pickup, can_manage_messages, can_manage_refunds,
                   can_manage_users, can_manage_water_log, can_manage_reports,
                   active, must_change_password, created_at, last_login`,
        [
          neonAuthUserId,
          displayName,
          email,
          input.phone_number ?? null,
          input.role,
          input.brand_id,
          f.can_manage_products ?? false,
          f.can_manage_stops ?? false,
          f.can_manage_orders ?? false,
          f.can_manage_pickup ?? false,
          f.can_manage_messages ?? false,
          f.can_manage_refunds ?? false,
          f.can_manage_users ?? false,
          f.can_manage_water_log ?? false,
          f.can_manage_reports ?? false,
          input.mustChangePassword ?? true,
          neonAuthUserId,
        ],
      );
      if (!ins.rows[0]) throw new Error("admin_users insert returned no row");

      if (input.brand_id) {
        await client.query(
          `INSERT INTO admin_user_brands (admin_user_id, brand_id)
           VALUES ($1, $2)
           ON CONFLICT (admin_user_id, brand_id) DO NOTHING`,
          [ins.rows[0].id, input.brand_id],
        );
      }
      return ins.rows[0];
    });
    insertedRow = result;
  } catch (err) {
    // We created the Neon Auth account but failed to insert the local
    // row. Don't roll back the auth user — the operator can re-link
    // it via the admin UI / DB. Surface the error.
    console.error(
      "[createAdminUser] Neon Auth user created (id=" + neonAuthUserId + ") but admin_users insert failed:",
      err,
    );
    return {
      user: null,
      error:
        `Neon Auth user was created but the local admin row failed: ${
          err instanceof Error ? err.message : String(err)
        }. The Neon Auth account is orphaned — link it manually via the admin_users table.`,
    };
  }

  // 4. Welcome email (best-effort).
  const emailResult = await sendWelcomeEmailSafe({
    to: email,
    name: displayName,
    role: input.role,
    password,
    brandId: input.brand_id ?? undefined,
  });

  return {
    user: mapUserRow(insertedRow),
    error: null,
    tempPassword: password,
    emailSent: emailResult.sent,
    emailError: emailResult.error,
    authPath,
  };
}

/**
 * Public sign-up + emailVerified = true fallback. Used when the
 * caller's Neon Auth session is not flagged as admin, or the admin
 * endpoint rejected the request for any reason.
 */
async function signupFallbackCreate(
  email: string,
  password: string,
  name: string,
): Promise<{ userId: string | null; error: string | null }> {
  const baseUrl = process.env.NEON_AUTH_BASE_URL;
  if (!baseUrl) {
    return {
      userId: null,
      error: "NEON_AUTH_BASE_URL is not set; cannot fall back to public sign-up.",
    };
  }

  try {
    const res = await fetch(`${baseUrl}/sign-up/email`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Origin": process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:4000",
        "x-neon-auth-proxy": "node",
      },
      body: JSON.stringify({
        email,
        password,
        name,
        callbackURL: "/admin",
      }),
    });
    const data = (await res.json().catch(() => ({}))) as {
      user?: { id?: string };
      code?: string;
      message?: string;
    };

    if (!res.ok) {
      if (data.code === "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL") {
        // Look up the existing user id so we can still link.
        const existing = await query<{ id: string }>(
          `SELECT id FROM neon_auth.user WHERE email = $1 LIMIT 1`,
          [email],
        );
        if (existing.rows[0]?.id) {
          return { userId: existing.rows[0].id, error: null };
        }
        return { userId: null, error: "User already exists but could not be looked up." };
      }
      return {
        userId: null,
        error: data.message ?? data.code ?? `Sign-up failed (HTTP ${res.status})`,
      };
    }

    const userId = data.user?.id;
    if (!userId) {
      return { userId: null, error: "Sign-up response missing user.id" };
    }

    // Flip emailVerified so the user can sign in without a verification step.
    // The platform admin already vetted this email — they're handing the
    // password to the user directly.
    try {
      await query(`UPDATE neon_auth.user SET "emailVerified" = true WHERE id = $1`, [userId]);
    } catch (verifyErr) {
      console.warn(
        "[createAdminUser] Failed to set emailVerified=true (non-fatal):",
        verifyErr,
      );
    }

    return { userId, error: null };
  } catch (err) {
    return {
      userId: null,
      error: err instanceof Error ? err.message : String(err),
    };
  }
}

export async function updateAdminUser(input: UpdateAdminUserInput): Promise<{ user: AdminUserRow | null; error: string | null }> {
  try {
    // Build a partial SET clause. Each `can_manage_*` column is set
    // individually — the input's `flags` partial is spread across them.
    const sets: string[] = [];
    const params: unknown[] = [];
    const push = (col: string, val: unknown) => { params.push(val); sets.push(`${col} = $${params.length}`); };

    if (input.role !== undefined) push("role", input.role);
    if (input.brand_id !== undefined) push("brand_id", input.brand_id);
    if (input.active !== undefined) push("active", input.active);
    if (input.display_name !== undefined) push("display_name", input.display_name);
    if (input.phone_number !== undefined) push("phone_number", input.phone_number);
    if (input.flags) {
      for (const [key, val] of Object.entries(input.flags)) {
        if (val !== undefined) push(key, val);
      }
    }
    if (sets.length === 0) return { user: null, error: "Nothing to update" };

    params.push(input.id);
    const sql = `UPDATE admin_users SET ${sets.join(", ")}
                 WHERE id = $${params.length}
                 RETURNING id, user_id, display_name, email, phone_number, role, brand_id,
                           can_manage_products, can_manage_stops, can_manage_orders,
                           can_manage_pickup, can_manage_messages, can_manage_refunds,
                           can_manage_users, can_manage_water_log, can_manage_reports,
                           active, must_change_password, created_at, last_login`;
    const { rows } = await query<Record<string, unknown>>(sql, params);
    if (!rows[0]) return { user: null, error: "User not found" };
    return { user: mapUserRow(rows[0]), error: null };
  } catch (err) {
    return { user: null, error: err instanceof Error ? err.message : String(err) };
  }
}

export async function deleteAdminUser(id: string): Promise<{ success: boolean; error: string | null }> {
  try {
    // No Supabase Auth — nothing to delete from the auth service.
    const { rowCount } = await query(`DELETE FROM admin_users WHERE id = $1`, [id]);
    return { success: (rowCount ?? 0) > 0, error: null };
  } catch (err) {
    return { success: false, error: err instanceof Error ? err.message : String(err) };
  }
}

export async function setMustChangePassword(userId: string): Promise<{ success: boolean; error: string | null }> {
  try {
    const { rowCount } = await query(
      `UPDATE admin_users SET must_change_password = true WHERE id = $1`,
      [userId],
    );
    return { success: (rowCount ?? 0) > 0, error: null };
  } catch (err) {
    return { success: false, error: err instanceof Error ? err.message : String(err) };
  }
}

/**
 * Sends a password-reset email to the user via Neon Auth.
 *
 * Authorization: platform_admin only. Brand-scoped admins cannot
 * trigger password resets for users outside their brand.
 *
 * The endpoint used (`requestPasswordReset`) is a public Neon Auth
 * endpoint — it does not require the caller to be the target user.
 * For the same reason the public /api/auth/forgot-password route
 * works, this will always succeed against the Neon Auth API; we
 * still return the result so the UI can show a clear success/failure
 * message.
 */
export async function sendPasswordResetEmail(
  email: string,
): Promise<{ success: boolean; error: string | null }> {
  try {
    // Authz: must be signed in as a platform_admin.
    const me = await getAdminUser();
    if (!me) {
      return { success: false, error: "Not authenticated." };
    }
    if (me.role !== "platform_admin") {
      return { success: false, error: "Only platform admins can send password resets." };
    }

    const trimmedEmail = email.trim().toLowerCase();
    if (!trimmedEmail) {
      return { success: false, error: "Email is required." };
    }

    const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:4000";

    const result = await neonAuthRequestPasswordReset({
      email: trimmedEmail,
      redirectTo: `${siteUrl}/reset-password`,
    });

    if (result?.error) {
      console.error("[admin/sendPasswordResetEmail] Neon Auth error:", result.error);
      return {
        success: false,
        error: result.error.message ?? result.error.code ?? "Failed to send reset email",
      };
    }

    return { success: true, error: null };
  } catch (err) {
    console.error("[admin/sendPasswordResetEmail] Unexpected error:", err);
    return {
      success: false,
      error: err instanceof Error ? err.message : String(err),
    };
  }
}

export async function getBrands(): Promise<{ brands: { id: string; name: string }[]; error: string | null }> {
  try {
    // Sort by name so the platform admin's brand picker stays stable.
    const { rows } = await query<{ id: string; name: string }>(
      `SELECT id, name FROM brands ORDER BY name`,
    );
    return { brands: rows, error: null };
  } catch (err) {
    return { brands: [], error: err instanceof Error ? err.message : String(err) };
  }
}
