import { useCallback, useEffect, useState } from "react";
import { client } from "@/lib/api";
import { hasToken } from "@/lib/auth";
import {
  AuthTransientError,
  AuthUnauthorizedError,
  fetchMeDirect,
} from "@/lib/auth-api";

const AVATAR_BUCKET = "user-avatars";

export interface UserInfo {
  id?: string;
  name?: string;
  email?: string;
}

interface UseUserProfileResult {
  loading: boolean;
  authed: boolean;
  user: UserInfo | null;
  avatarUrl: string | null;
  avatarObjectKey: string | null;
  refresh: () => Promise<void>;
  uploadAvatar: (file: File) => Promise<void>;
  removeAvatar: () => Promise<void>;
}

async function resolveAvatarUrl(objectKey?: string | null): Promise<string | null> {
  if (!objectKey) return null;
  try {
    const res = await client.storage.getDownloadUrl({
      bucket_name: AVATAR_BUCKET,
      object_key: objectKey,
    });
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const url = (res as any)?.data?.download_url || (res as any)?.download_url;
    return url || null;
  } catch (err) {
    console.error("Failed to resolve avatar url", err);
    return null;
  }
}

async function findExistingAvatarKey(userId: string): Promise<string | null> {
  // The backend node can transiently 500 with "failed to get from node cache:
  // could not acquire callback lock: timeout" when the platform's internal
  // DNS/balancer resolver is still warming up. We retry a couple of times
  // with short backoff before giving up so the user's avatar isn't lost to a
  // cold-start race. On permanent failure we just return null — the caller
  // already treats that as "no avatar" and continues successfully.
  const MAX_ATTEMPTS = 3;
  const BACKOFF_MS = [300, 800];

  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
    try {
      const res = await client.storage.listObjects({ bucket_name: AVATAR_BUCKET });
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const objects: Array<{ object_key: string; last_modified?: string }> =
        (res as any)?.data?.objects || (res as any)?.objects || [];
      const prefix = `${userId}/`;
      const mine = objects
        .filter((o) => o.object_key && o.object_key.startsWith(prefix))
        .sort((a, b) => (b.last_modified || "").localeCompare(a.last_modified || ""));
      return mine[0]?.object_key || null;
    } catch (err) {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const anyErr: any = err;
      const status =
        anyErr?.status ??
        anyErr?.response?.status ??
        anyErr?.data?.status ??
        null;
      const message = String(
        anyErr?.message ?? anyErr?.data?.message ?? anyErr ?? ""
      ).toLowerCase();

      // Transient infra / DNS / node cache / 5xx — worth retrying.
      const isTransient =
        (typeof status === "number" && status >= 500) ||
        message.includes("failed the initial dns") ||
        message.includes("could not acquire callback lock") ||
        message.includes("node cache") ||
        message.includes("timeout") ||
        message.includes("network");

      if (isTransient && attempt < MAX_ATTEMPTS) {
        await new Promise((r) => setTimeout(r, BACKOFF_MS[attempt - 1] ?? 800));
        continue;
      }

      // Degrade gracefully — not a fatal error, the user can still use the app.
      console.debug("Avatar list unavailable, continuing without it:", err);
      return null;
    }
  }
  return null;
}

function getAvatarKeyFromStorage(userId: string): string | null {
  try {
    return localStorage.getItem(`avatar_key_${userId}`);
  } catch {
    return null;
  }
}

function setAvatarKeyInStorage(userId: string, key: string | null) {
  try {
    if (key) localStorage.setItem(`avatar_key_${userId}`, key);
    else localStorage.removeItem(`avatar_key_${userId}`);
  } catch {
    /* ignore */
  }
}

