import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { getCampaignAnalytics, type GetCampaignAnalyticsParams } from '../index.js'
import { ToolingError } from '../../../../../../../types.js'
import type { ExecutionContext } from '../../../../../../../../base/types.js'

// Mock fetch globally
const mockFetch = vi.fn()
vi.stubGlobal('fetch', mockFetch)

const createMockContext = (): ExecutionContext => ({
  executionId: 'exec-456',
  organizationId: 'org-123',
  organizationName: 'Test Org',
  userId: 'user-789',
  resourceId: 'test-resource',
  executionDepth: 0,
  logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
  store: new Map()
})

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

  afterEach(() => {
    vi.resetAllMocks()
  })

  const validCredentials = { apiKey: 'test-api-key' }
  const validParams: GetCampaignAnalyticsParams = {
    campaign_id: 'campaign-123'
  }

  describe('Validation', () => {
    it('should throw validation_error when campaign_id is empty', async () => {
      const params = { campaign_id: '' }

      await expect(getCampaignAnalytics(validCredentials, params)).rejects.toThrow(ToolingError)
      await expect(getCampaignAnalytics(validCredentials, params)).rejects.toMatchObject({
        errorType: 'validation_error',
        message: 'Missing required parameter: campaign_id'
      })
    })
  })

  describe('Success Cases', () => {
    it('should return full analytics with v2 API field names mapped', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () =>
          Promise.resolve([
            {
              campaign_id: 'c1',
              emails_sent_count: 100,
              open_count: 50,
              open_count_unique: 40,
              reply_count: 10,
              reply_count_unique: 8,
              bounced_count: 2,
              unsubscribed_count: 1
            }
          ])
      })

      const context = createMockContext()
      const result = await getCampaignAnalytics(validCredentials, { campaign_id: 'c1' }, context)

      expect(result.campaign_id).toBe('c1')
      expect(result.sent).toBe(100)
      expect(result.opened).toBe(50)
      expect(result.unique_opened).toBe(40)
      expect(result.replied).toBe(10)
      expect(result.unique_replied).toBe(8)
      expect(result.bounced).toBe(2)
      expect(result.unsubscribed).toBe(1)
    })

    it('should default missing numeric fields to 0', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve([{ campaign_id: 'c1' }])
      })

      const result = await getCampaignAnalytics(validCredentials, { campaign_id: 'c1' })

      expect(result.campaign_id).toBe('c1')
      expect(result.sent).toBe(0)
      expect(result.opened).toBe(0)
      expect(result.unique_opened).toBe(0)
      expect(result.replied).toBe(0)
      expect(result.unique_replied).toBe(0)
      expect(result.bounced).toBe(0)
      expect(result.unsubscribed).toBe(0)
    })

    it('should handle wrapped { data: [...] } response with v2 fields', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () =>
          Promise.resolve({
            data: [
              {
                campaign_id: 'c1',
                emails_sent_count: 100,
                open_count: 50,
                open_count_unique: 40,
                reply_count: 10,
                reply_count_unique: 8,
                bounced_count: 2,
                unsubscribed_count: 1
              }
            ]
          })
      })

      const result = await getCampaignAnalytics(validCredentials, { campaign_id: 'c1' })

      expect(result.sent).toBe(100)
      expect(result.opened).toBe(50)
      expect(result.replied).toBe(10)
    })

    it('should include optional date range in query params when provided', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve([{ campaign_id: 'campaign-123', emails_sent_count: 50 }])
      })

      await getCampaignAnalytics(validCredentials, {
        campaign_id: 'campaign-123',
        start_date: '2024-01-01',
        end_date: '2024-01-31'
      })

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.instantly.ai/api/v2/campaigns/analytics?id=campaign-123&start_date=2024-01-01&end_date=2024-01-31',
        {
          headers: {
            Authorization: 'Bearer test-api-key'
          }
        }
      )
    })

    it('should exclude date range from query params when not provided', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve([{ campaign_id: 'campaign-123', emails_sent_count: 20 }])
      })

      await getCampaignAnalytics(validCredentials, validParams)

      expect(mockFetch).toHaveBeenCalledWith('https://api.instantly.ai/api/v2/campaigns/analytics?id=campaign-123', {
        headers: {
          Authorization: 'Bearer test-api-key'
        }
      })

      const callUrl = mockFetch.mock.calls[0][0] as string
      expect(callUrl).not.toContain('start_date')
      expect(callUrl).not.toContain('end_date')
    })
  })

  describe('Error Cases', () => {
    it('should handle API error response', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 400,
        statusText: 'Bad Request',
        text: () => Promise.resolve('Invalid campaign ID')
      })

      await expect(getCampaignAnalytics(validCredentials, validParams)).rejects.toThrow(ToolingError)
    })

    it('should handle 401 unauthorized error', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 401,
        statusText: 'Unauthorized',
        text: () => Promise.resolve('Invalid API key')
      })

      await expect(getCampaignAnalytics(validCredentials, validParams)).rejects.toThrow(ToolingError)
    })

    it('should handle network error', async () => {
      mockFetch.mockRejectedValue(new Error('Network error'))

      await expect(getCampaignAnalytics(validCredentials, validParams)).rejects.toThrow()
    })
  })
})
