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

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

  const validCredentials = { apiKey: 'test-api-key' }
  const validParams: UpdateCampaignParams = {
    campaign_id: 'campaign-123',
    name: 'My Campaign'
  }

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

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

  describe('Success Cases', () => {
    it('should update campaign name and verify URL and request body', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: 'campaign-123', name: 'New Name' })
      })

      const context = createMockContext()
      const result = await updateCampaign(validCredentials, { campaign_id: 'campaign-123', name: 'New Name' }, context)

      expect(result).toEqual({
        success: true,
        campaign_id: 'campaign-123'
      })

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.instantly.ai/api/v2/campaigns/campaign-123',
        expect.objectContaining({
          method: 'PATCH',
          headers: {
            Authorization: 'Bearer test-api-key',
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({ name: 'New Name' })
        })
      )
    })

    it('should update campaign sequences', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: 'campaign-456' })
      })

      const sequences = [{ steps: [{ type: 'email', subject: 'Hello', body: 'World', delay: 0 }] }]

      const result = await updateCampaign(validCredentials, {
        campaign_id: 'campaign-456',
        sequences
      })

      expect(result).toEqual({
        success: true,
        campaign_id: 'campaign-456'
      })

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.instantly.ai/api/v2/campaigns/campaign-456',
        expect.objectContaining({
          method: 'PATCH',
          body: JSON.stringify({ sequences })
        })
      )
    })

    it('should exclude undefined fields from request body', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: 'campaign-123' })
      })

      await updateCampaign(validCredentials, {
        campaign_id: 'campaign-123',
        name: 'X',
        daily_limit: undefined
      })

      const callArgs = mockFetch.mock.calls[0]
      const requestBody = JSON.parse(callArgs[1].body)

      expect(requestBody).toEqual({ name: 'X' })
      expect(requestBody).not.toHaveProperty('daily_limit')
    })

    it('should work without execution context (no logging)', async () => {
      const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})

      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: 'campaign-123' })
      })

      const result = await updateCampaign(validCredentials, validParams)

      expect(result).toEqual({
        success: true,
        campaign_id: 'campaign-123'
      })

      expect(consoleSpy).not.toHaveBeenCalled()

      consoleSpy.mockRestore()
    })
  })

  describe('URL Construction', () => {
    it('should construct URL with campaign_id exactly', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({})
      })

      await updateCampaign(validCredentials, { campaign_id: 'campaign-123', name: 'Test' })

      const calledUrl = mockFetch.mock.calls[0][0]
      expect(calledUrl).toBe('https://api.instantly.ai/api/v2/campaigns/campaign-123')
    })
  })

  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(updateCampaign(validCredentials, validParams)).rejects.toThrow(ToolingError)
      await expect(updateCampaign(validCredentials, validParams)).rejects.toMatchObject({
        errorType: 'validation_error'
      })
    })
  })

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

      const context = createMockContext()
      await updateCampaign(validCredentials, { campaign_id: 'campaign-123', name: 'Updated Name' }, context)

      expect(context.logger.info).toHaveBeenCalledWith(expect.stringContaining('[InstantlyAdapter] Updating campaign:'))

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