/**
 * Tests for Instantly updateInterestStatus fetch implementation
 *
 * Tests the native fetch implementation of updateInterestStatus method:
 * 1. Success cases for different interest values (-1, 0, 1)
 * 2. Success case with optional campaign_id
 * 3. Success case with disable_auto_interest option
 * 4. Error cases for validation failures
 * 5. Error cases for API errors (401, 422, etc.)
 */

import { describe, it, expect, vi, beforeEach } from 'vitest'
import { updateInterestStatus } from '../index.js'
import { ToolingError } from '../../../../../../../types.js'

// Mock fetch globally
const mockFetch = vi.fn()
global.fetch = mockFetch as typeof fetch

describe('updateInterestStatus', () => {
  const validCredentials = { apiKey: 'test-api-key' }
  const testContext = {
    organizationId: 'org-123',
    organizationName: 'Test Org',
    executionId: 'exec-456',
    resourceId: 'resource-789',
    executionDepth: 0,
    logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
    store: new Map()
  }

  beforeEach(() => {
    vi.clearAllMocks()
  })

  describe('Success Cases', () => {
    it('should update interest status to Not Interested (interest_value=-1)', async () => {
      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => ({})
      })

      const result = await updateInterestStatus(
        validCredentials,
        { lead_email: 'test@example.com', interest_value: -1 },
        testContext
      )

      expect(result).toEqual({
        success: true,
        lead_email: 'test@example.com'
      })

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.instantly.ai/api/v2/leads/update-interest-status',
        expect.objectContaining({
          method: 'POST',
          headers: {
            Authorization: 'Bearer test-api-key',
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            lead_email: 'test@example.com',
            interest_value: -1
          })
        })
      )
    })

    it('should update interest status to Interested (interest_value=1)', async () => {
      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => ({})
      })

      const result = await updateInterestStatus(
        validCredentials,
        { lead_email: 'interested@example.com', interest_value: 1 },
        testContext
      )

      expect(result).toEqual({
        success: true,
        lead_email: 'interested@example.com'
      })

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.instantly.ai/api/v2/leads/update-interest-status',
        expect.objectContaining({
          method: 'POST',
          body: JSON.stringify({
            lead_email: 'interested@example.com',
            interest_value: 1
          })
        })
      )
    })

    it('should reset interest status to Lead/default (interest_value=0)', async () => {
      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => ({})
      })

      const result = await updateInterestStatus(
        validCredentials,
        { lead_email: 'reset@example.com', interest_value: 0 },
        testContext
      )

      expect(result).toEqual({
        success: true,
        lead_email: 'reset@example.com'
      })

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.instantly.ai/api/v2/leads/update-interest-status',
        expect.objectContaining({
          method: 'POST',
          body: JSON.stringify({
            lead_email: 'reset@example.com',
            interest_value: 0
          })
        })
      )
    })

    it('should include campaign_id when provided', async () => {
      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => ({})
      })

      const result = await updateInterestStatus(
        validCredentials,
        {
          lead_email: 'campaign@example.com',
          interest_value: -1,
          campaign_id: 'campaign-123'
        },
        testContext
      )

      expect(result).toEqual({
        success: true,
        lead_email: 'campaign@example.com'
      })

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.instantly.ai/api/v2/leads/update-interest-status',
        expect.objectContaining({
          method: 'POST',
          body: JSON.stringify({
            lead_email: 'campaign@example.com',
            interest_value: -1,
            campaign_id: 'campaign-123'
          })
        })
      )
    })

    it('should include disable_auto_interest when provided', async () => {
      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => ({})
      })

      const result = await updateInterestStatus(
        validCredentials,
        {
          lead_email: 'auto@example.com',
          interest_value: 1,
          disable_auto_interest: true
        },
        testContext
      )

      expect(result).toEqual({
        success: true,
        lead_email: 'auto@example.com'
      })

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.instantly.ai/api/v2/leads/update-interest-status',
        expect.objectContaining({
          method: 'POST',
          body: JSON.stringify({
            lead_email: 'auto@example.com',
            interest_value: 1,
            disable_auto_interest: true
          })
        })
      )
    })

    it('should work without execution context', async () => {
      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => ({})
      })

      const result = await updateInterestStatus(validCredentials, {
        lead_email: 'nocontext@example.com',
        interest_value: -1
      })

      expect(result).toEqual({
        success: true,
        lead_email: 'nocontext@example.com'
      })
    })
  })

  describe('Validation Error Cases', () => {
    it('should throw validation_error when lead_email is missing', async () => {
      await expect(
        updateInterestStatus(validCredentials, { lead_email: '', interest_value: -1 }, testContext)
      ).rejects.toThrow(ToolingError)

      try {
        await updateInterestStatus(validCredentials, { lead_email: '', interest_value: -1 }, testContext)
      } catch (error) {
        expect((error as ToolingError).errorType).toBe('validation_error')
        expect((error as ToolingError).message).toBe('Missing required parameter: lead_email')
      }

      // Fetch should not be called on validation error
      expect(mockFetch).not.toHaveBeenCalled()
    })

    it('should throw validation_error when interest_value is undefined', async () => {
      await expect(
        updateInterestStatus(
          validCredentials,
          { lead_email: 'test@example.com', interest_value: undefined as unknown as number },
          testContext
        )
      ).rejects.toThrow(ToolingError)

      try {
        await updateInterestStatus(
          validCredentials,
          { lead_email: 'test@example.com', interest_value: undefined as unknown as number },
          testContext
        )
      } catch (error) {
        expect((error as ToolingError).errorType).toBe('validation_error')
        expect((error as ToolingError).message).toBe('Missing required parameter: interest_value')
      }

      expect(mockFetch).not.toHaveBeenCalled()
    })

    it('should throw validation_error when interest_value is null', async () => {
      await expect(
        updateInterestStatus(
          validCredentials,
          { lead_email: 'test@example.com', interest_value: null as unknown as number },
          testContext
        )
      ).rejects.toThrow(ToolingError)

      try {
        await updateInterestStatus(
          validCredentials,
          { lead_email: 'test@example.com', interest_value: null as unknown as number },
          testContext
        )
      } catch (error) {
        expect((error as ToolingError).errorType).toBe('validation_error')
        expect((error as ToolingError).message).toBe('Missing required parameter: interest_value')
      }

      expect(mockFetch).not.toHaveBeenCalled()
    })

    it('should throw validation_error when interest_value is 2 (invalid)', async () => {
      await expect(
        updateInterestStatus(validCredentials, { lead_email: 'test@example.com', interest_value: 2 }, testContext)
      ).rejects.toThrow(ToolingError)

      try {
        await updateInterestStatus(validCredentials, { lead_email: 'test@example.com', interest_value: 2 }, testContext)
      } catch (error) {
        expect((error as ToolingError).errorType).toBe('validation_error')
        expect((error as ToolingError).message).toBe(
          'interest_value must be 1 (interested), 0 (default), or -1 (not-interested)'
        )
      }

      expect(mockFetch).not.toHaveBeenCalled()
    })

    it('should throw validation_error when interest_value is -2 (invalid)', async () => {
      await expect(
        updateInterestStatus(validCredentials, { lead_email: 'test@example.com', interest_value: -2 }, testContext)
      ).rejects.toThrow(ToolingError)

      try {
        await updateInterestStatus(
          validCredentials,
          { lead_email: 'test@example.com', interest_value: -2 },
          testContext
        )
      } catch (error) {
        expect((error as ToolingError).errorType).toBe('validation_error')
        expect((error as ToolingError).message).toBe(
          'interest_value must be 1 (interested), 0 (default), or -1 (not-interested)'
        )
      }

      expect(mockFetch).not.toHaveBeenCalled()
    })

    it('should throw validation_error when interest_value is 100 (invalid)', async () => {
      try {
        await updateInterestStatus(
          validCredentials,
          { lead_email: 'test@example.com', interest_value: 100 },
          testContext
        )
      } catch (error) {
        expect((error as ToolingError).errorType).toBe('validation_error')
        expect((error as ToolingError).message).toContain('interest_value must be')
      }

      expect(mockFetch).not.toHaveBeenCalled()
    })
  })

  describe('API Error Cases', () => {
    it('should throw credentials_invalid on 401 Unauthorized', async () => {
      mockFetch.mockResolvedValue({
        ok: false,
        status: 401,
        statusText: 'Unauthorized',
        text: async () =>
          JSON.stringify({
            error: {
              type: 'authentication_error',
              message: 'Invalid API key'
            }
          })
      })

      try {
        await updateInterestStatus(
          { apiKey: 'invalid-key' },
          { lead_email: 'test@example.com', interest_value: -1 },
          testContext
        )
        // Should not reach here
        expect(true).toBe(false)
      } catch (error) {
        expect(error).toBeInstanceOf(ToolingError)
        expect((error as ToolingError).errorType).toBe('credentials_invalid')
        expect((error as ToolingError).message).toContain('Invalid API key')
      }
    })

    it('should throw credentials_invalid on 403 Forbidden', async () => {
      mockFetch.mockResolvedValue({
        ok: false,
        status: 403,
        statusText: 'Forbidden',
        text: async () =>
          JSON.stringify({
            message: 'Access denied'
          })
      })

      try {
        await updateInterestStatus(
          validCredentials,
          { lead_email: 'test@example.com', interest_value: -1 },
          testContext
        )
        expect(true).toBe(false)
      } catch (error) {
        expect(error).toBeInstanceOf(ToolingError)
        expect((error as ToolingError).errorType).toBe('credentials_invalid')
      }
    })

    it('should throw validation_error on 400 Bad Request', async () => {
      mockFetch.mockResolvedValue({
        ok: false,
        status: 400,
        statusText: 'Bad Request',
        text: async () =>
          JSON.stringify({
            error: {
              message: 'Invalid lead email format'
            }
          })
      })

      try {
        await updateInterestStatus(validCredentials, { lead_email: 'invalid-email', interest_value: -1 }, testContext)
        expect(true).toBe(false)
      } catch (error) {
        expect(error).toBeInstanceOf(ToolingError)
        expect((error as ToolingError).errorType).toBe('validation_error')
        expect((error as ToolingError).message).toContain('Invalid lead email format')
      }
    })

    it('should throw validation_error on 422 Unprocessable Entity', async () => {
      mockFetch.mockResolvedValue({
        ok: false,
        status: 422,
        statusText: 'Unprocessable Entity',
        text: async () =>
          JSON.stringify({
            error: {
              message: 'Lead not found in any campaign'
            }
          })
      })

      try {
        await updateInterestStatus(
          validCredentials,
          { lead_email: 'notfound@example.com', interest_value: -1 },
          testContext
        )
        expect(true).toBe(false)
      } catch (error) {
        expect(error).toBeInstanceOf(ToolingError)
        // 422 falls into 400-499 range which maps to validation_error
        expect((error as ToolingError).errorType).toBe('validation_error')
      }
    })

    it('should throw rate_limit_exceeded on 429 Too Many Requests', async () => {
      mockFetch.mockResolvedValue({
        ok: false,
        status: 429,
        statusText: 'Too Many Requests',
        text: async () =>
          JSON.stringify({
            message: 'Rate limit exceeded. Please try again later.'
          })
      })

      try {
        await updateInterestStatus(
          validCredentials,
          { lead_email: 'test@example.com', interest_value: -1 },
          testContext
        )
        expect(true).toBe(false)
      } catch (error) {
        expect(error).toBeInstanceOf(ToolingError)
        expect((error as ToolingError).errorType).toBe('api_error')
      }
    })

    it('should throw server_unavailable on 500 Internal Server Error', async () => {
      mockFetch.mockResolvedValue({
        ok: false,
        status: 500,
        statusText: 'Internal Server Error',
        text: async () => 'Internal server error'
      })

      try {
        await updateInterestStatus(
          validCredentials,
          { lead_email: 'test@example.com', interest_value: -1 },
          testContext
        )
        expect(true).toBe(false)
      } catch (error) {
        expect(error).toBeInstanceOf(ToolingError)
        expect((error as ToolingError).errorType).toBe('api_error')
      }
    })

    it('should throw server_unavailable on 503 Service Unavailable', async () => {
      mockFetch.mockResolvedValue({
        ok: false,
        status: 503,
        statusText: 'Service Unavailable',
        text: async () => 'Service temporarily unavailable'
      })

      try {
        await updateInterestStatus(
          validCredentials,
          { lead_email: 'test@example.com', interest_value: -1 },
          testContext
        )
        expect(true).toBe(false)
      } catch (error) {
        expect(error).toBeInstanceOf(ToolingError)
        expect((error as ToolingError).errorType).toBe('api_error')
      }
    })

    it('should handle non-JSON error responses', async () => {
      mockFetch.mockResolvedValue({
        ok: false,
        status: 500,
        statusText: 'Internal Server Error',
        text: async () => 'Plain text error message'
      })

      try {
        await updateInterestStatus(
          validCredentials,
          { lead_email: 'test@example.com', interest_value: -1 },
          testContext
        )
        expect(true).toBe(false)
      } catch (error) {
        expect(error).toBeInstanceOf(ToolingError)
        expect((error as ToolingError).message).toBe('Failed after 3 retries: Plain text error message')
      }
    })
  })

  describe('Request Body Construction', () => {
    it('should construct correct request body with all parameters', async () => {
      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => ({})
      })

      await updateInterestStatus(
        validCredentials,
        {
          lead_email: 'full@example.com',
          interest_value: -1,
          campaign_id: 'campaign-abc',
          disable_auto_interest: true
        },
        testContext
      )

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

      expect(requestBody).toEqual({
        lead_email: 'full@example.com',
        interest_value: -1,
        campaign_id: 'campaign-abc',
        disable_auto_interest: true
      })
    })

    it('should not include undefined optional parameters', async () => {
      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => ({})
      })

      await updateInterestStatus(
        validCredentials,
        {
          lead_email: 'minimal@example.com',
          interest_value: 0
        },
        testContext
      )

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

      expect(requestBody).toEqual({
        lead_email: 'minimal@example.com',
        interest_value: 0
      })
      expect(requestBody).not.toHaveProperty('campaign_id')
      expect(requestBody).not.toHaveProperty('disable_auto_interest')
    })

    it('should include disable_auto_interest when set to false', async () => {
      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => ({})
      })

      await updateInterestStatus(
        validCredentials,
        {
          lead_email: 'test@example.com',
          interest_value: 1,
          disable_auto_interest: false
        },
        testContext
      )

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

      expect(requestBody.disable_auto_interest).toBe(false)
    })
  })

  describe('Authorization Header', () => {
    it('should use Bearer token authorization', async () => {
      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => ({})
      })

      await updateInterestStatus(
        { apiKey: 'my-secret-api-key' },
        { lead_email: 'test@example.com', interest_value: 1 },
        testContext
      )

      const callArgs = mockFetch.mock.calls[0]
      expect(callArgs[1].headers).toEqual({
        Authorization: 'Bearer my-secret-api-key',
        'Content-Type': 'application/json'
      })
    })
  })
})
