import { randomBytes, createHash } from "crypto";
import { cookies } from "next/headers";
import { createClient } from "@supabase/supabase-js";
import { getServiceSupabase } from "@/lib/supabase/client";

const SESSION_COOKIE_NAME = "gantt_session";
const SESSION_HOURS_SHORT = 12;
const SESSION_DAYS_REMEMBER = 30;

export interface AuthUser {
  id: string;
  name: string;
  email: string;
  avatarUrl?: string;
  createdAt: string;
}

interface UserRow {
  id: string;
  name: string;
  email: string;
  avatar_url: string | null;
  created_at: string;
}

function getPublicSupabase() {
  const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL;
  const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY;
  if (!supabaseUrl || !supabaseAnonKey) {
    throw new Error("Missing Supabase public environment variables");
  }

  return createClient(supabaseUrl, supabaseAnonKey, {
    auth: {
      autoRefreshToken: false,
      persistSession: false,
    },
  });
}

function normalizeEmail(email: string): string {
  return email.trim().toLowerCase();
}

function normalizeAvatarUrl(value: string | null | undefined): string | undefined {
  if (value == null) return undefined;
  const trimmed = value.trim();
  if (!trimmed) return undefined;
  if (!trimmed.startsWith("data:image/")) {
    throw new Error("Avatar must be an image");
  }
  if (trimmed.length > 2_000_000) {
    throw new Error("Avatar image is too large");
  }
  return trimmed;
}

function fallbackNameFromEmail(email: string, preferredName?: string | null): string {
  const trimmed = (preferredName || "").trim();
  if (trimmed.length >= 2) return trimmed;
  const base = email.split("@")[0]?.trim() || "User";
  return base.length >= 2 ? base : "User";
}

function hashSessionToken(token: string): string {
  return createHash("sha256").update(token).digest("hex");
}

async function deleteExpiredSessions() {
  const supabase = getServiceSupabase();
  const { error } = await supabase
    .from("sessions")
    .delete()
    .lte("expires_at", new Date().toISOString());

  if (error) {
    console.error("Failed to delete expired sessions:", error);
  }
}

function mapUserRow(row: UserRow): AuthUser {
  return {
    id: row.id,
    name: row.name,
    email: row.email,
    avatarUrl: row.avatar_url ?? undefined,
    createdAt: row.created_at,
  };
}

async function upsertUserMirrors(params: {
  id: string;
  name: string;
  email: string;
  avatarUrl?: string | null;
}) {
  const supabase = getServiceSupabase();
  const { error: userError } = await supabase.from("users").upsert(
    {
      id: params.id,
      name: params.name,
      email: normalizeEmail(params.email),
      avatar_url: params.avatarUrl ?? null,
    },
    { onConflict: "id" }
  );

  if (userError) {
    throw new Error(userError.message || "Failed to sync user profile");
  }

  const { error: profileError } = await supabase.from("profiles").upsert(
    {
      id: params.id,
      name: params.name,
      avatar_url: params.avatarUrl ?? null,
      updated_at: new Date().toISOString(),
    },
    { onConflict: "id" }
  );

  if (profileError) {
    console.error("Failed to sync profiles mirror:", profileError.message);
  }
}

export async function registerUser(params: {
  name: string;
  email: string;
  password: string;
}): Promise<AuthUser> {
  await deleteExpiredSessions();

  const name = params.name.trim();
  const email = normalizeEmail(params.email);
  const password = params.password;

  if (name.length < 2) throw new Error("Name must be at least 2 characters");
  if (!email.includes("@")) throw new Error("Invalid email");
  if (password.length < 8) throw new Error("Password must be at least 8 characters");

  const supabase = getServiceSupabase();
  const { data, error } = await supabase.auth.admin.createUser({
    email,
    password,
    email_confirm: true,
    user_metadata: { name },
  });

  if (error || !data.user) {
    const message = error?.message || "Failed to create user";
    if (message.toLowerCase().includes("already")) {
      throw new Error("Email already exists");
    }
    throw new Error(message);
  }

  const authUser = data.user;
  const userName = fallbackNameFromEmail(email, name);
  await upsertUserMirrors({
    id: authUser.id,
    name: userName,
    email,
    avatarUrl: null,
  });

  const { data: userRow, error: userError } = await supabase
    .from("users")
    .select("id, name, email, avatar_url, created_at")
    .eq("id", authUser.id)
    .single();

  if (userError || !userRow) {
    throw new Error(userError?.message || "Failed to load user profile");
  }

  return mapUserRow(userRow as UserRow);
}

