import {
  createContext,
  useContext,
  useEffect,
  useState,
  type ReactNode,
} from 'react'
import {
  browserLocalPersistence,
  onAuthStateChanged,
  setPersistence,
  signInWithPopup,
  signInWithRedirect,
  getRedirectResult,
  signOut as firebaseSignOut,
  type User,
} from 'firebase/auth'
import { auth, googleProvider } from '../lib/firebase'
import { registerUser, getUserByEmail } from '../lib/api'
import {
  deriveSecretKey,
  generateDeviceKey,
  generateSalt,
  getSalt,
  saveSalt,
  getDeviceKey,
  saveDeviceKey,
  encryptSecretKey,
  decryptSecretKey,
  saveEncryptedSecretKey,
  getEncryptedSecretKey,
} from '../lib/crypto'
import { REMINDER_TIME_KEY, REMINDER_ENABLED_KEY } from '../hooks/useReminder'

type MongoUser = {
  id: string
  email: string
  displayName?: string
  photoURL?: string
  theme?: string
  tutorial?: boolean
  backgroundImage?: string | null
  backgroundImages?: string[]
  reminder?: {
    enabled: boolean
    time?: string
    timezone?: string
  }
}

type AuthContextValue = {
  user: User | null
  userId: string | null
  mongoUser: MongoUser | null
  loading: boolean
  secretKey: Uint8Array | null
  authError: string | null
  signInWithGoogle: () => Promise<void>
  signOut: () => Promise<void>
  refreshMongoUser: () => Promise<void>
}

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

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null)
  const [userId, setUserId] = useState<string | null>(null)
  const [mongoUser, setMongoUser] = useState<MongoUser | null>(null)
  const [secretKey, setSecretKey] = useState<Uint8Array | null>(null)
  const [loading, setLoading] = useState(true)
  const [authError, setAuthError] = useState<string | null>(null)

  // Apply custom background image whenever mongoUser changes
  useEffect(() => {
    const bg = mongoUser?.backgroundImage
    if (bg) {
      document.body.style.backgroundImage = `url(${bg})`
      document.body.style.backgroundSize = 'cover'
      document.body.style.backgroundPosition = 'center'
      document.body.style.backgroundAttachment = 'fixed'
      document.body.classList.add('gj-has-bg')
    } else {
      document.body.style.backgroundImage = ''
      document.body.classList.remove('gj-has-bg')
    }
  }, [mongoUser?.backgroundImage])

  // Initialize encryption keys on login
  async function initializeEncryption(authUser: User) {
    try {
      const firebaseUID = authUser.uid

      // Get or create salt
      let salt = getSalt()
      if (!salt) {
        salt = generateSalt()
        saveSalt(salt)
      }

      // Derive master key from Firebase UID (stable across sessions)
      const derivedKey = await deriveSecretKey(firebaseUID, salt)

      // Check if device key exists
      let deviceKey = await getDeviceKey()
      if (!deviceKey) {
        // First login on this device: generate device key
        deviceKey = await generateDeviceKey()
        await saveDeviceKey(deviceKey)
      }

      // Check if encrypted key exists in IndexedDB
      const cachedEncrypted = await getEncryptedSecretKey()
      if (!cachedEncrypted) {
        // First login (or IndexedDB cleared): encrypt and cache the key
        const encrypted = await encryptSecretKey(derivedKey, deviceKey)
        await saveEncryptedSecretKey(encrypted.ciphertext, encrypted.nonce)
      } else {
        // Subsequent login on same device: verify we can decrypt
        // (This ensures device key is correct)
        try {
          await decryptSecretKey(
            cachedEncrypted.ciphertext,
            cachedEncrypted.nonce,
            deviceKey
          )
        } catch (error) {
          console.warn('Device key mismatch, regenerating...', error)
          // Device key doesn't match - regenerate
          deviceKey = await generateDeviceKey()
          await saveDeviceKey(deviceKey)
          const encrypted = await encryptSecretKey(derivedKey, deviceKey)
          await saveEncryptedSecretKey(encrypted.ciphertext, encrypted.nonce)
        }
      }

      // Keep secret key in memory for session
      setSecretKey(derivedKey)
    } catch (error) {
      console.error('Error initializing encryption:', error)
      throw error
    }
  }

  function syncReminderFromDb(mongoUser: MongoUser) {
    const r = mongoUser.reminder
    if (r) {
      localStorage.setItem(REMINDER_ENABLED_KEY, r.enabled ? 'true' : 'false')
      if (r.time) localStorage.setItem(REMINDER_TIME_KEY, r.time)
      else localStorage.removeItem(REMINDER_TIME_KEY)
    } else {
      localStorage.setItem(REMINDER_ENABLED_KEY, 'false')
      localStorage.removeItem(REMINDER_TIME_KEY)
    }
  }

  // Register or fetch user from MongoDB
  async function syncUserWithDatabase(authUser: User) {
    try {
      const token = await authUser.getIdToken()
      const email = authUser.email!

      // Initialize encryption before syncing user
      await initializeEncryption(authUser)

      // Try to get existing user
      try {
        // console.log('[Auth] Fetching user by email:', email)
        const existingUser = await getUserByEmail(email, token) as MongoUser
        setUserId(existingUser.id)
        setMongoUser(existingUser)
        syncReminderFromDb(existingUser)
      } catch (error) {
        console.warn('[Auth] User not found, registering...', error)
        const newUser = await registerUser(
          {
            email,
            displayName: authUser.displayName || undefined,
            photoURL: authUser.photoURL || undefined,
          },
          token
        ) as MongoUser
        // console.log('[Auth] Registered new user:', newUser.id)
        setUserId(newUser.id)
        setMongoUser(newUser)
        syncReminderFromDb(newUser)
      }
    } catch (error) {
      console.error('[Auth] Error syncing user with database:', error)
      throw error
    }
  }

  useEffect(() => {
    // Handle returning from a redirect sign-in (Safari / iOS / Android WebViews)
    getRedirectResult(auth).catch((error) => {
      console.error('[Auth] Redirect sign-in error:', error)
      setAuthError(error instanceof Error ? error.message : 'Sign-in failed')
    })
    // onAuthStateChanged below handles the successful redirect result automatically

    const unsubscribe = onAuthStateChanged(auth, async (u) => {
      setUser(u)
      if (u) {
        try {
          await syncUserWithDatabase(u)
        } catch (error) {
          console.error('Auth sync failed:', error)
        }
      } else {
        setUserId(null)
        setMongoUser(null)
        setSecretKey(null)
      }
      setLoading(false)
    })
    return () => unsubscribe()
  }, [])

  async function signInWithGoogle() {
    setAuthError(null)
    await setPersistence(auth, browserLocalPersistence)

    // Safari blocks cross-origin storage in popups (ITP), so use redirect flow
    const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
    if (isSafari) {
      await signInWithRedirect(auth, googleProvider)
      return
    }

    try {
      await signInWithPopup(auth, googleProvider)
    } catch (err: unknown) {
      const code = (err as { code?: string })?.code
      if (code === 'auth/popup-blocked') {
        // Popup was blocked (common on iOS Safari / Android WebViews) — fall back to redirect
        await signInWithRedirect(auth, googleProvider)
      } else {
        throw err
      }
    }
  }

  async function refreshMongoUser() {
    if (!user) return
    try {
      const token = await user.getIdToken()
      const email = user.email!
      const updated = await getUserByEmail(email, token) as MongoUser
      setMongoUser(updated)
    } catch (error) {
      console.error('[Auth] Error refreshing mongo user:', error)
    }
  }

  async function signOut() {
    setSecretKey(null)
    setMongoUser(null)
    localStorage.removeItem('gj-tour-pending-step')
    localStorage.removeItem(REMINDER_TIME_KEY)
    localStorage.removeItem(REMINDER_ENABLED_KEY)
    await firebaseSignOut(auth)
    setUserId(null)
  }

  const value: AuthContextValue = {
    user,
    userId,
    mongoUser,
    secretKey,
    loading,
    authError,
    signInWithGoogle,
    signOut,
    refreshMongoUser,
  }

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

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