/**
 * VideoMetricsSyncService Unit Tests
 *
 * Tests the service that syncs video performance metrics from YouTube API
 * to YouTubeContent.performance fields in the database.
 */

import { describe, it, expect, vi, beforeEach } from 'vitest'

// Mock YouTubeClient as a proper class before importing the service
const mockGetVideoStatistics = vi.fn()

vi.mock('@/lib/integrations/youtube/YouTubeClient', () => {
  return {
    YouTubeClient: class MockYouTubeClient {
      getVideoStatistics = mockGetVideoStatistics
    },
  }
})

const mockFindByID = vi.fn()
const mockFind = vi.fn()
const mockUpdate = vi.fn()

const mockPayload = {
  findByID: mockFindByID,
  find: mockFind,
  update: mockUpdate,
} as unknown as import('payload').Payload

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

  function setupChannel(): void {
    mockFindByID.mockResolvedValueOnce({
      id: 1,
      name: 'BlogWoman',
      youtubeChannelId: 'UCxxx',
    })
  }

  function setupSocialAccount(overrides?: Record<string, unknown>): void {
    mockFind.mockResolvedValueOnce({
      docs: [
        {
          id: 10,
          credentials: {
            accessToken: 'token-123',
            refreshToken: 'refresh-456',
          },
          isActive: true,
          ...overrides,
        },
      ],
      totalDocs: 1,
    })
  }

  function setupVideos(
    videos: Array<{ id: number; youtube?: { videoId?: string }; status: string }>,
  ): void {
    mockFind.mockResolvedValueOnce({
      docs: videos,
      totalDocs: videos.length,
    })
  }

  it('should sync metrics for published videos with YouTube videoId', async () => {
    setupChannel()
    setupSocialAccount()
    setupVideos([
      { id: 100, youtube: { videoId: 'yt-vid-1' }, status: 'published' },
      { id: 101, youtube: { videoId: 'yt-vid-2' }, status: 'tracked' },
    ])

    mockGetVideoStatistics.mockResolvedValueOnce([
      { id: 'yt-vid-1', views: 1500, likes: 120, comments: 45 },
      { id: 'yt-vid-2', views: 3200, likes: 250, comments: 80 },
    ])

    mockUpdate.mockResolvedValue({})

    const { VideoMetricsSyncService } = await import(
      '@/lib/integrations/youtube/VideoMetricsSyncService'
    )

    const service = new VideoMetricsSyncService(mockPayload)
    const result = await service.syncMetrics(1)

    expect(result.success).toBe(true)
    expect(result.syncedCount).toBe(2)
    expect(result.skippedCount).toBe(0)
    expect(result.errors).toHaveLength(0)

    expect(mockUpdate).toHaveBeenCalledTimes(2)
    expect(mockUpdate).toHaveBeenCalledWith(
      expect.objectContaining({
        collection: 'youtube-content',
        id: 100,
        data: expect.objectContaining({
          performance: expect.objectContaining({
            views: 1500,
            likes: 120,
            comments: 45,
          }),
        }),
      }),
    )
    expect(mockUpdate).toHaveBeenCalledWith(
      expect.objectContaining({
        collection: 'youtube-content',
        id: 101,
        data: expect.objectContaining({
          performance: expect.objectContaining({
            views: 3200,
            likes: 250,
            comments: 80,
          }),
        }),
      }),
    )
  })

  it('should skip videos without a YouTube videoId', async () => {
    setupChannel()
    setupSocialAccount()
    setupVideos([
      { id: 100, youtube: { videoId: 'yt-vid-1' }, status: 'published' },
      { id: 101, youtube: {}, status: 'published' },
      { id: 102, status: 'published' },
    ])

    mockGetVideoStatistics.mockResolvedValueOnce([
      { id: 'yt-vid-1', views: 500, likes: 40, comments: 10 },
    ])

    mockUpdate.mockResolvedValue({})

    const { VideoMetricsSyncService } = await import(
      '@/lib/integrations/youtube/VideoMetricsSyncService'
    )

    const service = new VideoMetricsSyncService(mockPayload)
    const result = await service.syncMetrics(1)

    expect(result.success).toBe(true)
    expect(result.syncedCount).toBe(1)
    expect(result.skippedCount).toBe(2)
    expect(mockGetVideoStatistics).toHaveBeenCalledWith(['yt-vid-1'])
  })

  it('should return early with success when no videos exist', async () => {
    setupChannel()
    setupSocialAccount()
    setupVideos([])

    const { VideoMetricsSyncService } = await import(
      '@/lib/integrations/youtube/VideoMetricsSyncService'
    )

    const service = new VideoMetricsSyncService(mockPayload)
    const result = await service.syncMetrics(1)

    expect(result.success).toBe(true)
    expect(result.syncedCount).toBe(0)
    expect(result.skippedCount).toBe(0)
    expect(mockGetVideoStatistics).not.toHaveBeenCalled()
  })

  it('should return error when channel is not found', async () => {
    mockFindByID.mockResolvedValueOnce(null)

    const { VideoMetricsSyncService } = await import(
      '@/lib/integrations/youtube/VideoMetricsSyncService'
    )

    const service = new VideoMetricsSyncService(mockPayload)
    const result = await service.syncMetrics(999)

    expect(result.success).toBe(false)
    expect(result.errors).toContainEqual(
      expect.stringContaining('not found'),
    )
    expect(mockFind).not.toHaveBeenCalled()
  })

  it('should return error when no active social account exists', async () => {
    setupChannel()
    mockFind.mockResolvedValueOnce({ docs: [], totalDocs: 0 })

    const { VideoMetricsSyncService } = await import(
      '@/lib/integrations/youtube/VideoMetricsSyncService'
    )

    const service = new VideoMetricsSyncService(mockPayload)
    const result = await service.syncMetrics(1)

    expect(result.success).toBe(false)
    expect(result.errors).toContainEqual(
      expect.stringContaining('No active social account'),
    )
  })

  it('should return error when credentials are missing', async () => {
    setupChannel()
    setupSocialAccount({ credentials: {} })

    const { VideoMetricsSyncService } = await import(
      '@/lib/integrations/youtube/VideoMetricsSyncService'
    )

    const service = new VideoMetricsSyncService(mockPayload)
    const result = await service.syncMetrics(1)

    expect(result.success).toBe(false)
    expect(result.errors).toContainEqual(
      expect.stringContaining('No valid API credentials'),
    )
  })

  it('should batch videos into groups of 50', async () => {
    setupChannel()
    setupSocialAccount()

    // Create 75 videos to force 2 batches (50 + 25)
    const videos = Array.from({ length: 75 }, (_, i) => ({
      id: i + 1,
      youtube: { videoId: `yt-vid-${i + 1}` },
      status: 'published',
    }))
    setupVideos(videos)

    // First batch: 50 videos
    mockGetVideoStatistics.mockResolvedValueOnce(
      Array.from({ length: 50 }, (_, i) => ({
        id: `yt-vid-${i + 1}`,
        views: 100,
        likes: 10,
        comments: 5,
      })),
    )

    // Second batch: 25 videos
    mockGetVideoStatistics.mockResolvedValueOnce(
      Array.from({ length: 25 }, (_, i) => ({
        id: `yt-vid-${i + 51}`,
        views: 200,
        likes: 20,
        comments: 10,
      })),
    )

    mockUpdate.mockResolvedValue({})

    const { VideoMetricsSyncService } = await import(
      '@/lib/integrations/youtube/VideoMetricsSyncService'
    )

    const service = new VideoMetricsSyncService(mockPayload)
    const result = await service.syncMetrics(1)

    expect(result.success).toBe(true)
    expect(result.syncedCount).toBe(75)
    expect(mockGetVideoStatistics).toHaveBeenCalledTimes(2)

    const firstCallIds = mockGetVideoStatistics.mock.calls[0][0] as string[]
    expect(firstCallIds).toHaveLength(50)

    const secondCallIds = mockGetVideoStatistics.mock.calls[1][0] as string[]
    expect(secondCallIds).toHaveLength(25)
  })

  it('should record errors for videos missing from API response', async () => {
    setupChannel()
    setupSocialAccount()
    setupVideos([
      { id: 100, youtube: { videoId: 'yt-vid-1' }, status: 'published' },
      { id: 101, youtube: { videoId: 'yt-vid-2' }, status: 'published' },
    ])

    // Only return stats for one video
    mockGetVideoStatistics.mockResolvedValueOnce([
      { id: 'yt-vid-1', views: 500, likes: 40, comments: 10 },
    ])

    mockUpdate.mockResolvedValue({})

    const { VideoMetricsSyncService } = await import(
      '@/lib/integrations/youtube/VideoMetricsSyncService'
    )

    const service = new VideoMetricsSyncService(mockPayload)
    const result = await service.syncMetrics(1)

    expect(result.success).toBe(false)
    expect(result.syncedCount).toBe(1)
    expect(result.errors).toContainEqual(
      expect.stringContaining('No statistics returned for video yt-vid-2'),
    )
  })

  it('should handle API errors gracefully per batch', async () => {
    setupChannel()
    setupSocialAccount()
    setupVideos([
      { id: 100, youtube: { videoId: 'yt-vid-1' }, status: 'published' },
    ])

    mockGetVideoStatistics.mockRejectedValueOnce(new Error('API quota exceeded'))

    const { VideoMetricsSyncService } = await import(
      '@/lib/integrations/youtube/VideoMetricsSyncService'
    )

    const service = new VideoMetricsSyncService(mockPayload)
    const result = await service.syncMetrics(1)

    expect(result.success).toBe(false)
    expect(result.syncedCount).toBe(0)
    expect(result.errors).toContainEqual(
      expect.stringContaining('Batch API call failed'),
    )
  })

  it('should set lastSyncedAt in performance data', async () => {
    setupChannel()
    setupSocialAccount()
    setupVideos([
      { id: 100, youtube: { videoId: 'yt-vid-1' }, status: 'published' },
    ])

    mockGetVideoStatistics.mockResolvedValueOnce([
      { id: 'yt-vid-1', views: 100, likes: 10, comments: 5 },
    ])

    mockUpdate.mockResolvedValue({})

    const { VideoMetricsSyncService } = await import(
      '@/lib/integrations/youtube/VideoMetricsSyncService'
    )

    const service = new VideoMetricsSyncService(mockPayload)
    await service.syncMetrics(1)

    expect(mockUpdate).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          performance: expect.objectContaining({
            lastSyncedAt: expect.any(String),
          }),
        }),
      }),
    )
  })
})
