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

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

  const validCredentials = { apiKey: 'test-api-key' }
  const validParams: AddToCampaignParams = {
    campaign_id: 'campaign-123',
    leads: [
      {
        email: 'test@example.com',
        first_name: 'Test',
        last_name: 'User',
        company_name: 'Test Company'
      }
    ]
  }

  describe('Validation', () => {
    it('should throw error when campaign_id is missing', async () => {
      const params = {
        campaign_id: '',
        leads: [{ email: 'test@example.com' }]
      }

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

    it('should throw error when leads array is empty', async () => {
      const params = {
        campaign_id: 'campaign-123',
        leads: []
      }

      await expect(addToCampaign(validCredentials, params)).rejects.toThrow(ToolingError)
      await expect(addToCampaign(validCredentials, params)).rejects.toMatchObject({
        errorType: 'validation_error',
        message: 'Must provide at least one lead'
      })
    })

    it('should throw error when lead is missing email', async () => {
      const params = {
        campaign_id: 'campaign-123',
        leads: [{ email: '', first_name: 'Test' }]
      }

      await expect(addToCampaign(validCredentials, params)).rejects.toThrow(ToolingError)
      await expect(addToCampaign(validCredentials, params)).rejects.toMatchObject({
        errorType: 'validation_error',
        message: 'Each lead must have an email address'
      })
    })
  })

  describe('Success Cases', () => {
    it('should add single lead to campaign successfully', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ status: 'success', leads_uploaded: 1 })
      })

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

      expect(result).toEqual({
        success: true,
        added_count: 1,
        errors: undefined
      })

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.instantly.ai/api/v2/leads',
        expect.objectContaining({
          method: 'POST',
          headers: {
            Authorization: 'Bearer test-api-key',
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            campaign_id: 'campaign-123',
            leads: [
              {
                email: 'test@example.com',
                first_name: 'Test',
                last_name: 'User',
                company_name: 'Test Company'
              }
            ]
          })
        })
      )
    })

    it('should add multiple leads to campaign', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ status: 'success', leads_uploaded: 3 })
      })

      const params: AddToCampaignParams = {
        campaign_id: 'campaign-123',
        leads: [
          { email: 'lead1@example.com', first_name: 'Lead', last_name: 'One' },
          { email: 'lead2@example.com', first_name: 'Lead', last_name: 'Two' },
          { email: 'lead3@example.com' }
        ]
      }

      const result = await addToCampaign(validCredentials, params)

      expect(result).toEqual({
        success: true,
        added_count: 3,
        errors: undefined
      })
    })

    it('should handle response with uploaded field', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ uploaded: 5 })
      })

      const params: AddToCampaignParams = {
        campaign_id: 'campaign-123',
        leads: [
          { email: 'a@example.com' },
          { email: 'b@example.com' },
          { email: 'c@example.com' },
          { email: 'd@example.com' },
          { email: 'e@example.com' }
        ]
      }

      const result = await addToCampaign(validCredentials, params)

      expect(result).toEqual({
        success: true,
        added_count: 5,
        errors: undefined
      })
    })

    it('should handle wrapped response format', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ data: { leads_uploaded: 2 } })
      })

      const params: AddToCampaignParams = {
        campaign_id: 'campaign-123',
        leads: [{ email: 'a@example.com' }, { email: 'b@example.com' }]
      }

      const result = await addToCampaign(validCredentials, params)

      expect(result).toEqual({
        success: true,
        added_count: 2,
        errors: undefined
      })
    })

    it('should handle lead with only email', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ status: 'success', leads_uploaded: 1 })
      })

      const params: AddToCampaignParams = {
        campaign_id: 'campaign-123',
        leads: [{ email: 'minimal@example.com' }]
      }

      const result = await addToCampaign(validCredentials, params)

      expect(result.success).toBe(true)
      expect(mockFetch).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({
          body: JSON.stringify({
            campaign_id: 'campaign-123',
            leads: [
              {
                email: 'minimal@example.com',
                first_name: undefined,
                last_name: undefined,
                company_name: undefined
              }
            ]
          })
        })
      )
    })
  })

  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')
      })

      const result = await addToCampaign(validCredentials, validParams)

      expect(result.success).toBe(false)
      expect(result.added_count).toBe(0)
      expect(result.errors).toBeDefined()
      expect(result.errors!.length).toBeGreaterThan(0)
    })

    it('should handle network error', async () => {
      // Mock repeated failures to exhaust retries
      mockFetch.mockRejectedValue(new Error('Network error'))

      const result = await addToCampaign(validCredentials, validParams)

      expect(result.success).toBe(false)
      expect(result.added_count).toBe(0)
      expect(result.errors).toBeDefined()
      expect(result.errors!.length).toBeGreaterThan(0)
    })

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

      const result = await addToCampaign(validCredentials, validParams)

      expect(result.success).toBe(false)
      expect(result.errors).toBeDefined()
    })
  })

  describe('Logging', () => {
    it('should log when context is provided', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ status: 'success', leads_uploaded: 1 })
      })

      const context = createMockContext()
      await addToCampaign(validCredentials, validParams, context)

      expect(context.logger.info).toHaveBeenCalledWith(
        expect.stringContaining('[InstantlyAdapter] Adding leads to campaign:')
      )

      expect(context.logger.info).toHaveBeenCalledWith(
        expect.stringContaining('[InstantlyAdapter] Leads added successfully:')
      )
    })
  })
})
