import { describe, it, beforeEach, afterEach } from 'node:test'
import assert from 'node:assert'
import { FCPClient } from '../../client.js'
import { createTempFileManager } from '../helpers/temp-files.js'
import { getFixture } from '../fixtures/api-responses.js'
import { MockAgent, setGlobalDispatcher, getGlobalDispatcher } from 'undici'
import { createMockAPI } from '../helpers/mock-api.js'

describe('API Integration Tests', () => {
  let tempManager
  let originalEnv
  let mockAgent
  let mockAPI
  let originalDispatcher

  beforeEach(() => {
    // Save original dispatcher
    originalDispatcher = getGlobalDispatcher()
    
    // Set up undici mock agent
    mockAgent = new MockAgent()
    setGlobalDispatcher(mockAgent)
    mockAgent.disableNetConnect()
    
    // Create mock API helper
    mockAPI = createMockAPI(mockAgent)
    
    tempManager = createTempFileManager()
    originalEnv = { ...process.env }
    
    // Set test environment
    process.env.NODE_ENV = 'test'
    process.env.FCP_API_KEY = 'test-api-key'
    process.env.FCP_PROJECT = 'test-project'
  })

  afterEach(() => {
    tempManager.cleanup()
    process.env = originalEnv
    mockAgent.close()
    setGlobalDispatcher(originalDispatcher)
  })

  describe('Full instance lifecycle', () => {
    it('creates, monitors, and deletes an instance', async () => {
      const tempDir = tempManager.createTempDir()
      const { publicKeyPath } = tempManager.createTestSSHKeys(tempDir)
      process.env.FCP_SSH_KEY = publicKeyPath

      // 1. Check SSH keys (empty)
      mockAPI.mockSSHKeys()

      // 2. Create SSH key
      mockAPI.mockCreateSSHKey('key_new_123')

      // 3. Get availability
      mockAPI.mockAvailability()

      // 4. Create bid
      mockAPI.mockCreateBid(getFixture('bids.created'))

      // 5. Check status (pending)
      mockAPI.mockBid('bid_new_123', getFixture('bids.created'))

      // 6. Check status (provisioning)
      mockAPI.mockBid('bid_new_123', {
        ...getFixture('bids.provisioning'),
        id: 'bid_new_123'
      })

      // 7. Check status (running)
      mockAPI.mockBid('bid_new_123', {
        ...getFixture('bids.running'),
        id: 'bid_new_123'
      })

      // 8. Delete instance
      mockAPI.mockDeleteBid('bid_new_123')

      const client = new FCPClient()

      // Create instance
      const bidId = await client.createInstance('gpu.a100.1x', { region: 'us-west-2' })
      assert.equal(bidId, 'bid_new_123')

      // Check initial status
      let status = await client.getInstance(bidId)
      assert.equal(status.status, 'PENDING')
      assert.equal(status.ip, null)

      // Check provisioning status
      status = await client.getInstance(bidId)
      assert.equal(status.status, 'PROVISIONING')

      // Check running status
      status = await client.getInstance(bidId)
      assert.equal(status.status, 'RUNNING')
      assert.equal(status.ip, '54.123.45.67')

      // Delete instance
      const deleted = await client.deleteInstance(bidId)
      assert.equal(deleted, true)
    })
  })

  describe('Error handling and recovery', () => {
    it('handles API rate limiting', async () => {
      // First request gets rate limited (no retries in test mode)
      mockAPI.mockJSON('/v2/spot/availability', 'GET', getFixture('errors.rateLimited'), 429)

      const client = new FCPClient()
      
      await assert.rejects(
        client.listAvailable(),
        error => error.status === 429
      )
    })

    it('handles network errors', async () => {
      // No retries in test mode
      mockAPI.mockError('/v2/spot/availability', 'GET', new Error('ECONNRESET'))

      const client = new FCPClient()
      
      await assert.rejects(
        client.listAvailable(),
        { message: 'fetch failed' }
      )
    })

    it('handles server errors', async () => {
      mockAPI.mockJSON('/v2/spot/availability', 'GET', getFixture('errors.serverError'), 500)

      const client = new FCPClient()
      
      await assert.rejects(
        client.listAvailable(),
        error => error.status === 500
      )
    })
  })

  describe('Concurrent operations', () => {
    it('handles multiple simultaneous requests', async () => {
      const bidIds = ['bid_1', 'bid_2', 'bid_3']
      
      // Mock responses for each bid
      bidIds.forEach(id => {
        mockAPI.mockBid(id, {
          ...getFixture('bids.running'),
          id,
          instance: {
            ...getFixture('bids.running').instance,
            public_ip: `54.123.45.${id.split('_')[1]}`
          }
        })
      })

      const client = new FCPClient()
      
      // Make concurrent requests
      const results = await Promise.all(
        bidIds.map(id => client.getInstance(id))
      )

      assert.equal(results.length, 3)
      results.forEach((result, index) => {
        assert.equal(result.id, bidIds[index])
        assert.equal(result.status, 'RUNNING')
        assert(result.ip.startsWith('54.123.45.'))
      })
    })
  })

  describe('Region-specific operations', () => {
    it('filters instances by region correctly', async () => {
      mockAPI.mockAvailability()

      const client = new FCPClient()
      const all = await client.listAvailable()
      
      // Verify we have instances in multiple regions
      const westInstances = all.filter(i => i.region === 'us-west-2')
      const eastInstances = all.filter(i => i.region === 'us-east-1')
      
      assert(westInstances.length > 0)
      assert(eastInstances.length > 0)
      
      // Verify proper filtering in createInstance
      const tempDir = tempManager.createTempDir()
      const { publicKeyPath } = tempManager.createTestSSHKeys(tempDir)
      process.env.FCP_SSH_KEY = publicKeyPath

      mockAPI.setupInstanceCreationWithRegion('gpu.a100.1x', 'us-east-1')

      await client.createInstance('gpu.a100.1x', { region: 'us-east-1' })
    })
  })

  describe('Instance listing and filtering', () => {
    it('correctly filters active instances', async () => {
      mockAPI.mockBidsList()

      const client = new FCPClient()
      const instances = await client.listMyInstances()

      // Should only return PENDING and RUNNING instances
      assert.equal(instances.length, 2)
      
      const statuses = instances.map(i => i.status)
      assert(statuses.includes('RUNNING'))
      assert(statuses.includes('PENDING'))
      assert(!statuses.includes('TERMINATED'))
      assert(!statuses.includes('FAILED'))
    })

    it('handles empty instance list', async () => {
      mockAPI.mockBidsList(getFixture('bidsList.empty'))

      const client = new FCPClient()
      const instances = await client.listMyInstances()

      assert.equal(instances.length, 0)
    })
  })

  describe('SSH key management', () => {
    it('deduplicates SSH keys by fingerprint', async () => {
      const tempDir = tempManager.createTempDir()
      const { publicKeyPath, publicKey } = tempManager.createTestSSHKeys(tempDir)
      process.env.FCP_SSH_KEY = publicKeyPath

      // Mock existing keys with same fingerprint
      mockAPI.mockSSHKeys(getFixture('sshKeys.withKeys'))

      // Should NOT create a new key
      mockAPI.mockAvailability(getFixture('availability.limited'))

      mockAPI.mockJSONWithBody('/v2/spot/bids', 'POST', {
        project: 'test-project',
        auction: 'auction_h100_limited',
        launch_specification: {
          ssh_keys: ['key_existing']
        }
      }, getFixture('bids.created'), 201)

      const client = new FCPClient()
      await client.createInstance('gpu.h100.8x')
    })
  })
})