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

import PostEmbed from '@/components/PostEmbed'
import { buildPost } from '@/test/factories'

const dialogue = vi.hoisted (() => ({
  confirm: vi.fn (),
}))

const nicoViewer = vi.hoisted (() => ({
  props: vi.fn (),
}))

vi.mock ('@/components/dialogues/DialogueProvider', () => ({
  useDialogue: () => dialogue,
}))

vi.mock ('@/components/NicoViewer', () => ({
  default: (props: { id: string }) => {
    nicoViewer.props (props)
    return <div>Nico:{props.id}</div>
  },
}))

vi.mock ('react-youtube', () => ({
  default: ({ videoId }: { videoId: string }) => <div>YouTube:{videoId}</div>,
}))

describe ('PostEmbed', () => {
  beforeEach (() => {
    vi.clearAllMocks ()
  })

  it ('embeds nicovideo watch URLs', () => {
    render (<PostEmbed post={buildPost ({ url: 'https://www.nicovideo.jp/watch/sm12345' })}/>)

    expect (screen.getByText ('Nico:sm12345')).toBeInTheDocument ()
  })

  it ('reports niconico metadata as milliseconds', () => {
    const onVideoReady = vi.fn ()
    const onPlaybackChange = vi.fn ()
    render (
        <PostEmbed
            post={buildPost ({ url: 'https://www.nicovideo.jp/watch/sm12345' })}
            onVideoReady={onVideoReady}
            onPlaybackChange={onPlaybackChange}/>,
    )

    nicoViewer.props.mock.calls[0][0].onMetadataChange ({
      currentTime:            7_000,
      duration:               120_000,
      isVideoMetaDataLoaded:  true,
      maximumBuffered:        30,
      muted:                  false,
      showComment:            true,
      volume:                 1,
    })

    expect (onVideoReady).toHaveBeenCalledWith (120_000)
    expect (onPlaybackChange).toHaveBeenCalledWith (7_000)
  })

  it ('reports niconico video readiness only once', () => {
    const onVideoReady = vi.fn ()
    render (
        <PostEmbed
            post={buildPost ({ url: 'https://www.nicovideo.jp/watch/sm12345' })}
            onVideoReady={onVideoReady}/>,
    )

    nicoViewer.props.mock.calls[0][0].onLoadComplete ({
      title:           '動画',
      videoId:         'sm12345',
      lengthInSeconds: 120,
      thumbnailUrl:    'https://example.com/thumb.jpg',
      description:     '',
      viewCount:       1,
      commentCount:    2,
      mylistCount:     3,
      postedAt:        '2026-01-02T03:04:05.000Z',
      watchId:         12345,
    })
    nicoViewer.props.mock.calls[0][0].onMetadataChange ({
      currentTime:            7_000,
      duration:               120_000,
      isVideoMetaDataLoaded:  true,
      maximumBuffered:        30,
      muted:                  false,
      showComment:            true,
      volume:                 1,
    })

    expect (onVideoReady).toHaveBeenCalledTimes (1)
    expect (onVideoReady).toHaveBeenCalledWith (120_000)
  })

  it ('embeds x/twitter status URLs', () => {
    render (<PostEmbed post={buildPost ({ url: 'https://x.com/someone/status/12345' })}/>)

    expect (screen.getByRole ('link', { name: '@someone' })).toBeInTheDocument ()
  })

  it ('embeds youtube watch URLs', () => {
    render (<PostEmbed post={buildPost ({ url: 'https://www.youtube.com/watch?v=abc123' })}/>)

    expect (screen.getByText ('YouTube:abc123')).toBeInTheDocument ()
  })

  it ('asks before framing unknown external pages', async () => {
    dialogue.confirm.mockResolvedValueOnce (true)
    render (
        <PostEmbed
            post={buildPost ({ url: 'https://example.com/page', title: 'external' })}/>,
    )

    fireEvent.click (screen.getByRole ('link', { name: '外部ページを表示' }))

    await waitFor (() => {
      expect (dialogue.confirm).toHaveBeenCalled ()
    })
    expect (await screen.findByTitle ('external')).toHaveAttribute (
        'src',
        'https://example.com/page',
    )
  })
})
