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

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

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

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

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

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

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

    it('should throw validation_error when leads count exceeds 1000', async () => {
      const leads = Array.from({ length: 1001 }, (_, i) => ({
        email: `lead${i}@example.com`
      }))
      const params = { campaign_id: 'campaign-123', leads }

      await expect(bulkAddLeads(validCredentials, params)).rejects.toThrow(ToolingError)
      await expect(bulkAddLeads(validCredentials, params)).rejects.toMatchObject({
        errorType: 'validation_error',
        message: 'Cannot add more than 1,000 leads per request'
      })
    })

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

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

  describe('Success Cases', () => {
    it('should successfully add leads and return added_count from leads_uploaded', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ leads_uploaded: 5 })
      })

      const params: BulkAddLeadsParams = {
        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 context = createMockContext()
      const result = await bulkAddLeads(validCredentials, params, context)

      expect(result).toEqual({
        success: true,
        added_count: 5,
        failed_count: 0
      })

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.instantly.ai/api/v2/leads/add',
        expect.objectContaining({
          method: 'POST',
          headers: {
            Authorization: 'Bearer test-api-key',
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            campaign_id: 'campaign-123',
            leads: [
              {
                email: 'a@example.com',
                first_name: undefined,
                last_name: undefined,
                company_name: undefined,
                website: undefined,
                custom_variables: undefined
              },
              {
                email: 'b@example.com',
                first_name: undefined,
                last_name: undefined,
                company_name: undefined,
                website: undefined,
                custom_variables: undefined
              },
              {
                email: 'c@example.com',
                first_name: undefined,
                last_name: undefined,
                company_name: undefined,
                website: undefined,
                custom_variables: undefined
              },
              {
                email: 'd@example.com',
                first_name: undefined,
                last_name: undefined,
                company_name: undefined,
                website: undefined,
                custom_variables: undefined
              },
              {
                email: 'e@example.com',
                first_name: undefined,
                last_name: undefined,
                company_name: undefined,
                website: undefined,
                custom_variables: undefined
              }
            ]
          })
        })
      )
    })

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

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

      const result = await bulkAddLeads(validCredentials, params)

      expect(result).toEqual({
        success: true,
        added_count: 3,
        failed_count: 0
      })
    })

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

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

      const result = await bulkAddLeads(validCredentials, params)

      expect(result).toEqual({
        success: true,
        added_count: 2,
        failed_count: 0
      })
    })

    it('should return success false when failed_count is greater than 0', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () =>
          Promise.resolve({
            added_count: 8,
            failed_count: 2,
            errors: [
              { email: 'bad1@example.com', error: 'Invalid email' },
              { email: 'bad2@example.com', error: 'Duplicate lead' }
            ]
          })
      })

      const params: BulkAddLeadsParams = {
        campaign_id: 'campaign-123',
        leads: Array.from({ length: 10 }, (_, i) => ({ email: `lead${i}@example.com` }))
      }

      const result = await bulkAddLeads(validCredentials, params)

      expect(result.success).toBe(false)
      expect(result.added_count).toBe(8)
      expect(result.failed_count).toBe(2)
      expect(result.errors).toBeDefined()
      expect(result.errors!.length).toBe(2)
    })

    it('should handle custom_variables in lead payload', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ leads_uploaded: 1 })
      })

      const params: BulkAddLeadsParams = {
        campaign_id: 'campaign-123',
        leads: [
          {
            email: 'custom@example.com',
            first_name: 'Custom',
            custom_variables: { industry: 'SaaS', role: 'CEO' }
          }
        ]
      }

      const result = await bulkAddLeads(validCredentials, params)

      expect(result.success).toBe(true)
      expect(result.added_count).toBe(1)

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.instantly.ai/api/v2/leads/add',
        expect.objectContaining({
          body: JSON.stringify({
            campaign_id: 'campaign-123',
            leads: [
              {
                email: 'custom@example.com',
                first_name: 'Custom',
                last_name: undefined,
                company_name: undefined,
                website: undefined,
                custom_variables: { industry: 'SaaS', role: 'CEO' }
              }
            ]
          })
        })
      )
    })
  })

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

      await expect(bulkAddLeads(validCredentials, validParams)).rejects.toThrow(ToolingError)
      await expect(bulkAddLeads(validCredentials, validParams)).rejects.toMatchObject({
        errorType: 'validation_error'
      })
    })
  })

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

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

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

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