import { renderHook } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

import {
  useDataApiRevokeOnCreateDefaultEnabled,
  useTrackDefaultPrivilegesExposure,
} from '../useDataApiRevokeOnCreateDefault'
import { usePHFlag } from '@/hooks/ui/useFlag'
import * as constants from '@/lib/constants'
import { useTrack } from '@/lib/telemetry/track'

vi.mock('@/hooks/ui/useFlag', () => ({
  usePHFlag: vi.fn(),
}))

vi.mock('@/lib/constants', async () => {
  const actual = await vi.importActual<typeof import('@/lib/constants')>('@/lib/constants')
  return {
    ...actual,
    IS_TEST_ENV: false,
  }
})

vi.mock('@/lib/telemetry/track', () => ({
  useTrack: vi.fn(),
}))

describe('useDataApiRevokeOnCreateDefaultEnabled', () => {
  afterEach(() => {
    vi.restoreAllMocks()
    vi.mocked(constants, { partial: true }).IS_TEST_ENV = false
  })

  it('returns false when the PostHog flag is undefined (not yet resolved)', () => {
    vi.mocked(usePHFlag).mockReturnValue(undefined)
    const { result } = renderHook(() => useDataApiRevokeOnCreateDefaultEnabled())
    expect(result.current).toBe(false)
  })

  it('returns false when the PostHog flag is false', () => {
    vi.mocked(usePHFlag).mockReturnValue(false)
    const { result } = renderHook(() => useDataApiRevokeOnCreateDefaultEnabled())
    expect(result.current).toBe(false)
  })

  it('returns true when the PostHog flag is true', () => {
    vi.mocked(usePHFlag).mockReturnValue(true)
    const { result } = renderHook(() => useDataApiRevokeOnCreateDefaultEnabled())
    expect(result.current).toBe(true)
  })

  it('returns false in test env regardless of flag value', () => {
    vi.mocked(constants, { partial: true }).IS_TEST_ENV = true
    vi.mocked(usePHFlag).mockReturnValue(true)
    const { result } = renderHook(() => useDataApiRevokeOnCreateDefaultEnabled())
    expect(result.current).toBe(false)
  })
})

