import {
  createContext,
  type Dispatch,
  useContext,
  useEffect,
  useMemo,
  useState,
  type ReactNode,
  type SetStateAction,
} from 'react'
import {
  getCurrentUser,
  logoutCurrentUser,
  type ApiBootstrapUserPayload,
  type ReleaseManifestPayload,
} from './auth-api'
import {
  ensureSuperTokensInit,
  redirectToSuperTokensThirdPartyLogin,
  signInWithSuperTokens,
  signOutFromSuperTokens,
  signUpWithSuperTokens,
} from './supertokens-client'
import { validateStrongPassword } from './password-policy'
import { isSuperTokensConfigured } from './supertokens-runtime'

export type AuthMethod = 'supertokens' | 'email' | 'github' | 'google' | 'orcid'
export type ColorMode = 'dark' | 'light'
export type PlatformRole = 'viewer' | 'operator' | 'reviewer' | 'admin'

export interface PlatformUser {
  id: string
  name: string
  email: string
  authMethod: AuthMethod
  plan: 'free' | 'operator' | 'studio' | 'enterprise'
  role?: PlatformRole
  isAdmin?: boolean
  canDownload?: boolean
  billing?: {
    source: string
    accessStatus: string
    canDownload: boolean
    subscriptionId?: string | null
    customerId?: string | null
    transactionId?: string | null
    lastEventId?: string | null
    lastEventType?: string | null
    lastEventAt?: string | null
    updatedAt?: string | null
    statePath?: string | null
  }
}

export interface PlatformLoginInput {
  method: AuthMethod
  email: string
  password?: string
}

export interface PlatformRegisterInput {
  method: 'supertokens' | 'email'
  email: string
  password: string
  name?: string
}

export type ReleaseAuthorityViewer = ReleaseManifestPayload['manifest']['viewer']

interface PlatformAuthContextValue {
  user: PlatformUser | null
  isAuthenticated: boolean
  isLoading: boolean
  colorMode: ColorMode
  superTokensConfigured: boolean
  releaseAuthoritySyncItems: string[]
  login: (input: PlatformLoginInput) => Promise<{ ok: boolean; error?: string }>
  register: (input: PlatformRegisterInput) => Promise<{ ok: boolean; error?: string }>
  loginWithProvider: (provider: 'github' | 'google' | 'orcid') => Promise<void>
  logout: () => Promise<void>
  toggleColorMode: () => void
  reconcileReleaseAuthority: (viewer: ReleaseAuthorityViewer) => boolean
  invalidateSession: (reason?: string) => void
}

const AUTH_STORAGE_KEY = 'hypertwist.platform.user.v1'
const COLOR_MODE_STORAGE_KEY = 'hypertwist.platform.color-mode.v1'
const PlatformAuthContext = createContext<PlatformAuthContextValue | null>(null)
type PlatformUserSetter = Dispatch<SetStateAction<PlatformUser | null>>
type ReleaseAuthoritySyncSetter = Dispatch<SetStateAction<string[]>>

function normalizeEmail(value: unknown) {
  return String(value || '').trim().toLowerCase()
}

function deriveNameFromEmail(email: string) {
  const userPart = email.split('@')[0] || 'Operator'
  return userPart
    .replace(/[._-]+/g, ' ')
    .replace(/\b\w/g, (char) => char.toUpperCase())
    .trim()
}

function normalizePlan(value: unknown): PlatformUser['plan'] {
  const plan = String(value || '').trim().toLowerCase()
  if (plan === 'operator' || plan === 'studio' || plan === 'enterprise' || plan === 'free') {
    return plan
  }
  if (plan === 'student' || plan === 'team' || plan === 'group' || plan === 'scholar') {
    return 'operator'
  }
  return 'free'
}

