import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
import {
  GoogleAuthProvider,
  createUserWithEmailAndPassword,
  onAuthStateChanged,
  signInWithEmailAndPassword,
  signInWithPopup,
  signInWithRedirect,
  signOut,
  type UserCredential,
  type User
} from 'firebase/auth';
import { auth } from '../lib/firebase';

interface AuthContextType {
  user: User | null;
  loading: boolean;
  codeVerified: boolean;
  verificationLoading: boolean;
  hasProfile: boolean;
  profileLoading: boolean;
  loginWithEmail: (email: string, password: string) => Promise<void>;
  registerWithEmail: (email: string, password: string) => Promise<UserCredential>;
  checkUsernameAvailability: (username: string) => Promise<boolean>;
  registerProfile: (params: { email: string; firebaseUid: string; username: string }) => Promise<void>;
  refreshProfileStatus: () => Promise<void>;
  loginWithGoogle: () => Promise<void>;
  sendLoginCode: () => Promise<void>;
  verifyLoginCode: (code: string) => Promise<void>;
  logout: () => Promise<void>;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);
const provider = new GoogleAuthProvider();
provider.setCustomParameters({ prompt: 'select_account' });
const VERIFICATION_STORAGE_KEY = 'auth_code_session_v1';
const ABSOLUTE_MAX_SESSION_MS = 24 * 60 * 60 * 1000;
const PASSWORD_PROVIDER_ID = 'password';
const GOOGLE_PROVIDER_ID = 'google.com';

const readStoredSession = (): { email: string; token: string; expiresAt?: number; createdAt?: number } | null => {
  const rawValue = localStorage.getItem(VERIFICATION_STORAGE_KEY);
  if (!rawValue) return null;
  try {
    const parsed = JSON.parse(rawValue) as { email?: string; token?: string; expiresAt?: number; createdAt?: number };
    if (!parsed.email || !parsed.token) return null;
    return { email: parsed.email, token: parsed.token, expiresAt: parsed.expiresAt, createdAt: parsed.createdAt };
  } catch {
    return null;
  }
};

