/**
 * VideoUploadService Unit Tests
 *
 * Tests the service that uploads videos to YouTube via the Data API v3.
 * Validates credential resolution, media file handling, and YouTube API interaction.
 */

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

// --- Mocks ---

const mockVideosInsert = vi.fn()

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

  return {
    google: {
      auth: {
        OAuth2: MockOAuth2,
      },
      youtube: () => ({
        videos: {
          insert: mockVideosInsert,
        },
      }),
    },
  }
})

const mockExistsSync = vi.fn()
const mockCreateReadStream = vi.fn()

vi.mock('fs', () => ({
  default: {
    existsSync: (...args: unknown[]) => mockExistsSync(...args),
    createReadStream: (...args: unknown[]) => mockCreateReadStream(...args),
  },
  existsSync: (...args: unknown[]) => mockExistsSync(...args),
  createReadStream: (...args: unknown[]) => mockCreateReadStream(...args),
}))

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

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

// --- Helpers ---

function setupYouTubePlatform(): void {
  mockFind.mockResolvedValueOnce({
    docs: [{ id: 100, slug: 'youtube' }],
    totalDocs: 1,
  })
}

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

function setupMedia(overrides?: Record<string, unknown>): void {
  mockFindByID.mockResolvedValueOnce({
    id: 1,
    filename: 'test-video.mp4',
    mimeType: 'video/mp4',
    ...overrides,
  })
}

const defaultMetadata = {
  title: 'My Test Video',
  description: 'A great video about testing',
  tags: ['test', 'video'],
  visibility: 'public' as const,
  categoryId: '22',
}

// --- Tests ---