function normalizeAuthMethod(value: unknown): AuthMethod | undefined {
  const method = String(value || '').trim().toLowerCase()
  if (
    method === 'supertokens' ||
    method === 'email' ||
    method === 'github' ||
    method === 'google' ||
    method === 'orcid'
  ) {
    return method
  }
  return undefined
}

function deriveBootstrapAuthMethod(payload: ApiBootstrapUserPayload, fallbackMethod: AuthMethod): AuthMethod {
  const explicitMethod = normalizeAuthMethod(payload.user?.auth_method)
  if (explicitMethod) {
    return explicitMethod
  }

  const mode = String(payload.mode || '').trim().toLowerCase()
  if (mode === 'supertokens') return 'supertokens'
  if (mode === 'github' || mode === 'google' || mode === 'orcid') return mode
  if (mode === 'email' || mode === 'email_fallback' || mode === 'local_fallback' || mode === 'local-fallback') {
    return 'email'
  }

  return fallbackMethod
}

function normalizeRole(value: unknown): PlatformRole | undefined {
  const role = String(value || '').trim().toLowerCase()
  if (role === 'viewer' || role === 'operator' || role === 'reviewer' || role === 'admin') {
    return role
  }
  return undefined
}

function readStoredUser(): PlatformUser | null {
  if (typeof window === 'undefined') return null
  try {
    const raw = window.localStorage.getItem(AUTH_STORAGE_KEY)
    if (!raw) return null
    const parsed = JSON.parse(raw) as PlatformUser
    if (!parsed?.id || !parsed?.email || !parsed?.authMethod) return null
    return parsed
  } catch {
    return null
  }
}

function writeStoredUser(user: PlatformUser | null) {
  if (typeof window === 'undefined') return
  if (!user) {
    window.localStorage.removeItem(AUTH_STORAGE_KEY)
    return
  }
  window.localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(user))
}

function readStoredColorMode(): ColorMode {
  if (typeof window === 'undefined') return 'dark'
  const stored = window.localStorage.getItem(COLOR_MODE_STORAGE_KEY)
  return stored === 'light' ? 'light' : 'dark'
}

function writeStoredColorMode(mode: ColorMode) {
  if (typeof window === 'undefined') return
  window.localStorage.setItem(COLOR_MODE_STORAGE_KEY, mode)
  document.documentElement.dataset.theme = mode
}

function persistSessionUser(
  setUser: PlatformUserSetter,
  setReleaseAuthoritySyncItems: ReleaseAuthoritySyncSetter,
  nextUser: PlatformUser | null,
) {
  setUser(nextUser)
  setReleaseAuthoritySyncItems([])
  writeStoredUser(nextUser)
}

function restoreStoredSession(
  setUser: PlatformUserSetter,
  setReleaseAuthoritySyncItems: ReleaseAuthoritySyncSetter,
) {
  setUser(readStoredUser())
  setReleaseAuthoritySyncItems([])
}

function clearSessionState(
  setUser: PlatformUserSetter,
  setReleaseAuthoritySyncItems: ReleaseAuthoritySyncSetter,
) {
  persistSessionUser(setUser, setReleaseAuthoritySyncItems, null)
}

function normalizeApiUser(input: ApiBootstrapUserPayload['user'] | undefined, fallbackMethod: AuthMethod): PlatformUser | null {
  if (!input) return null
  const id = String(input.id || '').trim()
  const email = normalizeEmail(input.email)
  if (!id || !email) return null
  const role = normalizeRole(input.role)
  return {
    id,
    email,
    name: String(input.name || '').trim() || deriveNameFromEmail(email),
    authMethod: normalizeAuthMethod(input.auth_method) || fallbackMethod,
    plan: normalizePlan(input.plan),
    role,
    isAdmin: input.is_admin === true || role === 'admin',
    canDownload: input.can_download === true,
    billing: input.billing ? {
      source: String(input.billing.source || 'session'),
      accessStatus: String(input.billing.access_status || 'session-default'),
      canDownload: input.billing.can_download === true,
      subscriptionId: input.billing.subscription_id || null,
      customerId: input.billing.customer_id || null,
      transactionId: input.billing.transaction_id || null,
      lastEventId: input.billing.last_event_id || null,
      lastEventType: input.billing.last_event_type || null,
      lastEventAt: input.billing.last_event_at || null,
      updatedAt: input.billing.updated_at || null,
      statePath: input.billing.state_path || null,
    } : undefined,
  }
}

