import { createContext, useContext, useState, useEffect, useRef, useMemo, useCallback } from 'react'
import type { Session } from '@supabase/supabase-js'
import { Stack } from '@junwon/aesthetics'
import { getServerAddress } from '@junwon/config'
import { getDatabase } from '../hooks/useDatabase'

export interface Response {
  success: boolean
  error?: Error
  data?: {
    session?: Session
  }
}

interface Profile {
  email: string | null
}

type MembershipState = 'loading' | 'active' | 'inactive'

interface VisaContextType {
  profile: Profile
  membershipState: MembershipState
  emailToVerify: string | null
  isSignedIn: boolean
  loading: boolean
  signIn: (email: string, redirectUrl: string) => Promise<Response>
  signOut: () => Promise<Response>
  verifyOtp: (
    token: string,
    type: 'email' | 'email_change',
    redirectTo: string
  ) => Promise<Response>
  updateAccount: (updates: Partial<Profile>, redirectUrl: string) => Promise<Response>
  deleteAccount: () => Promise<Response>
  getStripePortalLink: (returnUrl: string) => Promise<string>
  getSession: () => Promise<Session | null>
}

const VisaContext = createContext<VisaContextType | null>(null)

export const useVisa = () => {
  const context = useContext(VisaContext)
  if (!context) {
    throw new Error('useVisa must be used within a VisaProvider')
  }
  return context
}

const getAuthHeaders = async (database: any) => {
  const {
    data: { session },
  } = await database.current.auth.getSession()
  if (!session) throw new Error('No session found')

  return {
    Authorization: `Bearer ${session.access_token}`,
    'Refresh-Token': session.refresh_token,
    'Content-Type': 'application/json',
  }
}

