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

import { Telemetry } from './telemetry'

const mocks = vi.hoisted(() => ({
  identify: vi.fn(),
  useUser: vi.fn(),
  useOrganizationsQuery: vi.fn(),
}))

vi.mock('common', async (importOriginal) => {
  const actual = await importOriginal<typeof import('common')>()
  return {
    ...actual,
    posthogClient: {
      identify: mocks.identify,
    },
    useUser: () => mocks.useUser(),
    PageTelemetry: () => null,
  }
})

vi.mock('ui-patterns/consent', () => ({
  useConsentToast: () => ({ hasAcceptedConsent: true }),
}))

vi.mock('@/data/organizations/organizations-query', () => ({
  useOrganizationsQuery: () => mocks.useOrganizationsQuery(),
}))

vi.mock('@/hooks/misc/useSelectedOrganization', () => ({
  useSelectedOrganizationQuery: () => ({ data: undefined }),
}))

vi.mock('@sentry/nextjs', () => ({
  setUser: vi.fn(),
}))

const USER_ID = 'user-abc-123'
const CREATED_AT = '2026-05-14T22:30:00.000Z'

const orgs = (count: number) =>
  Array.from({ length: count }, (_, i) => ({ id: i, slug: `org-${i}` }))

describe('Telemetry — posthog identify mirroring', () => {
  beforeEach(() => {
    mocks.identify.mockReset()
    mocks.useUser.mockReset()
    mocks.useOrganizationsQuery.mockReset()
  })

  it('fires identify with both org_count and signup_timestamp when user and orgs are loaded', async () => {
    mocks.useUser.mockReturnValue({ id: USER_ID, created_at: CREATED_AT })
    mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(1) })

    render(<Telemetry />)

    await waitFor(() => {
      expect(mocks.identify).toHaveBeenCalledWith(USER_ID, {
        org_count: 1,
        signup_timestamp: CREATED_AT,
      })
    })
  })

  it('omits signup_timestamp when created_at is missing', async () => {
    mocks.useUser.mockReturnValue({ id: USER_ID, created_at: undefined })
    mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(1) })

    render(<Telemetry />)

    await waitFor(() => {
      expect(mocks.identify).toHaveBeenCalledWith(USER_ID, { org_count: 1 })
    })
  })

  it('dedupes when nothing has changed', async () => {
    mocks.useUser.mockReturnValue({ id: USER_ID, created_at: CREATED_AT })
    mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(1) })

    const { rerender } = render(<Telemetry />)
    await waitFor(() => expect(mocks.identify).toHaveBeenCalledTimes(1))

    rerender(<Telemetry />)
    rerender(<Telemetry />)

    // Still only one identify — same user, same orgCount, same signupTimestamp.
    expect(mocks.identify).toHaveBeenCalledTimes(1)
  })

  it('re-fires identify when created_at arrives after the first effect run (CodeRabbit regression)', async () => {
    // Initial render: user.id is set, but created_at is still undefined (e.g. partial session parse).
    mocks.useUser.mockReturnValue({ id: USER_ID, created_at: undefined })
    mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(1) })

    const { rerender } = render(<Telemetry />)
    await waitFor(() => expect(mocks.identify).toHaveBeenCalledTimes(1))
    expect(mocks.identify).toHaveBeenLastCalledWith(USER_ID, { org_count: 1 })

    // created_at lands later. The dedup ref must track signupTimestamp,
    // and the effect deps must include user.created_at, or the second
    // identify gets silently skipped — which is exactly the race we shipped
    // the original fix to close.
    mocks.useUser.mockReturnValue({ id: USER_ID, created_at: CREATED_AT })
    rerender(<Telemetry />)

    await waitFor(() => expect(mocks.identify).toHaveBeenCalledTimes(2))
    expect(mocks.identify).toHaveBeenLastCalledWith(USER_ID, {
      org_count: 1,
      signup_timestamp: CREATED_AT,
    })
  })

  it('re-fires identify when org_count changes', async () => {
    mocks.useUser.mockReturnValue({ id: USER_ID, created_at: CREATED_AT })
    mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(1) })

    const { rerender } = render(<Telemetry />)
    await waitFor(() => expect(mocks.identify).toHaveBeenCalledTimes(1))

    mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(2) })
    rerender(<Telemetry />)

    await waitFor(() => expect(mocks.identify).toHaveBeenCalledTimes(2))
    expect(mocks.identify).toHaveBeenLastCalledWith(USER_ID, {
      org_count: 2,
      signup_timestamp: CREATED_AT,
    })
  })

  it('does not fire identify when user or orgs are missing', async () => {
    // user missing
    mocks.useUser.mockReturnValue(null)
    mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(1) })
    const first = render(<Telemetry />)
    await new Promise((r) => setTimeout(r, 10))
    expect(mocks.identify).not.toHaveBeenCalled()
    first.unmount()

    // user present but orgs not loaded
    mocks.useUser.mockReturnValue({ id: USER_ID, created_at: CREATED_AT })
    mocks.useOrganizationsQuery.mockReturnValue({ data: undefined })
    render(<Telemetry />)
    await new Promise((r) => setTimeout(r, 10))
    expect(mocks.identify).not.toHaveBeenCalled()
  })
})
