import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { getStepAnalytics, type GetStepAnalyticsParams } 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('getStepAnalytics', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

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

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

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

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

  describe('Success Cases', () => {
    it('should return step analytics from array response', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () =>
          Promise.resolve([
            {
              step: '1',
              variant: '0',
              sent: 100,
              opened: 50,
              unique_opened: 40,
              replies: 10,
              unique_replies: 8
            }
          ])
      })

      const context = createMockContext()
      const result = await getStepAnalytics(validCredentials, validParams, context)

      expect(result).toEqual({
        steps: [
          {
            step: '1',
            variant: '0',
            sent: 100,
            opened: 50,
            unique_opened: 40,
            replies: 10,
            unique_replies: 8
          }
        ]
      })
    })

    it('should handle wrapped { data: [...] } response', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () =>
          Promise.resolve({
            data: [
              {
                step: '1',
                variant: '0',
                sent: 60,
                opened: 30,
                unique_opened: 25,
                replies: 5,
                unique_replies: 4
              }
            ]
          })
      })

      const result = await getStepAnalytics(validCredentials, validParams)

      expect(result.steps).toHaveLength(1)
      expect(result.steps[0].sent).toBe(60)
      expect(result.steps[0].step).toBe('1')
    })

    it('should handle { steps: [...] } response', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () =>
          Promise.resolve({
            steps: [
              {
                step: '2',
                variant: '1',
                sent: 80,
                opened: 40,
                unique_opened: 35,
                replies: 7,
                unique_replies: 6
              }
            ]
          })
      })

      const result = await getStepAnalytics(validCredentials, validParams)

      expect(result.steps).toHaveLength(1)
      expect(result.steps[0].step).toBe('2')
      expect(result.steps[0].sent).toBe(80)
    })

    it('should return empty steps when response is empty object', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({})
      })

      const result = await getStepAnalytics(validCredentials, validParams)

      expect(result).toEqual({ steps: [] })
    })
  })

  describe('URL Construction', () => {
    it('should include campaign_id as query param in the URL', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve([])
      })

      await getStepAnalytics(validCredentials, { campaign_id: 'campaign-123' })

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

  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(getStepAnalytics(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(getStepAnalytics(validCredentials, validParams)).rejects.toThrow(ToolingError)
    })

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

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