export async function authenticateUser(params: {
  email: string;
  password: string;
}): Promise<AuthUser | null> {
  await deleteExpiredSessions();

  const email = normalizeEmail(params.email);
  const authClient = getPublicSupabase();
  const { data: signInData, error: signInError } = await authClient.auth.signInWithPassword({
    email,
    password: params.password,
  });

  if (signInError || !signInData.user) return null;

  const supabase = getServiceSupabase();
  const authUser = signInData.user;

  const { data: existingRow } = await supabase
    .from("users")
    .select("id, name, email, avatar_url, created_at")
    .eq("id", authUser.id)
    .maybeSingle();

  const userName = fallbackNameFromEmail(
    authUser.email || email,
    typeof authUser.user_metadata?.name === "string" ? authUser.user_metadata.name : existingRow?.name
  );

  await upsertUserMirrors({
    id: authUser.id,
    name: userName,
    email: authUser.email || email,
    avatarUrl: existingRow?.avatar_url || null,
  });

  const { data: row } = await supabase
    .from("users")
    .select("id, name, email, avatar_url, created_at")
    .eq("id", authUser.id)
    .maybeSingle();

  if (!row) return null;

  return mapUserRow(row as UserRow);
}

export async function updateUserAccount(params: {
  userId: string;
  name?: string;
  email?: string;
  avatarUrl?: string | null;
  currentPassword?: string;
  newPassword?: string;
}): Promise<AuthUser> {
  await deleteExpiredSessions();

  const supabase = getServiceSupabase();

  // Get current user data
  const { data: row } = await supabase
    .from("users")
    .select("id, name, email, avatar_url, created_at")
    .eq("id", params.userId)
    .single();

  if (!row) throw new Error("User not found");

  const requestedName = typeof params.name === "string" ? params.name.trim() : row.name;
  const requestedEmail = typeof params.email === "string" ? normalizeEmail(params.email) : row.email;
  const hasAvatarInput = Object.prototype.hasOwnProperty.call(params, "avatarUrl");
  const requestedAvatar = hasAvatarInput ? normalizeAvatarUrl(params.avatarUrl) : row.avatar_url;
  const currentPassword = params.currentPassword || "";
  const newPassword = params.newPassword || "";

  if (requestedName.length < 2) throw new Error("Name must be at least 2 characters");
  if (!requestedEmail.includes("@")) throw new Error("Invalid email");
  if (newPassword && newPassword.length < 8) throw new Error("New password must be at least 8 characters");

  const emailChanged = requestedEmail !== row.email;
  const passwordChanged = newPassword.length > 0;
  const needsPasswordCheck = emailChanged || passwordChanged;

  if (needsPasswordCheck) {
    if (!currentPassword) throw new Error("Current password is required");
    const authClient = getPublicSupabase();
    const { error: authError } = await authClient.auth.signInWithPassword({
      email: row.email,
      password: currentPassword,
    });
    if (authError) {
      throw new Error("Current password is incorrect");
    }
  }

  if (emailChanged) {
    const { data: existing } = await supabase
      .from("users")
      .select("id")
      .eq("email", requestedEmail)
      .neq("id", row.id)
      .maybeSingle();

    if (existing) throw new Error("Email already exists");
  }

  if (emailChanged || passwordChanged || requestedName !== row.name) {
    const updatePayload: {
      email?: string;
      password?: string;
      user_metadata?: { name: string };
    } = {
      user_metadata: { name: requestedName },
    };

    if (emailChanged) updatePayload.email = requestedEmail;
    if (passwordChanged) updatePayload.password = newPassword;

    const { error: authUpdateError } = await supabase.auth.admin.updateUserById(row.id, updatePayload);
    if (authUpdateError) {
      const message = authUpdateError.message || "Failed to update account";
      if (message.toLowerCase().includes("already")) {
        throw new Error("Email already exists");
      }
      throw new Error(message);
    }
  }

  const { data: updated, error } = await supabase
    .from("users")
    .update({
      name: requestedName,
      email: requestedEmail,
      avatar_url: requestedAvatar ?? null,
    })
    .eq("id", row.id)
    .select("id, name, email, avatar_url, created_at")
    .single();

  if (error || !updated) {
    throw new Error(error?.message || "Failed to update user");
  }

  const { error: profileError } = await supabase.from("profiles").upsert(
    {
      id: row.id,
      name: requestedName,
      avatar_url: requestedAvatar ?? null,
      updated_at: new Date().toISOString(),
    },
    { onConflict: "id" }
  );

  if (profileError) {
    console.error("Failed to sync profiles mirror:", profileError.message);
  }

  return mapUserRow(updated as UserRow);
}