function computeReleaseAuthoritySyncItems(user: PlatformUser, viewer: ReleaseAuthorityViewer) {
  const syncItems: string[] = []
  const localRole = user.role || 'operator'
  const viewerRole = normalizeRole(viewer.role) || localRole
  const localAccessStatus = user.billing?.accessStatus || 'session-default'
  const viewerAccessStatus = String(viewer.accessStatus || '').trim() || localAccessStatus

  if (viewer.plan && user.plan !== viewer.plan) {
    syncItems.push(`Plan changed from local ${user.plan} to live ${viewer.plan}.`)
  }

  if (localRole !== viewerRole) {
    syncItems.push(`Role changed from local ${localRole} to live ${viewerRole}.`)
  }

  if (Boolean(user.canDownload) !== viewer.canDownload) {
    syncItems.push(
      `Desktop download access changed from local ${user.canDownload ? 'enabled' : 'not entitled'} to live ${viewer.canDownload ? 'enabled' : 'not entitled'}.`,
    )
  }

  if (localAccessStatus !== viewerAccessStatus) {
    syncItems.push(`Access status changed from local ${localAccessStatus} to live ${viewerAccessStatus}.`)
  }

  return syncItems
}

function reconcileUserWithReleaseAuthority(user: PlatformUser | null, viewer: ReleaseAuthorityViewer) {
  if (!user || !viewer.authenticated) {
    return { nextUser: user, syncItems: [] as string[] }
  }

  const syncItems = computeReleaseAuthoritySyncItems(user, viewer)
  if (syncItems.length === 0) {
    return { nextUser: user, syncItems }
  }

  const nextRole = normalizeRole(viewer.role) || user.role
  const nextPlan = viewer.plan ? normalizePlan(viewer.plan) : user.plan
  const nextAccessStatus = String(viewer.accessStatus || '').trim() || user.billing?.accessStatus || 'session-default'
  const existingBilling = user.billing

  return {
    nextUser: {
      ...user,
      plan: nextPlan,
      role: nextRole,
      isAdmin: user.isAdmin === true || nextRole === 'admin',
      canDownload: viewer.canDownload,
      billing: {
        source: existingBilling?.source || 'session',
        accessStatus: nextAccessStatus,
        canDownload: viewer.canDownload,
        subscriptionId: existingBilling?.subscriptionId || null,
        customerId: existingBilling?.customerId || null,
        transactionId: existingBilling?.transactionId || null,
        lastEventId: existingBilling?.lastEventId || null,
        lastEventType: existingBilling?.lastEventType || null,
        lastEventAt: existingBilling?.lastEventAt || null,
        updatedAt: existingBilling?.updatedAt || null,
        statePath: existingBilling?.statePath || null,
      },
    },
    syncItems,
  }
}

function createLocalFallbackUser(email: string, password: string, name?: string): PlatformUser {
  const safeEmail = normalizeEmail(email)
  return {
    id: `local-${btoa(`${safeEmail}:${password.length}`)}`,
    email: safeEmail,
    name: String(name || '').trim() || deriveNameFromEmail(safeEmail),
    authMethod: 'email',
    plan: 'free',
    role: 'operator',
    canDownload: false,
    billing: {
      source: 'local-fallback',
      accessStatus: 'local-fallback',
      canDownload: false,
    },
  }
}

async function parseSignInError(response: { status?: string; formFields?: Array<{ error?: string }> }) {
  if (response.status === 'WRONG_CREDENTIALS_ERROR') return 'Incorrect email or password.'
  if (response.status === 'FIELD_ERROR') return response.formFields?.[0]?.error || 'Check the highlighted field.'
  return 'Unable to sign in.'
}