export function VisaProvider({ children }: { children: React.ReactNode }) {
  const database = useRef(getDatabase())
  const serverAddress = useMemo(() => getServerAddress(), [])

  const [profile, setProfile] = useState<Profile>({ email: null })
  const [membershipState, setMembershipState] = useState<MembershipState>('loading')

  const [loading, setLoading] = useState(true)
  const [isSignedIn, setIsSignedIn] = useState(false)
  const [emailToVerify, setEmailToVerify] = useState<string | null>(null)

  useEffect(() => {
    let isMounted = true

    database.current.auth.getSession().then(({ data: { session } }) => {
      if (isMounted) {
        setProfile({
          email: session?.user?.email ?? null,
        })
        setIsSignedIn(!!session?.user)
        setLoading(false)
      }
    })

    const { data: authListener } = database.current.auth.onAuthStateChange((event, session) => {
      if (!isMounted) return
      setProfile({ email: session?.user?.email ?? null })
      setIsSignedIn(!!session?.user)
    })

    return () => {
      isMounted = false
      authListener.subscription.unsubscribe()
    }
  }, [])

  useEffect(() => {
    let isMounted = true

    const checkMembership = async () => {
      setMembershipState('loading')
      try {
        const hasActiveMembership = await getMembershipState()
        if (isMounted) {
          setMembershipState(hasActiveMembership ? 'active' : 'inactive')
        }
      } catch (error) {
        console.log('Error checking membership:', error)
        if (isMounted) {
          setMembershipState('loading')
        }
      }
    }

    if (isSignedIn && profile.email) {
      checkMembership()
    }

    return () => {
      isMounted = false
    }
  }, [profile])

  const signIn = useCallback(
    async (email: string, redirectUrl: string): Promise<Response> => {
      try {
        const response = await fetch(`${serverAddress}/visa/signin`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ email, redirect_url: redirectUrl }),
        })

        if (!response.ok) throw new Error('Sign in failed')
        setEmailToVerify(email)
        return { success: true }
      } catch (error) {
        console.log('Sign in error:', error)
        return { success: false }
      }
    },
    [serverAddress]
  )

  const verifyOtp = useCallback(
    async (
      token: string,
      type: 'email' | 'email_change',
      redirectTo: string
    ): Promise<Response> => {
      if (!emailToVerify) {
        return {
          success: false,
          error: new Error('No email provided'),
        }
      }

      try {
        setLoading(true)
        const response = await fetch(`${serverAddress}/visa/verifyOtp`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            email: emailToVerify,
            token,
            type,
            redirect_to: redirectTo,
          }),
        })
        setLoading(false)

        if (!response.ok) throw new Error('Verification failed')
        const data = await response.json()

        await database.current.auth.setSession({
          access_token: data.session.access_token,
          refresh_token: data.session.refresh_token,
        })

        setEmailToVerify(null)
        return {
          success: true,
          data: { session: data.session },
        }
      } catch (error) {
        console.log('Verification error:', error)
        return {
          success: false,
          error: new Error('Verification failed'),
        }
      }
    },
    [emailToVerify, serverAddress]
  )

  const signOut = useCallback(async (): Promise<Response> => {
    try {
      await database.current.auth.signOut()
      setEmailToVerify(null)
      setIsSignedIn(false)
      return { success: true }
    } catch (error) {
      console.log('Sign out error:', error)
      return { success: false }
    }
  }, [])

  const updateAccount = useCallback(
    async (updates: Partial<Profile>, redirectUrl: string): Promise<Response> => {
      try {
        const headers = await getAuthHeaders(database)

        if (updates.email) {
          const response = await fetch(`${serverAddress}/visa`, {
            method: 'PUT',
            headers,
            body: JSON.stringify({
              email: updates.email,
              redirectTo: redirectUrl,
            }),
          })

          if (!response.ok) throw new Error('Failed to update account')
          setEmailToVerify(updates.email)
          return { success: true }
        }
        return { success: false, error: new Error('No updates provided') }
      } catch (error) {
        console.log('Update account error:', error)
        return {
          success: false,
          error: new Error('Failed to update account'),
        }
      }
    },
    [serverAddress]
  )

  const deleteAccount = useCallback(async (): Promise<Response> => {
    try {
      const headers = await getAuthHeaders(database)

      const response = await fetch(`${serverAddress}/visa`, {
        method: 'DELETE',
        headers,
      })

      if (!response.ok) throw new Error('Failed to delete account')

      await database.current.auth.signOut()
      setEmailToVerify(null)
      setIsSignedIn(false)

      return { success: true }
    } catch (error) {
      console.log('Delete account error:', error)
      return {
        success: false,
        error: new Error('Failed to delete account'),
      }
    }
  }, [serverAddress])

  const getMembershipState = useCallback(async (): Promise<boolean> => {
    try {
      const headers = await getAuthHeaders(database)
      const response = await fetch(`${serverAddress}/visa/membership/state`, {
        method: 'POST',
        headers,
      })

      if (!response.ok) {
        throw new Error('Failed to get membership state')
      }

      const data = await response.json()
      return data.hasActiveMembership
    } catch (error) {
      console.log('Error getting membership state:', error)
      throw error
    }
  }, [serverAddress])

  const getStripePortalLink = useCallback(
    async (returnUrl: string): Promise<string> => {
      try {
        const headers = await getAuthHeaders(database)
        const response = await fetch(`${serverAddress}/visa/membership/portal`, {
          method: 'POST',
          headers,
          body: JSON.stringify({ return_url: returnUrl }),
        })

        if (!response.ok) {
          const errorData = await response.json()
          throw new Error(errorData.detail || 'Failed to get Stripe portal link')
        }

        const data = await response.json()
        return data.portalLink
      } catch (error) {
        console.log('Error getting Stripe portal link:', error)
        throw error
      }
    },
    [serverAddress]
  )

  const getSession = useCallback(async () => {
    const {
      data: { session },
    } = await database.current.auth.getSession()
    return session
  }, [])

  const value = useMemo(
    () => ({
      profile,
      emailToVerify,
      isSignedIn,
      loading,
      signIn,
      signOut,
      verifyOtp,
      updateAccount,
      deleteAccount,
      getStripePortalLink,
      getSession,
      membershipState,
    }),
    [
      profile,
      emailToVerify,
      isSignedIn,
      loading,
      signIn,
      signOut,
      verifyOtp,
      updateAccount,
      deleteAccount,
      getStripePortalLink,
      getSession,
      membershipState,
    ]
  )

  return (
    <VisaContext.Provider value={value}>
      <Stack f={1}>{children}</Stack>
    </VisaContext.Provider>
  )
}
