/**
 * ChannelMetricsSyncService Unit Tests
 *
 * Tests the service that syncs channel-level statistics from YouTube API
 * to YouTubeChannels.currentMetrics fields.
 */

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

// Mock YouTubeClient before importing the service
const mockGetChannelStats = vi.fn()

vi.mock('googleapis', () => {
  class MockOAuth2 {
    setCredentials(): void {
      // no-op
    }
  }

  return {
    google: {
      auth: {
        OAuth2: MockOAuth2,
      },
      youtube: () => ({
        channels: { list: vi.fn() },
        videos: { list: vi.fn() },
        commentThreads: { list: vi.fn() },
        comments: {
          insert: vi.fn(),
          setModerationStatus: vi.fn(),
          delete: vi.fn(),
        },
      }),
    },
  }
})

// Patch getChannelStats on the prototype after mocking googleapis
vi.mock('@/lib/integrations/youtube/YouTubeClient', async (importOriginal) => {
  const mod = await importOriginal<typeof import('@/lib/integrations/youtube/YouTubeClient')>()
  const OriginalClient = mod.YouTubeClient

  class MockedYouTubeClient extends OriginalClient {
    async getChannelStats(channelId: string) {
      return mockGetChannelStats(channelId)
    }
  }

  return {
    ...mod,
    YouTubeClient: MockedYouTubeClient,
  }
})

vi.stubEnv('GOOGLE_CLIENT_ID', 'test-client-id')
vi.stubEnv('GOOGLE_CLIENT_SECRET', 'test-client-secret')

// --- Payload mock ---