export const AuthProvider = ({ children }: { children: ReactNode }) => {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  const [codeVerified, setCodeVerified] = useState(false);
  const [verificationLoading, setVerificationLoading] = useState(true);
  const [hasProfile, setHasProfile] = useState(false);
  const [profileLoading, setProfileLoading] = useState(true);

  const isGoogleUser = (nextUser: User | null) => nextUser?.providerData.some((entry) => entry.providerId === GOOGLE_PROVIDER_ID) ?? false;
  const usesPasswordAuth = (nextUser: User | null) =>
    nextUser?.providerData.some((entry) => entry.providerId === PASSWORD_PROVIDER_ID) ?? false;

  const revokeServerSession = async (email: string, token: string) => {
    await fetch('/api/auth/logout-session', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, token })
    }).catch(() => null);
  };

  const hardLogout = async () => {
    const storedSession = readStoredSession();
    const email = user?.email || storedSession?.email;
    if (email && storedSession?.token) {
      await revokeServerSession(email, storedSession.token);
    }
    await signOut(auth);
    localStorage.removeItem(VERIFICATION_STORAGE_KEY);
    setCodeVerified(false);
    setVerificationLoading(false);
    setHasProfile(false);
    setProfileLoading(false);
  };

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (nextUser) => {
      setUser(nextUser);
      setLoading(false);
    });
    return unsubscribe;
  }, []);

  useEffect(() => {
    const validateStoredSession = async () => {
      if (!user?.email) {
        localStorage.removeItem(VERIFICATION_STORAGE_KEY);
        setCodeVerified(false);
        setVerificationLoading(false);
        return;
      }

      // Google auth skips the email code challenge.
      if (isGoogleUser(user) && !usesPasswordAuth(user)) {
        localStorage.removeItem(VERIFICATION_STORAGE_KEY);
        setCodeVerified(true);
        setVerificationLoading(false);
        return;
      }

      const storedSession = readStoredSession();
      if (!storedSession || storedSession.email !== user.email.toLowerCase()) {
        setCodeVerified(false);
        setVerificationLoading(false);
        return;
      }

      const expiresAt = Number(storedSession.expiresAt || 0);
      const createdAt = Number(storedSession.createdAt || 0);
      const now = Date.now();
      const absoluteExpiry = createdAt > 0 ? createdAt + ABSOLUTE_MAX_SESSION_MS : 0;
      const effectiveExpiry = expiresAt > 0 && absoluteExpiry > 0 ? Math.min(expiresAt, absoluteExpiry) : 0;

      if (effectiveExpiry > 0 && now >= effectiveExpiry) {
        await hardLogout();
        return;
      }

      try {
        const response = await fetch('/api/auth/check-session', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ email: user.email, token: storedSession.token })
        });

        if (!response.ok) {
          localStorage.removeItem(VERIFICATION_STORAGE_KEY);
          await hardLogout();
          return;
        }

        setCodeVerified(true);
      } catch {
        setCodeVerified(false);
      } finally {
        setVerificationLoading(false);
      }
    };

    setVerificationLoading(true);
    void validateStoredSession();
  }, [user]);

  useEffect(() => {
    if (!user?.email) return;

    const interval = window.setInterval(() => {
      const storedSession = readStoredSession();
      if (!storedSession?.token || !storedSession.email || storedSession.email !== user.email?.toLowerCase()) {
        return;
      }

      const now = Date.now();
      const expiresAt = Number(storedSession.expiresAt || 0);
      const createdAt = Number(storedSession.createdAt || 0);
      const absoluteExpiry = createdAt > 0 ? createdAt + ABSOLUTE_MAX_SESSION_MS : 0;
      const effectiveExpiry = expiresAt > 0 && absoluteExpiry > 0 ? Math.min(expiresAt, absoluteExpiry) : 0;

      if (effectiveExpiry > 0 && now >= effectiveExpiry) {
        void hardLogout();
      }
    }, 30 * 1000);

    return () => window.clearInterval(interval);
  }, [user]);

  const refreshProfileStatus = async () => {
    if (isGoogleUser(user) && !usesPasswordAuth(user)) {
      // Google-only accounts bypass the email-code/profile lookup on auth DB.
      setHasProfile(true);
      setProfileLoading(false);
      return;
    }

    if (!user?.email || !codeVerified) {
      setHasProfile(false);
      setProfileLoading(false);
      return;
    }

    try {
      setProfileLoading(true);
      const response = await fetch(`/api/auth/profile-status?email=${encodeURIComponent(user.email)}`);
      const payload = (await response.json().catch(() => null)) as { hasProfile?: boolean } | null;
      if (!response.ok) {
        setHasProfile(false);
        return;
      }
      setHasProfile(payload?.hasProfile === true);
    } catch {
      setHasProfile(false);
    } finally {
      setProfileLoading(false);
    }
  };

  useEffect(() => {
    void refreshProfileStatus();
  }, [user, codeVerified]);

  const loginWithGoogle = async () => {
    try {
      await signInWithPopup(auth, provider);
    } catch (caught) {
      const errorCode =
        typeof caught === 'object' && caught !== null && 'code' in caught ? String((caught as { code?: string }).code) : '';

      // Fallback for browsers that block popups.
      if (errorCode === 'auth/popup-blocked') {
        await signInWithRedirect(auth, provider);
        return;
      }

      throw caught;
    }
  };

  const loginWithEmail = async (email: string, password: string) => {
    await signInWithEmailAndPassword(auth, email, password);
  };

  const registerWithEmail = async (email: string, password: string) => {
    return createUserWithEmailAndPassword(auth, email, password);
  };

  const checkUsernameAvailability = async (username: string) => {
    const response = await fetch(`/api/auth/username-available?username=${encodeURIComponent(username)}`);
    const payload = (await response.json().catch(() => null)) as { available?: boolean; message?: string } | null;

    if (!response.ok) {
      throw new Error(payload?.message || 'Unable to check username.');
    }

    return payload?.available === true;
  };

  const registerProfile = async (params: { email: string; firebaseUid: string; username: string }) => {
    const response = await fetch('/api/auth/register-profile', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(params)
    });

    if (!response.ok) {
      const payload = (await response.json().catch(() => null)) as { message?: string } | null;
      throw new Error(payload?.message || 'Unable to register profile.');
    }

    setHasProfile(true);
  };

  const sendLoginCode = async () => {
    if (!user?.email) {
      throw new Error('No authenticated user.');
    }
    if (isGoogleUser(user) && !usesPasswordAuth(user)) {
      throw new Error('Email code is only required for email/password accounts.');
    }

    const response = await fetch('/api/auth/send-code', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email: user.email })
    });

    if (!response.ok) {
      const payload = (await response.json().catch(() => null)) as { message?: string } | null;
      throw new Error(payload?.message || 'Unable to send code.');
    }
  };

  const verifyLoginCode = async (code: string) => {
    if (!user?.email) {
      throw new Error('No authenticated user.');
    }
    if (isGoogleUser(user) && !usesPasswordAuth(user)) {
      throw new Error('Email code is not required for Google accounts.');
    }

    const response = await fetch('/api/auth/verify-code', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email: user.email, code })
    });

    const payload = (await response.json().catch(() => null)) as { token?: string; message?: string; expiresAt?: number } | null;
    if (!response.ok || !payload?.token) {
      throw new Error(payload?.message || 'Unable to verify code.');
    }

    localStorage.setItem(
      VERIFICATION_STORAGE_KEY,
      JSON.stringify({
        email: user.email.toLowerCase(),
        token: payload.token,
        expiresAt: payload.expiresAt,
        createdAt: Date.now()
      })
    );
    setCodeVerified(true);
  };

  const logout = async () => {
    await hardLogout();
  };

  const value = useMemo(
    () => ({
      user,
      loading,
      codeVerified,
      verificationLoading,
      hasProfile,
      profileLoading,
      loginWithEmail,
      registerWithEmail,
      checkUsernameAvailability,
      registerProfile,
      refreshProfileStatus,
      loginWithGoogle,
      sendLoginCode,
      verifyLoginCode,
      logout
    }),
    [codeVerified, hasProfile, loading, profileLoading, user, verificationLoading]
  );

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
};
