import React from 'react'
import { act, render, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

import { inspector, InspectorShell, SegmentEvent } from '../src/index'


jest.mock('@segment/analytics-react', () => ({
  useAnalytics: () => ({
    track: jest.fn(),
    page: jest.fn(),
    addSourceMiddleware: jest.fn()
  })
}))

describe('Event explorer', () => {
  const runMockPageTrace = () =>
    inspector.trace({
      stage: 'triggered',
      id: 'uuid1234',
      timestamp: '2021-04-20T19:44:14.701Z',
      event: {
        type: 'page',
        messageId: 'jgiex11',
        anonymousId: 'jgik1kmd',
        userId: null,
        properties: {
          path: '/users',
          title: 'Hubspot | Users',
          url: 'https://hubspot.com/users'
        }
      }
    })

  const runMockIdentifyTrace = () =>
    inspector.trace({
      stage: 'triggered',
      id: 'uuid5678',
      timestamp: '2021-04-20T19:48:16.701Z',
      event: {
        type: 'identify',
        messageId: 'nvk213',
        anonymousId: 'jgik1kmd',
        userId: 'steven.smith',
        traits: {
          email: 'steven.smith@hubspot.com',
          firstName: 'Steven',
          lastName: 'Smith'
        }
      }
    })

  const runMockTrackTrace1 = () =>
    inspector.trace({
      stage: 'triggered',
      id: 'uuid5679',
      timestamp: '2021-04-20T19:52:16.701Z',
      event: {
        type: 'track',
        event: 'Add to cart',
        messageId: 'oqjdi21',
        anonymousId: 'jgik1kmd',
        userId: 'steven.smith',
        properties: {
          productId: 8589814,
          name: 'Nike running shoes',
          giftwrap: false
        }
      }
    })

  const runMockTrackTrace2 = () =>
    inspector.trace({
      stage: 'triggered',
      id: 'uuid5680',
      timestamp: '2021-04-20T19:54:16.701Z',
      event: {
        type: 'track',
        event: 'Add to cart',
        messageId: 'jciwk34',
        anonymousId: 'jgik1kmd',
        userId: 'steven.smith',
        properties: {
          productId: 980921,
          name: 'Nike trainer shoes',
          giftwrap: false
        }
      }
    })

  it('shows a placeholder message until events arrive', () => {
    inspector.start()
    const screen = render(<InspectorShell landingPage='events' />)

    expect(screen.getByText(/waiting for events/i)).toBeInTheDocument()
  })

  it('shows an event list as traces arrive', async () => {
    inspector.start()
    const screen = render(<InspectorShell landingPage='events' />)

    act(runMockPageTrace)

    expect(screen.queryByText(/waiting for events/i)).not.toBeInTheDocument()

    const eventList = screen.getByRole('list', { name: /events/i })
    expect(eventList).toBeInTheDocument()

    const eventItem1 = within(eventList).getByRole('listitem', {
      name: /page event/
    })

    expect(within(eventItem1).getByText(/hubspot \| users/i)).toBeInTheDocument()
    expect(within(eventItem1).getByText(/occurrence time/i)).toBeInTheDocument()
    expect(within(eventItem1).getByText(/Apr 20, \d\d:\d\d:\d\d/i)).toBeInTheDocument()

    act(runMockIdentifyTrace)

    const eventItem2 = await waitFor(() => within(eventList).getByRole('listitem', { name: /identify event/ }))

    expect(within(eventItem2).getByText(/steven\.smith/i)).toBeInTheDocument()
    expect(within(eventItem2).getByText(/occurrence time/i)).toBeInTheDocument()
    expect(within(eventItem2).getByText(/Apr 20, \d\d:\d\d:\d\d/i)).toBeInTheDocument()
  })

  it('shows event list sorted by most recent events on top', () => {
    inspector.start()
    const screen = render(<InspectorShell landingPage='events' />)

    act(runMockPageTrace)
    act(runMockIdentifyTrace)
    act(runMockTrackTrace1)

    const eventList = screen.getByRole('list', { name: /events/i })

    expect(eventList.children[0]).toHaveTextContent(/add to cart/i)
    expect(eventList.children[1]).toHaveTextContent(/steven\.smith/i)
    expect(eventList.children[2]).toHaveTextContent(/hubspot \| users/i)
  })

  describe('Event details', () => {
    it('shows detailed event anatomy upon selecting an event from the list', async () => {
      inspector.start()
      const screen = render(<InspectorShell landingPage='events' />)

      act(runMockPageTrace)
      act(runMockIdentifyTrace)

      const eventList = await waitFor(() => screen.getByRole('list', { name: /events/i }))
      expect(eventList).toBeTruthy()

      const eventItem1 = within(eventList).getByRole('listitem', {
        name: /page event/
      })

      expect(screen.getByText(/Select an event to view its anatomy/i)).toBeInTheDocument()

      userEvent.click(eventItem1)

      await waitFor(() => expect(screen.queryByText(/Select an event to view its anatomy/i)).not.toBeInTheDocument())

      expect(screen.getByRole('heading', { name: /page event/i })).toBeInTheDocument()
      expect(screen.getByRole('rowheader', { name: /occurrence time/i })).toBeInTheDocument()

      expect(screen.getByRole('rowheader', { name: /status/i })).toBeInTheDocument()
      expect(screen.getByRole('cell', { name: /enqueued/i })).toBeInTheDocument()

      expect(screen.getByRole('rowheader', { name: /problems/i })).toBeInTheDocument()
      expect(screen.getByRole('cell', { name: /all clear/i })).toBeInTheDocument()
    })

    it('shows reported event errors in the detail panel', async () => {
      inspector.start()
      const screen = render(<InspectorShell landingPage='events' />)

      const traceId = 'uuid919e1'
      const event: SegmentEvent = {
        type: 'track',
        messageId: 'jgiex11',
        anonymousId: 'jgik1kmd',
        event: 'Add to cart',
        userId: null,
        properties: {
          path: '/users',
          title: 'Hubspot | Users',
          url: 'https://hubspot.com/users'
        }
      }

      act(() =>
        inspector.trace({
          id: traceId,
          timestamp: '2021-04-20T19:44:14.701Z',
          stage: 'triggered',
          event
        })
      )

      act(() =>
        inspector.trace({
          id: traceId,
          timestamp: '2021-04-20T19:44:14.701Z',
          type: 'error',
          fatal: true,
          message: 'Tracking plan violation',
          description: 'This event does not match with any of the pre-defined events',
          event
        })
      )

      const eventList = await waitFor(() => screen.getByRole('list', { name: /events/i }))
      expect(eventList).toBeTruthy()

      const eventItem1 = within(eventList).getByRole('listitem', {
        name: /track event/
      })

      userEvent.click(eventItem1)

      await waitFor(() => expect(screen.queryByText(/Select an event to view its anatomy/i)).not.toBeInTheDocument())

      expect(screen.getByRole('rowheader', { name: /status/i })).toBeInTheDocument()
      expect(screen.getByRole('cell', { name: /undeliverable/i })).toBeInTheDocument()

      expect(screen.getByRole('rowheader', { name: /problems/i })).toBeInTheDocument()
      expect(screen.getByRole('cell', { name: /1 error/i })).toBeInTheDocument()

      expect(screen.getByRole('heading', { name: 'Tracking plan violation' })).toBeInTheDocument()
      expect(screen.getByText(/this event does not match with any of the pre-defined/i)).toBeInTheDocument()
    })
  })

  describe('Event filtering', () => {
    it('filters events list by search query', async () => {
      inspector.start()
      const screen = render(<InspectorShell landingPage='events' />)

      act(runMockPageTrace)
      act(runMockIdentifyTrace)
      act(runMockTrackTrace1)
      act(runMockTrackTrace2)

      const eventList = await waitFor(() => screen.getByRole('list', { name: /events/i }))

      expect(
        within(eventList).getAllByRole('listitem', {
          name: /^\w+ event$/i
        })
      ).toHaveLength(4)

      const queryInput = screen.getByRole('textbox', { name: /filter events by query/i })

      expect(queryInput).toHaveDisplayValue('')

      userEvent.type(queryInput, 'shoes')

      await waitFor(() => {
        expect(
          within(eventList).getAllByRole('listitem', {
            name: /^\w+ event$/i
          })
        ).toHaveLength(2)

        // the query matched text should be highlighted
        expect(screen.getAllByText('shoes', { selector: 'mark' })).toHaveLength(2)
      })

      userEvent.clear(queryInput)

      expect(
        within(eventList).getAllByRole('listitem', {
          name: /^\w+ event$/i
        })
      ).toHaveLength(4)

      userEvent.click(screen.getByRole('button', { name: /all events/i }))

      await waitFor(() => userEvent.click(screen.getByRole('checkbox', { name: /identify event/i })))

      // only 1 matching event
      expect(
        within(eventList).getByRole('listitem', {
          name: /^\w+ event$/i
        })
      ).toHaveAccessibleName(/identify event/)
    })

    it('filters on query by doing a lookup only in the relevant attributes of events', async () => {
      inspector.start()
      const screen = render(<InspectorShell landingPage='events' />)

      act(() =>
        inspector.trace({
          stage: 'triggered',
          id: 'uuid5245',
          timestamp: '2021-04-20T19:52:16.701Z',
          event: {
            type: 'identify',
            traits: {
              firstName: 'smith'
            },
            // irrelevant - these are all not an area of interest
            messageId: 'abc',
            anonymousId: 'abc',
            userId: 'abc',
            integrations: {
              abc: 'abc'
            },
            context: {
              abc: 'abc'
            }
          }
        })
      )

      act(() =>
        inspector.trace({
          stage: 'triggered',
          id: 'uuid5680',
          timestamp: '2021-04-20T19:52:16.701Z',
          event: {
            type: 'track',
            event: 'Add to cart',
            // irrelevant - these are all not an area of interest
            messageId: 'abc',
            anonymousId: 'abc',
            userId: 'abc',
            integrations: {
              abc: 'abc'
            },
            context: {
              abc: 'abc'
            }
          }
        })
      )

      const eventList = await waitFor(() => screen.getByRole('list', { name: /events/i }))

      expect(
        within(eventList).getAllByRole('listitem', {
          name: /^\w+ event$/i
        })
      ).toHaveLength(2)

      const queryInput = screen.getByRole('textbox', { name: /filter events by query/i })

      userEvent.type(queryInput, 'abc')

      await waitFor(() => {
        expect(screen.getByText(/No events match your filters/)).toBeInTheDocument()
        expect(eventList).not.toBeInTheDocument()
      })
    })
  })
})