async function parseSignUpError(response: { status?: string; formFields?: Array<{ error?: string }> }) {
  if (response.status === 'FIELD_ERROR') return response.formFields?.[0]?.error || 'Check the highlighted field.'
  if (response.status === 'SIGN_UP_NOT_ALLOWED') return 'Sign-up is not allowed right now.'
  return 'Unable to create your account.'
}

function usePersistedColorMode(colorMode: ColorMode) {
  useEffect(() => {
    writeStoredColorMode(colorMode)
  }, [colorMode])
}

function useBootstrapPlatformSession({
  superTokensConfigured,
  setIsLoading,
  setUser,
  setReleaseAuthoritySyncItems,
}: {
  superTokensConfigured: boolean
  setIsLoading: Dispatch<SetStateAction<boolean>>
  setUser: PlatformUserSetter
  setReleaseAuthoritySyncItems: ReleaseAuthoritySyncSetter
}) {
  useEffect(() => {
    let cancelled = false

    async function bootstrap() {
      setIsLoading(true)
      if (superTokensConfigured) {
        ensureSuperTokensInit()
      }

      try {
        const response = await getCurrentUser()
        if (!response.ok) {
          if (!cancelled) {
            if (superTokensConfigured && response.status === 401) {
              clearSessionState(setUser, setReleaseAuthoritySyncItems)
            } else {
              restoreStoredSession(setUser, setReleaseAuthoritySyncItems)
            }
          }
          return
        }

        const payload = await response.json() as ApiBootstrapUserPayload
        const nextUser = normalizeApiUser(payload.user, deriveBootstrapAuthMethod(payload, 'supertokens'))
        if (!cancelled) {
          persistSessionUser(setUser, setReleaseAuthoritySyncItems, nextUser)
        }
      } catch {
        if (!cancelled) {
          restoreStoredSession(setUser, setReleaseAuthoritySyncItems)
        }
      } finally {
        if (!cancelled) {
          setIsLoading(false)
        }
      }
    }

    void bootstrap()

    return () => {
      cancelled = true
    }
  }, [setIsLoading, setReleaseAuthoritySyncItems, setUser, superTokensConfigured])
}