export function useUserProfile(): UseUserProfileResult {
  const [loading, setLoading] = useState(true);
  const [authed, setAuthed] = useState(false);
  const [user, setUser] = useState<UserInfo | null>(null);
  const [avatarObjectKey, setAvatarObjectKey] = useState<string | null>(null);
  const [avatarUrl, setAvatarUrl] = useState<string | null>(null);

  const loadProfile = useCallback(async () => {
    setLoading(true);

    // Fast path: if we have no token at all, we're definitely not authed.
    // This avoids firing a /auth/me request that would just 401.
    if (!hasToken()) {
      setAuthed(false);
      setUser(null);
      setAvatarObjectKey(null);
      setAvatarUrl(null);
      setLoading(false);
      return;
    }

    // We have a token — try /auth/me. Retry a couple of times on transient
    // errors (e.g. backend cold-start, 502/503/504, network blip) before
    // giving up. We use `fetchMeDirect` instead of `client.auth.me()` so that
    // a 401 NEVER triggers the web-sdk's automatic redirect to the Atoms
    // OIDC login page — we stay on /login, the way a standalone
    // email-and-password portal is supposed to.
    const MAX_ATTEMPTS = 3;
    const BACKOFF_MS = [1000, 2000];

    for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
      try {
        const me = await fetchMeDirect();
        setAuthed(true);
        setUser({ id: me.id, email: me.email, name: me.name });

        // Try localStorage first, then fall back to listing objects.
        let key = getAvatarKeyFromStorage(me.id);
        if (!key) {
          key = await findExistingAvatarKey(me.id);
          if (key) setAvatarKeyInStorage(me.id, key);
        }
        setAvatarObjectKey(key);
        setAvatarUrl(await resolveAvatarUrl(key));
        setLoading(false);
        return;
      } catch (err) {
        // 401 / malformed response — the stored token is bad. `fetchMeDirect`
        // has already cleared it; just drop to an unauthenticated state and
        // let the page decide whether to navigate to /login.
        if (err instanceof AuthUnauthorizedError) {
          setAuthed(false);
          setUser(null);
          setAvatarObjectKey(null);
          setAvatarUrl(null);
          setLoading(false);
          return;
        }

        // Transient 5xx / network error — retry with backoff before giving up.
        if (err instanceof AuthTransientError && attempt < MAX_ATTEMPTS) {
          await new Promise((r) =>
            setTimeout(r, BACKOFF_MS[attempt - 1] ?? 2000),
          );
          continue;
        }

        console.error("useUserProfile: failed to load", err);
        setAuthed(false);
        setUser(null);
        setAvatarObjectKey(null);
        setAvatarUrl(null);
        setLoading(false);
        return;
      }
    }
  }, []);

  useEffect(() => {
    loadProfile();
  }, [loadProfile]);

  const uploadAvatar = useCallback(
    async (file: File) => {
      if (!authed || !user?.id) throw new Error("You must be signed in to upload an avatar.");
      const ext = (file.name.split(".").pop() || "png").toLowerCase();
      const objectKey = `${user.id}/avatar-${Date.now()}.${ext}`;

      const uploadRes = await client.storage.getUploadUrl({
        bucket_name: AVATAR_BUCKET,
        object_key: objectKey,
      });
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const uploadUrl = (uploadRes as any)?.data?.upload_url || (uploadRes as any)?.upload_url;
      if (!uploadUrl) throw new Error("Could not obtain upload URL.");

      const putRes = await fetch(uploadUrl, {
        method: "PUT",
        headers: { "Content-Type": file.type || "application/octet-stream" },
        body: file,
      });
      if (!putRes.ok) throw new Error(`Upload failed with status ${putRes.status}`);

      setAvatarKeyInStorage(user.id, objectKey);
      setAvatarObjectKey(objectKey);
      setAvatarUrl(await resolveAvatarUrl(objectKey));
    },
    [authed, user]
  );

  const removeAvatar = useCallback(async () => {
    if (!user?.id) return;
    setAvatarKeyInStorage(user.id, null);
    setAvatarObjectKey(null);
    setAvatarUrl(null);
  }, [user]);

  return {
    loading,
    authed,
    user,
    avatarUrl,
    avatarObjectKey,
    refresh: loadProfile,
    uploadAvatar,
    removeAvatar,
  };
}