import { MESSAGE_CUSTOM_TYPE } from '@linktr.ee/messaging-taxonomy'
import { act, createEvent, fireEvent } from '@testing-library/react'
import { Channel, LocalMessage, StreamChat } from 'stream-chat'
import { beforeEach, describe, expect, it, vi } from 'vitest'

import { renderWithProviders, screen } from '../../test/utils'
import type { ChannelPreviewProps } from '../../types'

import { ChannelListProvider } from './ChannelListContext'
import CustomChannelPreview from './CustomChannelPreview'

const mockUser = {
  id: 'current-user',
  name: 'Current User',
}

const mockChatContext = vi.hoisted(() => ({
  current: {
    client: { userID: 'current-user' },
  } as { client?: { userID?: string } },
}))

vi.mock('stream-chat-react', async (importOriginal) => ({
  ...(await importOriginal<typeof import('stream-chat-react')>()),
  useChatContext: () => mockChatContext.current,
}))

type MockChannel = Channel & {
  emitMemberUpdated: (member?: { pinned_at?: string | null }) => void
}

const createMockChannel = (messages: Partial<LocalMessage>[]): MockChannel => {
  const memberUpdatedListeners = new Set<
    (event: { member?: { pinned_at?: string | null } }) => void
  >()

  return {
    id: 'channel-1',
    cid: 'messaging:channel-1',
    _client: { userID: mockUser.id } as unknown as StreamChat,
    state: {
      members: {
        [mockUser.id]: { user: mockUser, user_id: mockUser.id },
        'participant-1': {
          user: { id: 'participant-1', name: 'Alice' },
          user_id: 'participant-1',
        },
      },
      messages: messages as LocalMessage[],
      membership: {},
    },
    on: vi.fn(
      (
        eventName: string,
        listener: (event: { member?: { pinned_at?: string | null } }) => void
      ) => {
        if (eventName === 'member.updated') {
          memberUpdatedListeners.add(listener)
        }
      }
    ),
    off: vi.fn(
      (
        eventName: string,
        listener: (event: { member?: { pinned_at?: string | null } }) => void
      ) => {
        if (eventName === 'member.updated') {
          memberUpdatedListeners.delete(listener)
        }
      }
    ),
    emitMemberUpdated: (member?: { pinned_at?: string | null }) => {
      memberUpdatedListeners.forEach((listener) => listener({ member }))
    },
  } as unknown as MockChannel
}

