import type { User } from 'types';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useClient } from 'hooks/useClient';
import { useGlobal } from 'hooks/useGlobal';
import { usePasskeys } from './usePasskeys';

type UseAuthProps = { authUrl?: string; apiUrl?: string } | undefined;
export type UserWithToken = User & { token: string };

// POST /auth/signin — `token` is withheld when the account has passkeys, which routes the caller
// through the passkey challenge instead.
type SigninResponse = {
  token?: string;
  user: User & { passkeys: number };
};

// POST /auth/change-password — a fresh token, since changing the password invalidates the old one.
type TokenResponse = { token?: string };

export const useAuth = (props: UseAuthProps = {}) => {
  const { authUrl = '/api/auth', apiUrl = '/api' } = props;
  const queryClient = useQueryClient();
  const [user, setUser, refreshUser] = useGlobal<UserWithToken | null>('CURRENT_USER', null);
  const authClient = useClient(authUrl);
  const apiClient = useClient(apiUrl);
  const passKeyManager = usePasskeys();

  const { isLoading } = useQuery<UserWithToken | null>({
    queryKey: ['CURRENT_USER'],
    refetchOnMount: false,
    queryFn: async () => {
      const token =
        window.officerBearerToken ||
        document.body.dataset['officerBearerToken'] ||
        document.body.dataset['bearerToken'] ||
        new URL(window.location.href).searchParams.get('officerToken') ||
        localStorage.getItem('BEARER_TOKEN') ||
        sessionStorage.getItem('BEARER_TOKEN');
      if (!token) {
        setUser(null);
        return null;
      }

      try {
        const dbUser = (await authClient.get('/me')) as UserWithToken;
        setUser({ ...dbUser, token });
        return { ...dbUser, token } as UserWithToken;
      } catch (err) {
        return null;
      }
    },
  });

  const signin = async ({ email, password }: { email: string; password: string }) => {
    localStorage.removeItem('BEARER_TOKEN');
    const data = await authClient.post<SigninResponse>('/signin', { email, password });

    const { user, token } = data;

    if (!token && user.passkeys > 0) {
      await passKeyManager.signinWithPasskey(user);
      return;
    }

    // The server only withholds the token for the passkey flow handled above, so this should not
    // happen — but storing `undefined` would persist the literal string "undefined" as the token.
    if (!token) return;

    localStorage.setItem('BEARER_TOKEN', token);
    queryClient.invalidateQueries({ queryKey: ['CURRENT_USER'] });
    return token;
  };

  const signout = async () => {
    // Invalidate token on server (best-effort, don't block on failure)
    try {
      await authClient.post('/signout');
    } catch {
      // Token may already be invalid, continue with local cleanup
    }
    setUser(null);
    localStorage.removeItem('BEARER_TOKEN');
    localStorage.removeItem('CURRENT_USER');
  };

  const resetPassword = async (payload: ResetPasswordPayload) => {
    await authClient.post('/reset-password', payload);
  };

  const updateUser = async (payload: UpdateUserPayload) => {
    await apiClient.put('/users', payload);
    localStorage.removeItem('CURRENT_USER');
    refreshUser();
    queryClient.invalidateQueries({ queryKey: ['CURRENT_USER'] });
  };

  const changePassword = async (payload: ChangePasswordPayload) => {
    const data = await authClient.post<TokenResponse>('/change-password', payload);
    if (data.token) {
      localStorage.setItem('BEARER_TOKEN', data.token);
      queryClient.invalidateQueries({ queryKey: ['CURRENT_USER'] });
    }
  };

  const forgotPassword = async (payload: ForgotPasswordPayload) => {
    await authClient.post('/forgot-password', payload);
  };

  return {
    user,
    isAuthenticated: !!user,
    isLoading,
    refreshUser,
    signin,
    signout,
    resetPassword,
    updateUser,
    changePassword,
    forgotPassword,
    ...passKeyManager,
  };
};

export type UpdateUserPayload = {
  name: string;
  username?: string;
  avatar: string;
};

export type ResetPasswordPayload = {
  password: string;
  verificationCode: string;
};

export type ChangePasswordPayload = {
  password: string;
  newPassword: string;
  confirmPassword: string;
};

export type ForgotPasswordPayload = { email: string };