export async function listUsers(): Promise<AuthUser[]> {
  const supabase = getServiceSupabase();

  const { data: rows, error } = await supabase
    .from("users")
    .select("id, name, email, avatar_url, created_at")
    .order("name", { ascending: true });

  if (error) {
    console.error("Failed to list users:", error);
    return [];
  }

  return (rows || []).map(mapUserRow);
}

export async function createUserSession(
  userId: string,
  rememberMe: boolean
): Promise<{ token: string; expiresAt: string }> {
  await deleteExpiredSessions();

  const supabase = getServiceSupabase();
  const now = Date.now();
  const ttlMs = rememberMe
    ? SESSION_DAYS_REMEMBER * 24 * 60 * 60 * 1000
    : SESSION_HOURS_SHORT * 60 * 60 * 1000;

  const createdAt = new Date(now).toISOString();
  const expiresAt = new Date(now + ttlMs).toISOString();
  const token = randomBytes(32).toString("hex");
  const tokenHash = hashSessionToken(token);

  const { error } = await supabase.from("sessions").insert({
    user_id: userId,
    token_hash: tokenHash,
    created_at: createdAt,
    expires_at: expiresAt,
  });

  if (error) {
    throw new Error("Failed to create session");
  }

  return { token, expiresAt };
}

export async function revokeSession(token: string): Promise<void> {
  const supabase = getServiceSupabase();
  const tokenHash = hashSessionToken(token);

  await supabase.from("sessions").delete().eq("token_hash", tokenHash);
}

export async function getUserBySessionToken(token: string): Promise<AuthUser | null> {
  await deleteExpiredSessions();

  const supabase = getServiceSupabase();
  const tokenHash = hashSessionToken(token);
  const now = new Date().toISOString();

  const { data: row } = await supabase
    .from("sessions")
    .select("user_id, users(id, name, email, avatar_url, created_at)")
    .eq("token_hash", tokenHash)
    .gt("expires_at", now)
    .maybeSingle();

  if (!row || !row.users) return null;

  const user = Array.isArray(row.users) ? row.users[0] : row.users;
  return mapUserRow(user as UserRow);
}

export async function setSessionCookie(token: string, rememberMe: boolean): Promise<void> {
  const cookieStore = await cookies();
  const baseOptions = {
    httpOnly: true,
    sameSite: "lax" as const,
    secure: process.env.NODE_ENV === "production",
    path: "/",
  };

  if (rememberMe) {
    cookieStore.set(SESSION_COOKIE_NAME, token, {
      ...baseOptions,
      maxAge: SESSION_DAYS_REMEMBER * 24 * 60 * 60,
    });
    return;
  }

  // Session cookie (clears on browser close)
  cookieStore.set(SESSION_COOKIE_NAME, token, baseOptions);
}

export async function clearSessionCookie(): Promise<void> {
  const cookieStore = await cookies();
  cookieStore.set(SESSION_COOKIE_NAME, "", {
    httpOnly: true,
    sameSite: "lax" as const,
    secure: process.env.NODE_ENV === "production",
    path: "/",
    maxAge: 0,
  });
}

export async function getSessionTokenFromCookies(): Promise<string | null> {
  const cookieStore = await cookies();
  return cookieStore.get(SESSION_COOKIE_NAME)?.value ?? null;
}

export async function getAuthenticatedUser(): Promise<AuthUser | null> {
  const token = await getSessionTokenFromCookies();
  if (!token) return null;
  return getUserBySessionToken(token);
}