describe('CustomChannelPreview', () => {
  const defaultProps = {
    onChannelSelect: vi.fn(),
  }

  beforeEach(() => {
    mockChatContext.current = {
      client: { userID: 'current-user' },
    }
    defaultProps.onChannelSelect.mockClear()
  })

  it('applies selected and unselected row styling from context', () => {
    const channel = createMockChannel([])
    const otherChannel = createMockChannel([])
    otherChannel.id = 'channel-2'

    const { rerender } = renderWithProviders(
      <ChannelListProvider
        value={{
          selectedChannel: channel,
          onChannelSelect: defaultProps.onChannelSelect,
          debug: false,
        }}
      >
        <CustomChannelPreview {...defaultProps} channel={channel} />
      </ChannelListProvider>
    )

    let row = screen.getByRole('button')
    expect(row).toHaveClass('bg-black/[0.04]')
    expect(row).not.toHaveClass('hover:bg-black/[0.02]')

    rerender(
      <ChannelListProvider
        value={{
          selectedChannel: otherChannel,
          onChannelSelect: defaultProps.onChannelSelect,
          debug: false,
        }}
      >
        <CustomChannelPreview {...defaultProps} channel={channel} />
      </ChannelListProvider>
    )

    row = screen.getByRole('button')
    expect(row).not.toHaveClass('bg-black/[0.04]')
    expect(row).toHaveClass('hover:bg-black/[0.02]')

    rerender(
      <ChannelListProvider
        value={{
          selectedChannel: otherChannel,
          onChannelSelect: defaultProps.onChannelSelect,
          debug: false,
        }}
      >
        <CustomChannelPreview {...defaultProps} channel={channel} unread={1} />
      </ChannelListProvider>
    )
    expect(screen.getByRole('button')).toHaveClass('hover:bg-black/[0.02]')
  })

  it('selects the channel by click and activation keys only', () => {
    const channel = createMockChannel([])
    const onChannelSelect = vi.fn()
    const { container } = renderWithProviders(
      <ChannelListProvider value={{ onChannelSelect, debug: false }}>
        <CustomChannelPreview channel={channel} />
      </ChannelListProvider>
    )
    const row = container.querySelector('[role="button"]') as HTMLElement

    fireEvent.click(row)
    fireEvent.keyDown(row, { key: 'Enter' })
    const spaceEvent = createEvent.keyDown(row, { key: ' ' })
    fireEvent(row, spaceEvent)

    expect(spaceEvent.defaultPrevented).toBe(true)
    expect(onChannelSelect).toHaveBeenCalledTimes(3)
    expect(onChannelSelect).toHaveBeenNthCalledWith(1, channel)
    expect(onChannelSelect).toHaveBeenNthCalledWith(2, channel)
    expect(onChannelSelect).toHaveBeenNthCalledWith(3, channel)

    fireEvent.keyDown(row, { key: 'a' })
    fireEvent.keyDown(row, { key: 'Enter', repeat: true })
    expect(onChannelSelect).toHaveBeenCalledTimes(3)
  })

  it('renders with an unhydrated client and resolves the fallback member', () => {
    mockChatContext.current = { client: undefined }
    const channel = createMockChannel([])

    expect(() =>
      renderWithProviders(
        <CustomChannelPreview {...defaultProps} channel={channel} />
      )
    ).not.toThrow()
    expect(screen.getByText('Current User')).toBeInTheDocument()
  })

  it('resolves the other participant using the current client user id', () => {
    const channel = createMockChannel([])

    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )
    expect(screen.getByText('Alice')).toBeInTheDocument()

    mockChatContext.current = { client: { userID: 'participant-1' } }
    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )
    expect(screen.getByText('Current User')).toBeInTheDocument()
  })

  it('renders safe fallbacks when channel state is empty', () => {
    const channel = createMockChannel([])
    Object.assign(channel, { state: {} })

    expect(() =>
      renderWithProviders(
        <CustomChannelPreview {...defaultProps} channel={channel} />
      )
    ).not.toThrow()
    expect(screen.getByText('Unknown member')).toBeInTheDocument()
    expect(screen.getByText('No messages yet')).toBeInTheDocument()
    expect(screen.queryByText('Starred conversation.')).not.toBeInTheDocument()
  })

  it('renders a participant image only when one is supplied', () => {
    const channel = createMockChannel([])
    channel.state.members['participant-1'].user = {
      id: 'participant-1',
      name: 'Alice',
      image: 'https://example.com/alice.png',
    }

    const { container, rerender } = renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )
    expect(container.querySelectorAll('img')).toHaveLength(1)
    expect(container.querySelector('img')).toHaveAttribute(
      'src',
      'https://example.com/alice.png'
    )

    channel.state.members['participant-1'].user = {
      id: 'participant-1',
      name: 'Alice',
    }
    rerender(
      <CustomChannelPreview {...defaultProps} channel={channel} unread={1} />
    )
    expect(container.querySelectorAll('img')).toHaveLength(0)
  })

  it.each([
    [undefined, undefined],
    [3, '3'],
    [99, '99'],
    [100, '99+'],
  ])('renders the unread badge for unread=%s', (unread, expectedBadge) => {
    const channel = createMockChannel([])
    const { container } = renderWithProviders(
      <CustomChannelPreview
        {...defaultProps}
        channel={channel}
        unread={unread}
      />
    )

    if (expectedBadge) {
      expect(screen.getByText(expectedBadge)).toBeInTheDocument()
    } else {
      expect(
        container.querySelector('span[class*="bg-[#7f22fe]"]')
      ).not.toBeInTheDocument()
    }
  })

  it('renders a timestamp only when the last message has created_at', () => {
    const channel = createMockChannel([
      {
        id: 'msg-1',
        text: 'Recent message',
        type: 'regular',
        created_at: new Date(),
        user: { id: 'participant-1', name: 'Alice' },
      },
    ])

    const { container, rerender } = renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )
    const timestamp = () =>
      container.querySelector('h3')?.parentElement?.querySelector('span')

    expect(timestamp()).toHaveTextContent('Just now')
    expect(screen.getByText('Just now')).toBeInTheDocument()

    Object.assign(channel.state.messages[0], { created_at: undefined })
    rerender(
      <CustomChannelPreview {...defaultProps} channel={channel} unread={1} />
    )
    expect(timestamp()).toBeNull()
    expect(screen.queryByText('Just now')).not.toBeInTheDocument()
  })

  it.each([
    [
      'tip with text',
      {
        metadata: { custom_type: MESSAGE_CUSTOM_TYPE.TIP },
        text: 'A generous tip',
      },
      'A generous tip',
    ],
    [
      'tip without text',
      { metadata: { custom_type: MESSAGE_CUSTOM_TYPE.TIP } },
      'Sent a tip',
    ],
    [
      'paid with text',
      {
        metadata: { custom_type: MESSAGE_CUSTOM_TYPE.PAID },
        text: 'Paid message',
      },
      'Paid message',
    ],
    [
      'paid without text',
      { metadata: { custom_type: MESSAGE_CUSTOM_TYPE.PAID } },
      'Sent a message',
    ],
    [
      'link attachment',
      { attachments: [{ og_scrape_url: 'https://example.com' }] },
      'https://example.com',
    ],
    [
      'image attachment',
      { attachments: [{ type: 'image' }] },
      '📷 Sent an image',
    ],
    [
      'video attachment',
      { attachments: [{ type: 'video' }] },
      '🎥 Sent a video',
    ],
    ['audio attachment', { attachments: [{ type: 'audio' }] }, '🎵 Sent audio'],
    ['file attachment', { attachments: [{ type: 'file' }] }, '📎 Sent a file'],
    [
      'unknown attachment',
      { attachments: [{ type: 'unknown' }] },
      '📎 Sent an attachment',
    ],
  ])('renders the %s preview text', (_name, message, expectedText) => {
    const channel = createMockChannel([
      {
        id: 'msg-1',
        type: 'regular',
        ...message,
        user: { id: 'participant-1', name: 'Alice' },
      },
    ])

    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )
    expect(screen.getByText(expectedText)).toBeInTheDocument()
  })

  it('prefixes chatbot previews but not normal message previews', () => {
    const channel = createMockChannel([
      {
        id: 'msg-1',
        text: 'Automated response',
        type: 'regular',
        metadata: { custom_type: MESSAGE_CUSTOM_TYPE.CHATBOT },
      },
    ])
    const { rerender } = renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )
    expect(screen.getByText('✨ Automated response')).toBeInTheDocument()

    channel.state.messages[0].metadata = {}
    channel.state.messages[0].text = 'Human response'
    rerender(
      <CustomChannelPreview {...defaultProps} channel={channel} unread={1} />
    )
    expect(screen.getByText('Human response')).toBeInTheDocument()
    expect(screen.queryByText('✨ Human response')).not.toBeInTheDocument()
  })

  it('uses renderMessagePreview from context with the resolved default text', () => {
    const channel = createMockChannel([
      {
        id: 'msg-1',
        text: 'Default preview',
        type: 'regular',
      },
    ])
    const renderMessagePreview = vi.fn(
      (_message: LocalMessage | undefined, _defaultPreview?: string) =>
        'Custom preview'
    )

    renderWithProviders(
      <ChannelListProvider
        value={{ onChannelSelect: vi.fn(), debug: false, renderMessagePreview }}
      >
        <CustomChannelPreview channel={channel} />
      </ChannelListProvider>
    )

    expect(screen.getByText('Custom preview')).toBeInTheDocument()
    expect(renderMessagePreview).toHaveBeenCalledWith(
      channel.state.messages[0],
      'Default preview'
    )
  })

  it('renders the default preview when renderMessagePreview is not supplied', () => {
    const channel = createMockChannel([
      {
        id: 'msg-1',
        text: 'Default preview',
        type: 'regular',
      },
    ])

    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )
    expect(screen.getByText('Default preview')).toBeInTheDocument()
  })

  it('uses the viewer language translation when available', () => {
    const channel = createMockChannel([
      {
        id: 'msg-1',
        text: 'Raw text',
        type: 'regular',
        i18n: { es_text: 'Texto traducido', language: 'es' },
      },
    ])
    const { rerender } = renderWithProviders(
      <ChannelListProvider
        value={{
          onChannelSelect: vi.fn(),
          debug: false,
          viewerLanguage: 'es-MX',
        }}
      >
        <CustomChannelPreview channel={channel} />
      </ChannelListProvider>
    )
    expect(screen.getByText('Texto traducido')).toBeInTheDocument()

    rerender(
      <ChannelListProvider value={{ onChannelSelect: vi.fn(), debug: false }}>
        <CustomChannelPreview channel={channel} />
      </ChannelListProvider>
    )
    expect(screen.getByText('Raw text')).toBeInTheDocument()
  })

  it('logs the preview payload only when debug is enabled', () => {
    const channel = createMockChannel([])
    const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})

    renderWithProviders(
      <ChannelListProvider value={{ onChannelSelect: vi.fn(), debug: true }}>
        <CustomChannelPreview channel={channel} />
      </ChannelListProvider>
    )
    expect(consoleLog).toHaveBeenCalledWith(
      '📺 [ChannelList] 📋 CHANNEL PREVIEW RENDER',
      expect.objectContaining({
        channelId: channel.id,
        isSelected: false,
        participantName: 'Alice',
        unreadCount: 0,
        hasTimestamp: false,
      })
    )

    consoleLog.mockClear()
    const { rerender } = renderWithProviders(
      <ChannelListProvider value={{ onChannelSelect: vi.fn(), debug: false }}>
        <CustomChannelPreview channel={channel} />
      </ChannelListProvider>
    )
    expect(consoleLog).not.toHaveBeenCalled()
    rerender(
      <ChannelListProvider value={{ onChannelSelect: vi.fn(), debug: false }}>
        <CustomChannelPreview channel={channel} />
      </ChannelListProvider>
    )
    expect(consoleLog).not.toHaveBeenCalled()
  })

  it('uses username when participant name is UUID-like', () => {
    const channel = createMockChannel([])
    channel.state.members['participant-1'] = {
      user: {
        id: 'participant-1',
        name: 'a1b2c3d4-e5f6-4789-a012-3456789abcde',
        username: 'alice',
      },
      user_id: 'participant-1',
    }

    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )

    expect(screen.getByText('alice')).toBeInTheDocument()
  })

  it('shows Unknown member when participant has no usable name or username', () => {
    const channel = createMockChannel([])
    channel.state.members['participant-1'] = {
      user: {
        id: 'participant-1',
        name: 'participant-1',
      },
      user_id: 'participant-1',
    }

    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )

    expect(screen.getByText('Unknown member')).toBeInTheDocument()
  })

  it('shows the latest non-system message when the last message is a system message', () => {
    const channel = createMockChannel([
      {
        id: 'msg-1',
        text: 'Hello there!',
        type: 'regular',
        created_at: new Date('2026-01-01T00:00:00.000Z'),
        user: { id: 'participant-1', name: 'Alice' },
      },
      {
        id: 'msg-2',
        text: 'DM Agent has left the conversation',
        type: 'system',
        created_at: new Date('2026-01-01T00:01:00.000Z'),
      },
    ])

    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )

    expect(screen.getByText('Hello there!')).toBeInTheDocument()
    expect(
      screen.queryByText('DM Agent has left the conversation')
    ).not.toBeInTheDocument()
  })

  it('skips multiple consecutive system messages to find the latest non-system message', () => {
    const channel = createMockChannel([
      {
        id: 'msg-1',
        text: 'Nice to meet you!',
        type: 'regular',
        created_at: new Date('2026-01-01T00:00:00.000Z'),
        user: { id: 'participant-1', name: 'Alice' },
      },
      {
        id: 'msg-2',
        text: 'DM Agent has left the conversation',
        type: 'system',
        created_at: new Date('2026-01-01T00:01:00.000Z'),
      },
      {
        id: 'msg-3',
        text: 'DM Agent has rejoined the conversation',
        type: 'system',
        created_at: new Date('2026-01-01T00:02:00.000Z'),
      },
    ])

    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )

    expect(screen.getByText('Nice to meet you!')).toBeInTheDocument()
    expect(
      screen.queryByText('DM Agent has left the conversation')
    ).not.toBeInTheDocument()
    expect(
      screen.queryByText('DM Agent has rejoined the conversation')
    ).not.toBeInTheDocument()
  })

  it('ignores age safety system messages when resolving the channel preview text', () => {
    const channel = createMockChannel([
      {
        id: 'msg-1',
        text: 'Still available to chat',
        type: 'regular',
        created_at: new Date('2026-01-01T00:00:00.000Z'),
        user: { id: 'participant-1', name: 'Alice' },
      },
      {
        id: 'msg-2',
        type: 'system',
        created_at: new Date('2026-01-01T00:01:00.000Z'),
        metadata: {
          custom_type: 'SYSTEM_AGE_SAFETY_BLOCKED',
        },
      },
    ])

    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )

    expect(screen.getByText('Still available to chat')).toBeInTheDocument()
    expect(
      screen.queryByText(
        'This user isn’t able to reply because they don’t meet our age safety guidelines.'
      )
    ).not.toBeInTheDocument()
  })

  it('shows fallback text when all messages are system messages', () => {
    const channel = createMockChannel([
      {
        id: 'msg-1',
        text: 'DM Agent has left the conversation',
        type: 'system',
        created_at: new Date('2026-01-01T00:00:00.000Z'),
      },
      {
        id: 'msg-2',
        text: 'DM Agent has rejoined the conversation',
        type: 'system',
        created_at: new Date('2026-01-01T00:01:00.000Z'),
      },
    ])

    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )

    expect(screen.getByText('No messages yet')).toBeInTheDocument()
  })

  it('shows the latest message when it is not a system message', () => {
    const channel = createMockChannel([
      {
        id: 'msg-1',
        text: 'First message',
        type: 'regular',
        created_at: new Date('2026-01-01T00:00:00.000Z'),
        user: { id: 'participant-1', name: 'Alice' },
      },
      {
        id: 'msg-2',
        text: 'Latest message',
        type: 'regular',
        created_at: new Date('2026-01-01T00:01:00.000Z'),
        user: { id: 'participant-1', name: 'Alice' },
      },
    ])

    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )

    expect(screen.getByText('Latest message')).toBeInTheDocument()
  })

  it('shows fallback text when there are no messages', () => {
    const channel = createMockChannel([])

    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )

    expect(screen.getByText('No messages yet')).toBeInTheDocument()
  })

  it('shows a locked indicator for a text-only attachment message, even when paid', () => {
    // The channel-list preview always shows the locked wording for an
    // attachment-type message and never echoes `message.text` — the
    // revealed content is only ever shown inside the full conversation.
    const channel = createMockChannel([
      {
        id: 'msg-1',
        text: 'REDACTED',
        type: 'regular',
        created_at: new Date('2026-01-01T00:00:00.000Z'),
        user: { id: 'participant-1', name: 'Alice' },
        metadata: {
          custom_type: 'MESSAGE_ATTACHMENT',
          attachment_content_type: 'text',
          payment_status: 'paid',
        },
      },
    ])

    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )

    expect(screen.getByText('🔒 Sent a locked message')).toBeInTheDocument()
    expect(screen.queryByText(/REDACTED/)).not.toBeInTheDocument()
  })

  it('shows a locked indicator for an unpaid text-only attachment message', () => {
    const channel = createMockChannel([
      {
        id: 'msg-1',
        text: 'REDACTED',
        type: 'regular',
        created_at: new Date('2026-01-01T00:00:00.000Z'),
        user: { id: 'participant-1', name: 'Alice' },
        metadata: {
          custom_type: 'MESSAGE_ATTACHMENT',
          attachment_content_type: 'text',
          payment_status: 'pending',
        },
      },
    ])

    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )

    expect(screen.getByText('🔒 Sent a locked message')).toBeInTheDocument()
    expect(screen.queryByText(/REDACTED/)).not.toBeInTheDocument()
  })

  it('shows a locked indicator for a media attachment message, even when paid', () => {
    const channel = createMockChannel([
      {
        id: 'msg-1',
        type: 'regular',
        created_at: new Date('2026-01-01T00:00:00.000Z'),
        user: { id: 'participant-1', name: 'Alice' },
        metadata: {
          custom_type: 'MESSAGE_ATTACHMENT',
          attachment_content_type: 'media',
          payment_status: 'paid',
        },
      },
    ])

    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )

    expect(screen.getByText('🔒 Sent a locked attachment')).toBeInTheDocument()
  })

  it('shows a locked indicator for an unpaid media attachment message', () => {
    const channel = createMockChannel([
      {
        id: 'msg-1',
        type: 'regular',
        created_at: new Date('2026-01-01T00:00:00.000Z'),
        user: { id: 'participant-1', name: 'Alice' },
        metadata: {
          custom_type: 'MESSAGE_ATTACHMENT',
          attachment_content_type: 'media',
          payment_status: 'pending',
        },
      },
    ])

    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )

    expect(screen.getByText('🔒 Sent a locked attachment')).toBeInTheDocument()
  })

  it('updates starred state when the channel membership changes', () => {
    const channel = createMockChannel([])

    renderWithProviders(
      <CustomChannelPreview {...defaultProps} channel={channel} />
    )

    expect(
      screen.queryByRole('heading', { name: /starred conversation/i })
    ).not.toBeInTheDocument()

    act(() => {
      channel.emitMemberUpdated({ pinned_at: new Date().toISOString() })
    })

    expect(
      screen.getByRole('heading', { name: /starred conversation/i })
    ).toBeInTheDocument()
  })

  it('renders a custom channel preview with selection props from context', () => {
    const channel = createMockChannel([])
    const onChannelSelect = vi.fn()
    const ChannelPreview = vi.fn(
      ({
        active,
        channel,
        onChannelSelect,
        selectedChannel,
        viewerLanguage,
      }: ChannelPreviewProps) => {
        const isSelected = selectedChannel?.id === channel.id

        return (
          <button type="button" onClick={() => onChannelSelect(channel)}>
            {isSelected ? 'Selected' : 'Not selected'} {selectedChannel?.id}{' '}
            {viewerLanguage} {active ? 'active' : 'inactive'}
          </button>
        )
      }
    )

    renderWithProviders(
      <ChannelListProvider
        value={{
          selectedChannel: channel,
          onChannelSelect,
          debug: false,
          channelPreview: ChannelPreview,
          viewerLanguage: 'es',
        }}
      >
        <CustomChannelPreview active={false} channel={channel} />
      </ChannelListProvider>
    )

    fireEvent.click(
      screen.getByRole('button', { name: 'Selected channel-1 es active' })
    )

    expect(ChannelPreview).toHaveBeenCalledWith(
      expect.objectContaining({
        channel,
        selectedChannel: channel,
        onChannelSelect,
        viewerLanguage: 'es',
        active: true,
      }),
      expect.anything()
    )
    expect(onChannelSelect).toHaveBeenCalledWith(channel)
  })
})