export function PlatformAuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<PlatformUser | null>(() => readStoredUser())
  const [isLoading, setIsLoading] = useState(true)
  const [colorMode, setColorMode] = useState<ColorMode>(() => readStoredColorMode())
  const [releaseAuthoritySyncItems, setReleaseAuthoritySyncItems] = useState<string[]>([])
  const superTokensConfigured = isSuperTokensConfigured()

  usePersistedColorMode(colorMode)
  useBootstrapPlatformSession({
    superTokensConfigured,
    setIsLoading,
    setUser,
    setReleaseAuthoritySyncItems,
  })

  const value = useMemo<PlatformAuthContextValue>(() => ({
    user,
    isAuthenticated: Boolean(user),
    isLoading,
    colorMode,
    superTokensConfigured,
    releaseAuthoritySyncItems,
    async login(input) {
      if (input.method === 'github' || input.method === 'google' || input.method === 'orcid') {
        if (!superTokensConfigured) {
          return { ok: false, error: 'Shared browser auth is not fully configured yet.' }
        }
        await redirectToSuperTokensThirdPartyLogin(input.method)
        return { ok: true }
      }

      const safeEmail = normalizeEmail(input.email)

      if (!safeEmail) {
        return { ok: false, error: 'Email is required.' }
      }

      if (!superTokensConfigured) {
        const localUser = createLocalFallbackUser(safeEmail, input.password || '')
        persistSessionUser(setUser, setReleaseAuthoritySyncItems, localUser)
        return { ok: true }
      }

      await ensureSuperTokensInit()
      const response = await signInWithSuperTokens({
        formFields: [
          { id: 'email', value: safeEmail },
          { id: 'password', value: String(input.password || '') },
        ],
      }) as { status?: string; formFields?: Array<{ error?: string }> }

      if (response.status !== 'OK') {
        return { ok: false, error: await parseSignInError(response) }
      }

      try {
        const bootstrapResponse = await getCurrentUser()
        if (!bootstrapResponse.ok) {
          return { ok: false, error: 'Signed in, but account bootstrap failed.' }
        }
        const payload = await bootstrapResponse.json() as ApiBootstrapUserPayload
        const nextUser = normalizeApiUser(payload.user, deriveBootstrapAuthMethod(payload, 'supertokens'))
        persistSessionUser(setUser, setReleaseAuthoritySyncItems, nextUser)
      } catch {
        return { ok: false, error: 'Signed in, but the account API is unavailable.' }
      }

      return { ok: true }
    },
    async register(input) {
      const safeEmail = normalizeEmail(input.email)
      const passwordError = validateStrongPassword(input.password)
      if (!safeEmail) {
        return { ok: false, error: 'Email is required.' }
      }
      if (passwordError) {
        return { ok: false, error: passwordError }
      }

      if (!superTokensConfigured) {
        const localUser = createLocalFallbackUser(safeEmail, input.password, input.name)
        persistSessionUser(setUser, setReleaseAuthoritySyncItems, localUser)
        return { ok: true }
      }

      await ensureSuperTokensInit()
      const response = await signUpWithSuperTokens({
        formFields: [
          { id: 'email', value: safeEmail },
          { id: 'password', value: input.password },
        ],
      }) as { status?: string; formFields?: Array<{ error?: string }> }

      if (response.status !== 'OK') {
        return { ok: false, error: await parseSignUpError(response) }
      }

      try {
        const bootstrapResponse = await getCurrentUser()
        if (!bootstrapResponse.ok) {
          return { ok: false, error: 'Account created, but bootstrap failed.' }
        }
        const payload = await bootstrapResponse.json() as ApiBootstrapUserPayload
        const nextUser = normalizeApiUser(payload.user, deriveBootstrapAuthMethod(payload, 'supertokens'))
        persistSessionUser(setUser, setReleaseAuthoritySyncItems, nextUser)
      } catch {
        return { ok: false, error: 'Account created, but the account API is unavailable.' }
      }

      return { ok: true }
    },
    async loginWithProvider(provider) {
      if (!superTokensConfigured) {
        return
      }
      await redirectToSuperTokensThirdPartyLogin(provider)
    },
    async logout() {
      clearSessionState(setUser, setReleaseAuthoritySyncItems)
      if (!superTokensConfigured) {
        return
      }
      try {
        await logoutCurrentUser()
      } catch {
        // Keep local logout deterministic even if the API is unavailable.
      }
      try {
        await signOutFromSuperTokens()
      } catch {
        // Session revocation can already have happened server-side.
      }
    },
    toggleColorMode() {
      setColorMode((current) => current === 'dark' ? 'light' : 'dark')
    },
    reconcileReleaseAuthority(viewer) {
      const reconciled = reconcileUserWithReleaseAuthority(user, viewer)
      if (reconciled.syncItems.length > 0 && reconciled.nextUser) {
        setUser(reconciled.nextUser)
        setReleaseAuthoritySyncItems(reconciled.syncItems)
        writeStoredUser(reconciled.nextUser)
        return true
      }

      return false
    },
    invalidateSession() {
      clearSessionState(setUser, setReleaseAuthoritySyncItems)
    },
  }), [colorMode, isLoading, releaseAuthoritySyncItems, superTokensConfigured, user])

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

export function usePlatformAuth() {
  const context = useContext(PlatformAuthContext)
  if (!context) {
    throw new Error('usePlatformAuth must be used within PlatformAuthProvider')
  }
  return context
}
