import { render } from '@testing-library/react'

import history from 'dbaas/services/history'
import { measurePerformace, normalizeTransactionName } from 'dbaas/utils/tracing/performance'

import { HeroComponent, heroTracing, isHeroQueryReady } from '.'

jest.mock('dbaas/services/history', () => ({
  __esModule: true,
  default: {
    listen: jest.fn()
  }
}))

jest.mock('dbaas/routes/tracing', () => ({
  tracingRoutes: [{ path: '/tidbs/:id/overview', exact: true, scene: 'CoreJourney' }]
}))

jest.mock('dbaas/utils/tracing/performance', () => ({
  measurePerformace: jest.fn(),
  normalizeTransactionName: jest.fn(() => ['/tidbs/:id/overview', { scene: 'CoreJourney' }])
}))

const mockedHistory = history as jest.Mocked<typeof history>
const mockedMeasurePerformace = measurePerformace as jest.MockedFunction<typeof measurePerformace>
const mockedNormalizeTransactionName = normalizeTransactionName as jest.MockedFunction<typeof normalizeTransactionName>

describe('HeroComponent', () => {
  afterEach(() => {
    jest.restoreAllMocks()
  })

  beforeEach(() => {
    heroTracing.loaded = false
    heroTracing.prevName = ''
    heroTracing.pathname = window.location.pathname
    heroTracing.navigationStartTime = undefined
    heroTracing.resetError()
    mockedMeasurePerformace.mockClear()
    mockedNormalizeTransactionName.mockClear()
    mockedHistory.listen.mockClear()
  })

  it('waits until ready before completing the hero trace', () => {
    const { rerender } = render(<HeroComponent ready={false} />)

    expect(mockedMeasurePerformace).not.toHaveBeenCalled()

    rerender(<HeroComponent ready />)

    expect(mockedMeasurePerformace).toHaveBeenCalledTimes(1)

    rerender(<HeroComponent ready />)

    expect(mockedMeasurePerformace).toHaveBeenCalledTimes(1)

    rerender(<HeroComponent ready={false} />)
    rerender(<HeroComponent ready />)

    expect(mockedMeasurePerformace).toHaveBeenCalledTimes(1)
  })

  it('uses the normalized route and preserves route tags', () => {
    render(<HeroComponent ready />)

    expect(mockedNormalizeTransactionName).toHaveBeenCalledWith(window.location.pathname, expect.any(Array))
    expect(mockedMeasurePerformace).toHaveBeenCalledWith(
      expect.objectContaining({
        includeOrigins: [window.location.origin, 'tidbcloud.com'],
        name: 'c_/tidbs/:id/overview',
        tags: {
          scene: 'CoreJourney'
        }
      })
    )
  })

  it('does not create another hero transaction after navigation', () => {
    let navigationListener: ((location: { pathname: string }) => void) | undefined
    mockedHistory.listen.mockImplementation((listener: any) => {
      navigationListener = listener
      return jest.fn()
    })
    heroTracing.start()

    const first = render(<HeroComponent ready />)
    expect(mockedMeasurePerformace).toHaveBeenCalledTimes(1)

    first.unmount()
    navigationListener?.({ pathname: '/tidbs/tidb-2/overview' })
    render(<HeroComponent ready />)

    expect(mockedMeasurePerformace).toHaveBeenCalledTimes(1)
    expect(heroTracing.loaded).toBe(true)
  })

  it('does not create a hero transaction for SPA navigation', () => {
    let navigationListener: ((location: { pathname: string }) => void) | undefined
    mockedHistory.listen.mockImplementation((listener: any) => {
      navigationListener = listener
      return jest.fn()
    })
    jest.spyOn(Date, 'now').mockReturnValue(1_720_000_000_000)
    heroTracing.start()

    navigationListener?.({ pathname: '/tidbs/tidb-2/overview' })
    render(<HeroComponent ready />)

    expect(mockedMeasurePerformace).not.toHaveBeenCalled()
    expect(heroTracing.loaded).toBe(true)
  })

  it('does not reset the hero trace for search-only navigation', () => {
    let navigationListener: ((location: { pathname: string }) => void) | undefined
    mockedHistory.listen.mockImplementation((listener: any) => {
      navigationListener = listener
      return jest.fn()
    })
    heroTracing.start()
    render(<HeroComponent ready />)

    navigationListener?.({ pathname: window.location.pathname })

    expect(heroTracing.loaded).toBe(true)
    expect(mockedMeasurePerformace).toHaveBeenCalledTimes(1)
  })

  it('treats either a fetched result or an error result as query completion', () => {
    expect(isHeroQueryReady({ isFetched: false, isError: false })).toBe(false)
    expect(isHeroQueryReady({ isFetched: true, isError: false })).toBe(true)
    expect(isHeroQueryReady({ isFetched: false, isError: true })).toBe(true)
  })
})
