import { createContext, useContext, useEffect, useState, useCallback, useMemo } from 'react'
import type { User, Session, AuthError, Factor } from '@supabase/supabase-js'
import { supabase } from '../lib/supabase'
import { api } from '../api/client'

interface UserProfile {
  id: string
  email: string
  displayName: string | null
  isAdmin: boolean
}

interface MfaState {
  requiresMfa: boolean
  factorId: string | null
  enrolledFactors: Factor[]
}

interface AuthState {
  user: User | null
  session: Session | null
  loading: boolean
  isAdmin: boolean
  mfa: MfaState
}

interface MfaEnrollResult {
  factorId: string
  qrCode: string
  secret: string
  recoveryCodes?: string[]
}

interface AuthContextValue extends AuthState {
  signInWithEmail: (
    email: string,
    password: string
  ) => Promise<{ error: AuthError | null; requiresMfa?: boolean }>
  signUpWithEmail: (email: string, password: string) => Promise<{ error: AuthError | null }>
  signInWithGoogle: () => Promise<{ error: AuthError | null }>
  signInWithDiscord: () => Promise<{ error: AuthError | null }>
  signOut: () => Promise<void>
  verifyMfa: (code: string) => Promise<{ error: AuthError | null }>
  enrollMfa: () => Promise<{ data: MfaEnrollResult | null; error: AuthError | null }>
  verifyMfaEnrollment: (factorId: string, code: string) => Promise<{ error: AuthError | null }>
  unenrollMfa: (factorId: string) => Promise<{ error: AuthError | null }>
  isOAuthUser: boolean
  refreshMfaState: () => Promise<void>
}

const AuthContext = createContext<AuthContextValue | null>(null)

async function syncUserProfile(session: Session | null): Promise<boolean> {
  if (!session) return false
  try {
    const profile = await api.post<UserProfile>('/users/me', {})
    return profile.isAdmin
  } catch {
    return false
  }
}

async function getMfaState(): Promise<MfaState> {
  const defaultState: MfaState = { requiresMfa: false, factorId: null, enrolledFactors: [] }
  try {
    const { data: aalData } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel()
    if (!aalData) return defaultState

    const { data: factorsData } = await supabase.auth.mfa.listFactors()
    const verifiedFactors = factorsData?.totp?.filter((f) => f.status === 'verified') ?? []

    const requiresMfa = aalData.currentLevel === 'aal1' && aalData.nextLevel === 'aal2'
    const factorId = requiresMfa ? (verifiedFactors[0]?.id ?? null) : null

    return { requiresMfa, factorId, enrolledFactors: verifiedFactors }
  } catch {
    return defaultState
  }
}

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [state, setState] = useState<AuthState>({
    user: null,
    session: null,
    loading: true,
    isAdmin: false,
    mfa: { requiresMfa: false, factorId: null, enrolledFactors: [] },
  })

  const refreshMfaState = useCallback(async () => {
    const mfa = await getMfaState()
    setState((prev) => ({ ...prev, mfa }))
  }, [])

  useEffect(() => {
    supabase.auth.getSession().then(async ({ data: { session } }) => {
      const [isAdmin, mfa] = await Promise.all([syncUserProfile(session), getMfaState()])
      setState({ user: session?.user ?? null, session, loading: false, isAdmin, mfa })
    })

    const {
      data: { subscription },
    } = supabase.auth.onAuthStateChange(async (_event, session) => {
      const [isAdmin, mfa] = await Promise.all([syncUserProfile(session), getMfaState()])
      setState({ user: session?.user ?? null, session, loading: false, isAdmin, mfa })
    })

    return () => subscription.unsubscribe()
  }, [])

  const signInWithEmail = useCallback(async (email: string, password: string) => {
    const { error } = await supabase.auth.signInWithPassword({ email, password })
    if (error) return { error }

    const mfa = await getMfaState()
    setState((prev) => ({ ...prev, mfa }))
    return { error: null, requiresMfa: mfa.requiresMfa }
  }, [])

  const signUpWithEmail = useCallback(async (email: string, password: string) => {
    const { error } = await supabase.auth.signUp({ email, password })
    return { error }
  }, [])

  const signInWithGoogle = useCallback(async () => {
    const { error } = await supabase.auth.signInWithOAuth({
      provider: 'google',
      options: { redirectTo: `${window.location.origin}/auth/callback` },
    })
    return { error }
  }, [])

  const signInWithDiscord = useCallback(async () => {
    const { error } = await supabase.auth.signInWithOAuth({
      provider: 'discord',
      options: { redirectTo: `${window.location.origin}/auth/callback` },
    })
    return { error }
  }, [])

  const signOut = useCallback(async () => {
    await supabase.auth.signOut()
  }, [])

  const verifyMfa = useCallback(
    async (code: string) => {
      const factorId = state.mfa.factorId
      if (!factorId) {
        return { error: { message: 'No MFA factor found' } as AuthError }
      }
      const { data: challengeData, error: challengeError } = await supabase.auth.mfa.challenge({
        factorId,
      })
      if (challengeError) return { error: challengeError }

      const { error } = await supabase.auth.mfa.verify({
        factorId,
        challengeId: challengeData.id,
        code,
      })
      if (!error) {
        const mfa = await getMfaState()
        setState((prev) => ({ ...prev, mfa }))
      }
      return { error }
    },
    [state.mfa.factorId]
  )

  const enrollMfa = useCallback(async () => {
    const { data, error } = await supabase.auth.mfa.enroll({ factorType: 'totp' })
    if (error || !data) {
      return { data: null, error }
    }
    return {
      data: {
        factorId: data.id,
        qrCode: data.totp.qr_code,
        secret: data.totp.secret,
      },
      error: null,
    }
  }, [])

  const verifyMfaEnrollment = useCallback(async (factorId: string, code: string) => {
    const { data: challengeData, error: challengeError } = await supabase.auth.mfa.challenge({
      factorId,
    })
    if (challengeError) return { error: challengeError }

    const { error } = await supabase.auth.mfa.verify({
      factorId,
      challengeId: challengeData.id,
      code,
    })
    if (!error) {
      const mfa = await getMfaState()
      setState((prev) => ({ ...prev, mfa }))
    }
    return { error }
  }, [])

  const unenrollMfa = useCallback(async (factorId: string) => {
    const { error } = await supabase.auth.mfa.unenroll({ factorId })
    if (!error) {
      const mfa = await getMfaState()
      setState((prev) => ({ ...prev, mfa }))
    }
    return { error }
  }, [])

  const isOAuthUser = useMemo(() => {
    if (!state.user) return false
    const provider = state.user.app_metadata?.['provider']
    return provider === 'google' || provider === 'discord'
  }, [state.user])

  const value = useMemo(
    () => ({
      ...state,
      signInWithEmail,
      signUpWithEmail,
      signInWithGoogle,
      signInWithDiscord,
      signOut,
      verifyMfa,
      enrollMfa,
      verifyMfaEnrollment,
      unenrollMfa,
      isOAuthUser,
      refreshMfaState,
    }),
    [
      state,
      signInWithEmail,
      signUpWithEmail,
      signInWithGoogle,
      signInWithDiscord,
      signOut,
      verifyMfa,
      enrollMfa,
      verifyMfaEnrollment,
      unenrollMfa,
      isOAuthUser,
      refreshMfaState,
    ]
  )

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

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