import { beforeEach, describe, expect, it, vi } from 'vitest'
import { cleanup, render, screen, waitFor } from '@testing-library/react'
import { useEffect, type ReactNode } from 'react'

const mockGetCurrentUser = vi.fn()
const mockLogoutCurrentUser = vi.fn()
const mockEnsureSuperTokensInit = vi.fn()
const mockIsSuperTokensConfigured = vi.fn(() => true)

vi.mock('../auth/auth-api', () => ({
  getCurrentUser: (...args: unknown[]) => mockGetCurrentUser(...args),
  logoutCurrentUser: (...args: unknown[]) => mockLogoutCurrentUser(...args),
}))

vi.mock('../auth/supertokens-client', () => ({
  ensureSuperTokensInit: (...args: unknown[]) => mockEnsureSuperTokensInit(...args),
  signInWithSuperTokens: vi.fn(),
  signUpWithSuperTokens: vi.fn(),
  redirectToSuperTokensThirdPartyLogin: vi.fn(),
  signOutFromSuperTokens: vi.fn(),
}))

vi.mock('../auth/supertokens-runtime', () => ({
  isSuperTokensConfigured: () => mockIsSuperTokensConfigured(),
}))

import { PlatformAuthProvider, usePlatformAuth } from '../auth/platform-auth'

const AUTH_STORAGE_KEY = 'hypertwist.platform.user.v1'

function AuthProbe() {
  const { user, isLoading, releaseAuthoritySyncItems } = usePlatformAuth()

  return (
    <div>
      <div data-testid="loading">{String(isLoading)}</div>
      <div data-testid="user-id">{user?.id ?? 'none'}</div>
      <div data-testid="auth-method">{user?.authMethod ?? 'none'}</div>
      <div data-testid="plan">{user?.plan ?? 'none'}</div>
      <div data-testid="role">{user?.role ?? 'none'}</div>
      <div data-testid="can-download">{String(user?.canDownload ?? false)}</div>
      <div data-testid="billing-source">{user?.billing?.source ?? 'none'}</div>
      <div data-testid="sync-items">{releaseAuthoritySyncItems.join(' | ') || 'none'}</div>
    </div>
  )
}

function ReleaseAuthorityReconcileProbe({
  viewer,
}: {
  viewer: {
    authenticated: boolean
    canDownload: boolean
    plan: string | null
    role: string | null
    accessStatus: string | null
  }
}) {
  const { isLoading, reconcileReleaseAuthority } = usePlatformAuth()

  useEffect(() => {
    if (!isLoading) {
      reconcileReleaseAuthority(viewer)
    }
  }, [isLoading, reconcileReleaseAuthority, viewer])

  return <AuthProbe />
}

function renderWithProvider(children: ReactNode) {
  return render(<PlatformAuthProvider>{children}</PlatformAuthProvider>)
}

function createJsonResponse(status: number, body: unknown) {
  return {
    ok: status >= 200 && status < 300,
    status,
    json: async () => body,
  }
}

function seedStoredUser(user: Record<string, unknown>) {
  window.localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(user))
}