describe('VideoUploadService', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    process.env.GOOGLE_CLIENT_ID = 'test-client-id'
    process.env.GOOGLE_CLIENT_SECRET = 'test-client-secret'
  })

  it('should upload a video and return the YouTube video ID', async () => {
    setupYouTubePlatform()
    setupSocialAccount()
    setupMedia()
    mockExistsSync.mockReturnValue(true)
    mockCreateReadStream.mockReturnValue('fake-stream')

    mockVideosInsert.mockResolvedValueOnce({
      data: { id: 'yt-abc123' },
    })

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

    const service = new VideoUploadService(mockPayload)
    const result = await service.uploadVideo({
      mediaId: 1,
      metadata: defaultMetadata,
    })

    expect(result.success).toBe(true)
    expect(result.youtubeVideoId).toBe('yt-abc123')
    expect(result.youtubeUrl).toBe('https://www.youtube.com/watch?v=yt-abc123')
    expect(result.error).toBeUndefined()

    expect(mockVideosInsert).toHaveBeenCalledWith(
      expect.objectContaining({
        part: ['snippet', 'status'],
        requestBody: expect.objectContaining({
          snippet: {
            title: 'My Test Video',
            description: 'A great video about testing',
            tags: ['test', 'video'],
            categoryId: '22',
          },
          status: {
            privacyStatus: 'public',
          },
        }),
        media: { body: 'fake-stream' },
      }),
    )
  })

  it('should set privacyStatus to private and include publishAt for scheduled uploads', async () => {
    setupYouTubePlatform()
    setupSocialAccount()
    setupMedia()
    mockExistsSync.mockReturnValue(true)
    mockCreateReadStream.mockReturnValue('fake-stream')

    mockVideosInsert.mockResolvedValueOnce({
      data: { id: 'yt-scheduled-1' },
    })

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

    const service = new VideoUploadService(mockPayload)
    const result = await service.uploadVideo({
      mediaId: 1,
      metadata: { ...defaultMetadata, visibility: 'public' },
      scheduledPublishAt: '2026-03-01T12:00:00Z',
    })

    expect(result.success).toBe(true)
    expect(result.youtubeVideoId).toBe('yt-scheduled-1')

    expect(mockVideosInsert).toHaveBeenCalledWith(
      expect.objectContaining({
        requestBody: expect.objectContaining({
          status: {
            privacyStatus: 'private',
            publishAt: '2026-03-01T12:00:00Z',
          },
        }),
      }),
    )
  })

  it('should return error when no YouTube social account exists', async () => {
    // Platform found, but no account
    setupYouTubePlatform()
    mockFind.mockResolvedValueOnce({ docs: [], totalDocs: 0 })

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

    const service = new VideoUploadService(mockPayload)
    const result = await service.uploadVideo({
      mediaId: 1,
      metadata: defaultMetadata,
    })

    expect(result.success).toBe(false)
    expect(result.error).toContain('No active YouTube social account')
    expect(mockFindByID).not.toHaveBeenCalled()
  })

  it('should return error when social account has no credentials', async () => {
    setupYouTubePlatform()
    setupSocialAccount({ credentials: {} })

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

    const service = new VideoUploadService(mockPayload)
    const result = await service.uploadVideo({
      mediaId: 1,
      metadata: defaultMetadata,
    })

    expect(result.success).toBe(false)
    expect(result.error).toContain('No active YouTube social account')
    expect(mockFindByID).not.toHaveBeenCalled()
  })

  it('should return error when YouTube platform is not found', async () => {
    mockFind.mockResolvedValueOnce({ docs: [], totalDocs: 0 })

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

    const service = new VideoUploadService(mockPayload)
    const result = await service.uploadVideo({
      mediaId: 1,
      metadata: defaultMetadata,
    })

    expect(result.success).toBe(false)
    expect(result.error).toContain('No active YouTube social account')
  })

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

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

    const service = new VideoUploadService(mockPayload)
    const result = await service.uploadVideo({
      mediaId: 999,
      metadata: defaultMetadata,
    })

    expect(result.success).toBe(false)
    expect(result.error).toContain('Media with ID 999 not found')
  })

  it('should return error when media has no filename', async () => {
    setupYouTubePlatform()
    setupSocialAccount()
    setupMedia({ filename: undefined })

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

    const service = new VideoUploadService(mockPayload)
    const result = await service.uploadVideo({
      mediaId: 1,
      metadata: defaultMetadata,
    })

    expect(result.success).toBe(false)
    expect(result.error).toContain('has no filename')
  })

  it('should return error when file does not exist on disk', async () => {
    setupYouTubePlatform()
    setupSocialAccount()
    setupMedia()
    mockExistsSync.mockReturnValue(false)

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

    const service = new VideoUploadService(mockPayload)
    const result = await service.uploadVideo({
      mediaId: 1,
      metadata: defaultMetadata,
    })

    expect(result.success).toBe(false)
    expect(result.error).toContain('File not found on disk')
    expect(result.error).toContain('test-video.mp4')
  })

  it('should return error when YouTube API returns no video ID', async () => {
    setupYouTubePlatform()
    setupSocialAccount()
    setupMedia()
    mockExistsSync.mockReturnValue(true)
    mockCreateReadStream.mockReturnValue('fake-stream')

    mockVideosInsert.mockResolvedValueOnce({
      data: { id: null },
    })

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

    const service = new VideoUploadService(mockPayload)
    const result = await service.uploadVideo({
      mediaId: 1,
      metadata: defaultMetadata,
    })

    expect(result.success).toBe(false)
    expect(result.error).toContain('YouTube API returned no video ID')
  })

  it('should return error when YouTube API throws', async () => {
    setupYouTubePlatform()
    setupSocialAccount()
    setupMedia()
    mockExistsSync.mockReturnValue(true)
    mockCreateReadStream.mockReturnValue('fake-stream')

    mockVideosInsert.mockRejectedValueOnce(new Error('quotaExceeded'))

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

    const service = new VideoUploadService(mockPayload)
    const result = await service.uploadVideo({
      mediaId: 1,
      metadata: defaultMetadata,
    })

    expect(result.success).toBe(false)
    expect(result.error).toContain('YouTube upload failed')
    expect(result.error).toContain('quotaExceeded')
  })

  it('should support unlisted visibility', async () => {
    setupYouTubePlatform()
    setupSocialAccount()
    setupMedia()
    mockExistsSync.mockReturnValue(true)
    mockCreateReadStream.mockReturnValue('fake-stream')

    mockVideosInsert.mockResolvedValueOnce({
      data: { id: 'yt-unlisted-1' },
    })

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

    const service = new VideoUploadService(mockPayload)
    const result = await service.uploadVideo({
      mediaId: 1,
      metadata: { ...defaultMetadata, visibility: 'unlisted' },
    })

    expect(result.success).toBe(true)
    expect(mockVideosInsert).toHaveBeenCalledWith(
      expect.objectContaining({
        requestBody: expect.objectContaining({
          status: { privacyStatus: 'unlisted' },
        }),
      }),
    )
  })

  it('should omit categoryId from snippet when not provided', async () => {
    setupYouTubePlatform()
    setupSocialAccount()
    setupMedia()
    mockExistsSync.mockReturnValue(true)
    mockCreateReadStream.mockReturnValue('fake-stream')

    mockVideosInsert.mockResolvedValueOnce({
      data: { id: 'yt-nocat-1' },
    })

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

    const service = new VideoUploadService(mockPayload)
    const { categoryId: _, ...metadataWithoutCategory } = defaultMetadata
    const result = await service.uploadVideo({
      mediaId: 1,
      metadata: metadataWithoutCategory,
    })

    expect(result.success).toBe(true)
    expect(mockVideosInsert).toHaveBeenCalledWith(
      expect.objectContaining({
        requestBody: expect.objectContaining({
          snippet: expect.objectContaining({
            title: 'My Test Video',
            categoryId: undefined,
          }),
        }),
      }),
    )
  })
})