function createMockPayload(overrides: {
  channels?: any[]
  socialAccounts?: any[]
}) {
  const { channels = [], socialAccounts = [] } = overrides
  const updatedDocs: Array<{ collection: string; id: any; data: any }> = []

  return {
    find: vi.fn(async ({ collection, where }: any) => {
      if (collection === 'youtube-channels') {
        return { docs: channels, totalDocs: channels.length }
      }
      if (collection === 'social-accounts') {
        const linkedId = where?.linkedChannel?.equals
        const matching = socialAccounts.filter(
          (a: any) => a.linkedChannel === linkedId && a.isActive,
        )
        return { docs: matching, totalDocs: matching.length }
      }
      return { docs: [], totalDocs: 0 }
    }),
    update: vi.fn(async ({ collection, id, data }: any) => {
      updatedDocs.push({ collection, id, data })
      return { id, ...data }
    }),
    _updatedDocs: updatedDocs,
  }
}

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

  it('should sync metrics for active channels with valid credentials', async () => {
    const mockPayload = createMockPayload({
      channels: [
        {
          id: 1,
          name: 'BlogWoman',
          youtubeChannelId: 'UC_test_123',
          status: 'active',
        },
      ],
      socialAccounts: [
        {
          id: 10,
          linkedChannel: 1,
          isActive: true,
          credentials: {
            accessToken: 'token-abc',
            refreshToken: 'refresh-xyz',
          },
        },
      ],
    })

    mockGetChannelStats.mockResolvedValueOnce({
      subscriberCount: 15000,
      videoCount: 245,
      viewCount: 3200000,
    })

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

    const service = new ChannelMetricsSyncService(mockPayload as any)
    const result = await service.syncAllChannels()

    expect(result.success).toBe(true)
    expect(result.channelsSynced).toBe(1)
    expect(result.errors).toHaveLength(0)

    expect(mockGetChannelStats).toHaveBeenCalledWith('UC_test_123')

    expect(mockPayload.update).toHaveBeenCalledWith(
      expect.objectContaining({
        collection: 'youtube-channels',
        id: 1,
        data: expect.objectContaining({
          currentMetrics: expect.objectContaining({
            subscriberCount: 15000,
            totalViews: 3200000,
            videoCount: 245,
          }),
        }),
      }),
    )

    // Verify lastSyncedAt is an ISO string
    const updateCall = mockPayload.update.mock.calls[0][0]
    expect(updateCall.data.currentMetrics.lastSyncedAt).toBeDefined()
    expect(new Date(updateCall.data.currentMetrics.lastSyncedAt).toISOString()).toBe(
      updateCall.data.currentMetrics.lastSyncedAt,
    )
  })

  it('should skip channels without youtubeChannelId', async () => {
    const mockPayload = createMockPayload({
      channels: [
        {
          id: 2,
          name: 'Planned Channel',
          youtubeChannelId: undefined,
          status: 'active',
        },
      ],
      socialAccounts: [],
    })

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

    const service = new ChannelMetricsSyncService(mockPayload as any)
    const result = await service.syncAllChannels()

    expect(result.channelsSynced).toBe(0)
    expect(result.errors).toHaveLength(1)
    expect(result.errors[0]).toContain('has no youtubeChannelId')
    expect(mockGetChannelStats).not.toHaveBeenCalled()
    expect(mockPayload.update).not.toHaveBeenCalled()
  })

  it('should return success with zero synced when no active channels exist', async () => {
    const mockPayload = createMockPayload({
      channels: [],
      socialAccounts: [],
    })

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

    const service = new ChannelMetricsSyncService(mockPayload as any)
    const result = await service.syncAllChannels()

    expect(result.success).toBe(true)
    expect(result.channelsSynced).toBe(0)
    expect(result.errors).toHaveLength(0)
  })

  it('should record error when no social account is linked to channel', async () => {
    const mockPayload = createMockPayload({
      channels: [
        {
          id: 3,
          name: 'Orphan Channel',
          youtubeChannelId: 'UC_orphan',
          status: 'active',
        },
      ],
      socialAccounts: [],
    })

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

    const service = new ChannelMetricsSyncService(mockPayload as any)
    const result = await service.syncAllChannels()

    expect(result.channelsSynced).toBe(0)
    expect(result.errors).toHaveLength(1)
    expect(result.errors[0]).toContain('No active social account')
    expect(mockPayload.update).not.toHaveBeenCalled()
  })

  it('should record error when credentials are missing', async () => {
    const mockPayload = createMockPayload({
      channels: [
        {
          id: 4,
          name: 'No Creds Channel',
          youtubeChannelId: 'UC_nocreds',
          status: 'active',
        },
      ],
      socialAccounts: [
        {
          id: 20,
          linkedChannel: 4,
          isActive: true,
          credentials: {
            accessToken: undefined,
            refreshToken: undefined,
          },
        },
      ],
    })

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

    const service = new ChannelMetricsSyncService(mockPayload as any)
    const result = await service.syncAllChannels()

    expect(result.channelsSynced).toBe(0)
    expect(result.errors).toHaveLength(1)
    expect(result.errors[0]).toContain('No valid API credentials')
  })

  it('should handle YouTube API errors gracefully per channel', async () => {
    const mockPayload = createMockPayload({
      channels: [
        {
          id: 5,
          name: 'Failing Channel',
          youtubeChannelId: 'UC_fail',
          status: 'active',
        },
        {
          id: 6,
          name: 'Working Channel',
          youtubeChannelId: 'UC_works',
          status: 'active',
        },
      ],
      socialAccounts: [
        {
          id: 30,
          linkedChannel: 5,
          isActive: true,
          credentials: { accessToken: 'tok', refreshToken: 'ref' },
        },
        {
          id: 31,
          linkedChannel: 6,
          isActive: true,
          credentials: { accessToken: 'tok2', refreshToken: 'ref2' },
        },
      ],
    })

    mockGetChannelStats
      .mockRejectedValueOnce(new Error('API quota exceeded'))
      .mockResolvedValueOnce({
        subscriberCount: 500,
        videoCount: 20,
        viewCount: 100000,
      })

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

    const service = new ChannelMetricsSyncService(mockPayload as any)
    const result = await service.syncAllChannels()

    expect(result.success).toBe(false)
    expect(result.channelsSynced).toBe(1)
    expect(result.errors).toHaveLength(1)
    expect(result.errors[0]).toContain('API quota exceeded')

    // The working channel should still have been updated
    expect(mockPayload.update).toHaveBeenCalledTimes(1)
    expect(mockPayload.update).toHaveBeenCalledWith(
      expect.objectContaining({
        collection: 'youtube-channels',
        id: 6,
      }),
    )
  })

  it('should sync multiple channels in a single call', async () => {
    const mockPayload = createMockPayload({
      channels: [
        { id: 7, name: 'Channel A', youtubeChannelId: 'UC_a', status: 'active' },
        { id: 8, name: 'Channel B', youtubeChannelId: 'UC_b', status: 'active' },
      ],
      socialAccounts: [
        {
          id: 40,
          linkedChannel: 7,
          isActive: true,
          credentials: { accessToken: 'a_tok', refreshToken: 'a_ref' },
        },
        {
          id: 41,
          linkedChannel: 8,
          isActive: true,
          credentials: { accessToken: 'b_tok', refreshToken: 'b_ref' },
        },
      ],
    })

    mockGetChannelStats
      .mockResolvedValueOnce({ subscriberCount: 1000, videoCount: 50, viewCount: 200000 })
      .mockResolvedValueOnce({ subscriberCount: 2000, videoCount: 100, viewCount: 400000 })

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

    const service = new ChannelMetricsSyncService(mockPayload as any)
    const result = await service.syncAllChannels()

    expect(result.success).toBe(true)
    expect(result.channelsSynced).toBe(2)
    expect(result.errors).toHaveLength(0)
    expect(mockPayload.update).toHaveBeenCalledTimes(2)
  })
})