describe('PlatformAuthProvider bootstrap', () => {
  beforeEach(() => {
    cleanup()
    window.localStorage.clear()
    document.documentElement.removeAttribute('data-theme')
    mockGetCurrentUser.mockReset()
    mockLogoutCurrentUser.mockReset()
    mockEnsureSuperTokensInit.mockReset()
    mockIsSuperTokensConfigured.mockReset()
    mockIsSuperTokensConfigured.mockReturnValue(true)
  })

  it('preserves email fallback auth method when bootstrap mode is email_fallback', async () => {
    seedStoredUser({
      id: 'fallback-1',
      email: 'founder@hypertwist.app',
      name: 'Founder',
      authMethod: 'email',
      plan: 'free',
      role: 'operator',
      canDownload: false,
      billing: {
        source: 'local-fallback',
        accessStatus: 'local-fallback',
        canDownload: false,
      },
    })

    mockGetCurrentUser.mockResolvedValue(
      createJsonResponse(200, {
        mode: 'email_fallback',
        user: {
          id: 'fallback-1',
          email: 'founder@hypertwist.app',
          name: 'Founder',
          plan: 'operator',
          role: 'operator',
          can_download: true,
          billing: {
            source: 'local-fallback',
            access_status: 'local-fallback',
            can_download: false,
          },
        },
      }),
    )

    renderWithProvider(<AuthProbe />)

    await waitFor(() => {
      expect(screen.getByTestId('plan').textContent).toBe('operator')
    })

    expect(screen.getByTestId('auth-method').textContent).toBe('email')
    expect(screen.getByTestId('can-download').textContent).toBe('true')
    expect(screen.getByTestId('billing-source').textContent).toBe('local-fallback')
    expect(mockEnsureSuperTokensInit).toHaveBeenCalledTimes(1)

    expect(JSON.parse(window.localStorage.getItem(AUTH_STORAGE_KEY) || '{}')).toMatchObject({
      authMethod: 'email',
      plan: 'operator',
      canDownload: true,
    })
  })

  it('keeps a stored local fallback session when bootstrap cannot reach the account API', async () => {
    mockIsSuperTokensConfigured.mockReturnValue(false)
    seedStoredUser({
      id: 'local-1',
      email: 'operator@hypertwist.app',
      name: 'Operator',
      authMethod: 'email',
      plan: 'free',
      role: 'operator',
      canDownload: false,
      billing: {
        source: 'local-fallback',
        accessStatus: 'local-fallback',
        canDownload: false,
      },
    })

    mockGetCurrentUser.mockRejectedValue(new Error('offline'))

    renderWithProvider(<AuthProbe />)

    await waitFor(() => {
      expect(screen.getByTestId('loading').textContent).toBe('false')
    })

    expect(screen.getByTestId('auth-method').textContent).toBe('email')
    expect(screen.getByTestId('billing-source').textContent).toBe('local-fallback')
    expect(mockEnsureSuperTokensInit).not.toHaveBeenCalled()
  })

  it('clears a stale stored session when shared auth is configured and bootstrap returns unauthorized', async () => {
    seedStoredUser({
      id: 'stale-1',
      email: 'operator@hypertwist.app',
      name: 'Operator',
      authMethod: 'supertokens',
      plan: 'operator',
      role: 'operator',
      canDownload: true,
      billing: {
        source: 'session',
        accessStatus: 'active',
        canDownload: true,
      },
    })

    mockGetCurrentUser.mockResolvedValue(createJsonResponse(401, { error: 'unauthorized' }))

    renderWithProvider(<AuthProbe />)

    await waitFor(() => {
      expect(screen.getByTestId('loading').textContent).toBe('false')
    })

    expect(screen.getByTestId('user-id').textContent).toBe('none')
    expect(screen.getByTestId('plan').textContent).toBe('none')
    expect(window.localStorage.getItem(AUTH_STORAGE_KEY)).toBeNull()
    expect(mockEnsureSuperTokensInit).toHaveBeenCalledTimes(1)
  })

  it('reconciles stored browser auth state from live release-authority viewer truth and persists the update', async () => {
    mockIsSuperTokensConfigured.mockReturnValue(false)
    seedStoredUser({
      id: 'local-1',
      email: 'operator@hypertwist.app',
      name: 'Operator',
      authMethod: 'email',
      plan: 'free',
      role: 'viewer',
      canDownload: false,
      billing: {
        source: 'local-fallback',
        accessStatus: 'session-default',
        canDownload: false,
      },
    })

    mockGetCurrentUser.mockRejectedValue(new Error('offline'))

    renderWithProvider(
      <ReleaseAuthorityReconcileProbe
        viewer={{
          authenticated: true,
          canDownload: true,
          plan: 'operator',
          role: 'operator',
          accessStatus: 'active',
        }}
      />,
    )

    await waitFor(() => {
      expect(screen.getByTestId('plan').textContent).toBe('operator')
    })

    expect(screen.getByTestId('role').textContent).toBe('operator')
    expect(screen.getByTestId('can-download').textContent).toBe('true')
    expect(screen.getByTestId('sync-items').textContent).toContain('Plan changed from local free to live operator.')
    expect(screen.getByTestId('sync-items').textContent).toContain('Desktop download access changed from local not entitled to live enabled.')
    expect(JSON.parse(window.localStorage.getItem(AUTH_STORAGE_KEY) || '{}')).toMatchObject({
      plan: 'operator',
      role: 'operator',
      canDownload: true,
      billing: {
        accessStatus: 'active',
        canDownload: true,
      },
    })
  })
})