describe('useTrackDefaultPrivilegesExposure', () => {
  const track = vi.fn()

  beforeEach(() => {
    vi.mocked(useTrack).mockReturnValue(track)
  })

  afterEach(() => {
    vi.clearAllMocks()
  })

  it('does not fire while the flag is undefined', () => {
    vi.mocked(usePHFlag).mockReturnValue(undefined)
    renderHook(() =>
      useTrackDefaultPrivilegesExposure({
        surface: 'main',
        dataApiDefaultPrivileges: true,
        hasUserModified: false,
      })
    )
    expect(track).not.toHaveBeenCalled()
  })

  it('fires once when the flag resolves to true on the main surface', () => {
    vi.mocked(usePHFlag).mockReturnValue(true)
    renderHook(() =>
      useTrackDefaultPrivilegesExposure({
        surface: 'main',
        dataApiDefaultPrivileges: false,
        hasUserModified: false,
      })
    )
    expect(track).toHaveBeenCalledTimes(1)
    expect(track).toHaveBeenCalledWith(
      'project_creation_default_privileges_exposed',
      {
        surface: 'main',
        dataApiDefaultPrivileges: false,
        dataApiRevokeOnCreateDefaultEnabled: true,
      },
      undefined
    )
  })

  it('fires once when the flag resolves to false on the main surface', () => {
    vi.mocked(usePHFlag).mockReturnValue(false)
    renderHook(() =>
      useTrackDefaultPrivilegesExposure({
        surface: 'main',
        dataApiDefaultPrivileges: true,
        hasUserModified: false,
      })
    )
    expect(track).toHaveBeenCalledTimes(1)
    expect(track).toHaveBeenCalledWith(
      'project_creation_default_privileges_exposed',
      {
        surface: 'main',
        dataApiDefaultPrivileges: true,
        dataApiRevokeOnCreateDefaultEnabled: false,
      },
      undefined
    )
  })

  it('does not fire while the form value is stale relative to the flag (waits for sync)', () => {
    // Race: flag just resolved to true (treatment), but the caller-side sync
    // useEffect hasn't run yet, so the form value is still the legacy `true`.
    // Without the convergence gate, exposure would fire with the wrong value.
    vi.mocked(usePHFlag).mockReturnValue(true)
    renderHook(() =>
      useTrackDefaultPrivilegesExposure({
        surface: 'main',
        dataApiDefaultPrivileges: true,
        hasUserModified: false,
      })
    )
    expect(track).not.toHaveBeenCalled()
  })

  it('fires on the next render after the form syncs to match the flag', () => {
    vi.mocked(usePHFlag).mockReturnValue(true)
    const { rerender } = renderHook(
      ({ dataApiDefaultPrivileges }: { dataApiDefaultPrivileges: boolean }) =>
        useTrackDefaultPrivilegesExposure({
          surface: 'main',
          dataApiDefaultPrivileges,
          hasUserModified: false,
        }),
      { initialProps: { dataApiDefaultPrivileges: true } }
    )
    expect(track).not.toHaveBeenCalled()

    // Caller-side sync runs and updates the form value to !flag.
    rerender({ dataApiDefaultPrivileges: false })
    expect(track).toHaveBeenCalledTimes(1)
    expect(track).toHaveBeenCalledWith(
      'project_creation_default_privileges_exposed',
      expect.objectContaining({
        dataApiDefaultPrivileges: false,
        dataApiRevokeOnCreateDefaultEnabled: true,
      }),
      undefined
    )
  })

  it('fires immediately with the dirty value when the user has modified the field', () => {
    // User toggled the checkbox before the flag resolved, dirtying the field.
    // The sync gate is bypassed; exposure fires with the user's explicit value.
    vi.mocked(usePHFlag).mockReturnValue(true)
    renderHook(() =>
      useTrackDefaultPrivilegesExposure({
        surface: 'main',
        dataApiDefaultPrivileges: true, // form value disagrees with !flag=false
        hasUserModified: true,
      })
    )
    expect(track).toHaveBeenCalledTimes(1)
    expect(track).toHaveBeenCalledWith(
      'project_creation_default_privileges_exposed',
      {
        surface: 'main',
        dataApiDefaultPrivileges: true,
        dataApiRevokeOnCreateDefaultEnabled: true,
      },
      undefined
    )
  })

  it('fires on the vercel surface with the form-flag convergence gate', () => {
    vi.mocked(usePHFlag).mockReturnValue(true)
    renderHook(() =>
      useTrackDefaultPrivilegesExposure({
        surface: 'vercel',
        orgSlug: 'acme-org',
        dataApiDefaultPrivileges: false,
        hasUserModified: false,
      })
    )
    expect(track).toHaveBeenCalledWith(
      'project_creation_default_privileges_exposed',
      {
        surface: 'vercel',
        dataApiDefaultPrivileges: false,
        dataApiRevokeOnCreateDefaultEnabled: true,
      },
      { organization: 'acme-org' }
    )
  })

  it('skips emission on vercel surface when orgSlug is missing', () => {
    vi.mocked(usePHFlag).mockReturnValue(true)
    renderHook(() =>
      useTrackDefaultPrivilegesExposure({
        surface: 'vercel',
        orgSlug: undefined,
        dataApiDefaultPrivileges: false,
        hasUserModified: false,
      })
    )
    expect(track).not.toHaveBeenCalled()
  })

  it('deduplicates across re-renders', () => {
    vi.mocked(usePHFlag).mockReturnValue(true)
    const { rerender } = renderHook(() =>
      useTrackDefaultPrivilegesExposure({
        surface: 'main',
        dataApiDefaultPrivileges: false,
        hasUserModified: false,
      })
    )
    rerender()
    rerender()
    expect(track).toHaveBeenCalledTimes(1)
  })

  it('does not re-fire if the flag flips after initial exposure', () => {
    vi.mocked(usePHFlag).mockReturnValue(false)
    const { rerender } = renderHook(() =>
      useTrackDefaultPrivilegesExposure({
        surface: 'main',
        dataApiDefaultPrivileges: true,
        hasUserModified: false,
      })
    )
    vi.mocked(usePHFlag).mockReturnValue(true)
    rerender()
    expect(track).toHaveBeenCalledTimes(1)
    expect(track).toHaveBeenCalledWith(
      'project_creation_default_privileges_exposed',
      expect.objectContaining({ dataApiRevokeOnCreateDefaultEnabled: false }),
      undefined
    )
  })
})
