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

describe('FCPClient', () => {
  let tempManager
  let recorder
  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()
    recorder = createRecorder('fcp-client')
    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()
    recorder.cleanup()
    process.env = originalEnv
    mockAgent.close()
    setGlobalDispatcher(originalDispatcher)
  })

  describe('constructor', () => {
    it('throws when FCP_API_KEY is missing', () => {
      delete process.env.FCP_API_KEY
      assert.throws(
        () => new FCPClient(),
        { message: 'FCP_API_KEY not set' }
      )
    })

    it('throws when FCP_PROJECT is missing', () => {
      delete process.env.FCP_PROJECT
      assert.throws(
        () => new FCPClient(),
        { message: 'FCP_PROJECT not set' }
      )
    })

    it('initializes with valid environment', () => {
      const client = new FCPClient()
      assert.equal(client.apiKey, 'test-api-key')
      assert.equal(client.project, 'test-project')
    })
  })

  describe('listAvailable', () => {
    it('returns formatted auction data', async () => {
      const mockResponse = {
        auctions: [
          {
            instance_type: 'gpu.a100.1x',
            region: 'us-west-2',
            gpu_count: 1,
            price: 25.60,
            quantity: 5
          },
          {
            instance_type: 'gpu.h100.2x',
            region: 'us-east-1',
            gpu_count: 2,
            price: 51.20,
            quantity: 3
          }
        ]
      }

      mockAPI.mockAvailability(mockResponse)

      const client = new FCPClient()
      const result = await client.listAvailable()

      assert.equal(result.length, 2)
      assert.deepEqual(result[0], {
        type: 'gpu.a100.1x',
        region: 'us-west-2',
        gpu: 1,
        price: '$25.60',
        available: 5
      })
      assert.deepEqual(result[1], {
        type: 'gpu.h100.2x',
        region: 'us-east-1',
        gpu: 2,
        price: '$51.20',
        available: 3
      })
    })

    it('handles empty auction list', async () => {
      mockAPI.mockEmptyAvailability()

      const client = new FCPClient()
      const result = await client.listAvailable()
      
      assert.equal(result.length, 0)
    })

    it('handles network errors', async () => {
      mockAPI.mockError('/v2/spot/availability', 'GET', new Error('Network error'))

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

  describe('createInstance', () => {
    it('creates instance with SSH key registration', async () => {
      const tempDir = tempManager.createTempDir()
      const { publicKeyPath, publicKey } = tempManager.createTestSSHKeys(tempDir)
      
      process.env.FCP_SSH_KEY = publicKeyPath

      const { bidId } = mockAPI.setupInstanceCreation({
        bidId: 'bid-789',
        instanceType: 'gpu.a100.1x'
      })

      const client = new FCPClient()
      const result = await client.createInstance('gpu.a100.1x')
      
      assert.equal(result, bidId)
    })

    it('reuses existing SSH key', async () => {
      const tempDir = tempManager.createTempDir()
      const { publicKeyPath, publicKey } = tempManager.createTestSSHKeys(tempDir)
      
      process.env.FCP_SSH_KEY = publicKeyPath
      const keyFingerprint = publicKey.split(' ')[1]

      // Mock existing SSH key
      mockAPI.mockSSHKeys({
        ssh_keys: [{
          id: 'existing-key-123',
          name: 'old-key',
          public_key: `ssh-rsa ${keyFingerprint} user@host`
        }]
      })

      // Mock availability
      mockAPI.mockAvailability({
        auctions: [{
          id: 'auction-456',
          instance_type: 'gpu.a100.1x',
          region: 'us-west-2'
        }]
      })

      // Mock bid creation
      mockAPI.mockJSONWithBody('/v2/spot/bids', 'POST', {
        project: 'test-project',
        auction: 'auction-456',
        launch_specification: {
          ssh_keys: ['existing-key-123']
        }
      }, { id: 'bid-789' }, 201)

      const client = new FCPClient()
      const bidId = await client.createInstance('gpu.a100.1x')
      
      assert.equal(bidId, 'bid-789')
    })

    it('throws when no availability', async () => {
      const tempDir = tempManager.createTempDir()
      const { publicKeyPath } = tempManager.createTestSSHKeys(tempDir)
      process.env.FCP_SSH_KEY = publicKeyPath

      mockAPI.mockSSHKeys()
      mockAPI.mockCreateSSHKey()
      mockAPI.mockEmptyAvailability()

      const client = new FCPClient()
      
      await assert.rejects(
        client.createInstance('gpu.a100.1x'),
        { message: 'No availability for gpu.a100.1x' }
      )
    })

    it('filters by region when specified', async () => {
      const tempDir = tempManager.createTempDir()
      const { publicKeyPath } = tempManager.createTestSSHKeys(tempDir)
      process.env.FCP_SSH_KEY = publicKeyPath

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

      const client = new FCPClient()
      const bidId = await client.createInstance('gpu.a100.1x', { region: 'us-east-1' })
      
      assert.equal(bidId, 'bid-789')
    })
  })

  describe('getInstance', () => {
    it('returns instance details', async () => {
      const mockBid = {
        id: 'bid-123',
        instance_type: 'gpu.a100.1x',
        status: 'RUNNING',
        region: 'us-west-2',
        created_at: '2024-01-01T00:00:00Z',
        instance: {
          status: 'RUNNING',
          public_ip: '54.123.45.67'
        }
      }

      mockAPI.mockBid('bid-123', mockBid)

      const client = new FCPClient()
      const result = await client.getInstance('bid-123')

      assert.deepEqual(result, {
        id: 'bid-123',
        type: 'gpu.a100.1x',
        status: 'RUNNING',
        region: 'us-west-2',
        ip: '54.123.45.67',
        created: '2024-01-01T00:00:00Z'
      })
    })

    it('handles pending instance without IP', async () => {
      const mockBid = {
        id: 'bid-123',
        instance_type: 'gpu.a100.1x',
        status: 'PENDING',
        region: 'us-west-2',
        created_at: '2024-01-01T00:00:00Z'
      }

      mockAPI.mockBid('bid-123', mockBid)

      const client = new FCPClient()
      const result = await client.getInstance('bid-123')

      assert.equal(result.id, 'bid-123')
      assert.equal(result.status, 'PENDING')
      assert.equal(result.ip, null)
    })

    it('returns NOT_FOUND status for 404', async () => {
      mockAPI.mockJSON(`/v2/spot/bids/bid-123`, 'GET', { message: 'Bid not found' }, 404)

      const client = new FCPClient()
      const result = await client.getInstance('bid-123')

      assert.deepEqual(result, {
        id: 'bid-123',
        status: 'NOT_FOUND'
      })
    })

    it('throws on other errors', async () => {
      mockAPI.mockJSON(`/v2/spot/bids/bid-123`, 'GET', { message: 'Internal server error' }, 500)

      const client = new FCPClient()
      
      await assert.rejects(
        client.getInstance('bid-123'),
        error => error.status === 500
      )
    })
  })

  describe('deleteInstance', () => {
    it('returns true on successful deletion', async () => {
      mockAPI.mockDeleteBid('bid-123')

      const client = new FCPClient()
      const result = await client.deleteInstance('bid-123')
      
      assert.equal(result, true)
    })

    it('returns false for non-existent instance', async () => {
      mockAPI.mockJSON(`/v2/spot/bids/bid-123`, 'DELETE', { message: 'Bid not found' }, 404)

      const client = new FCPClient()
      const result = await client.deleteInstance('bid-123')
      
      assert.equal(result, false)
    })

    it('throws on other errors', async () => {
      mockAPI.mockJSON(`/v2/spot/bids/bid-123`, 'DELETE', { message: 'Forbidden' }, 403)

      const client = new FCPClient()
      
      await assert.rejects(
        client.deleteInstance('bid-123'),
        error => error.status === 403
      )
    })
  })

  describe('listMyInstances', () => {
    it('filters active instances', async () => {
      const mockBids = {
        bids: [
          {
            id: 'bid-1',
            instance_type: 'gpu.a100.1x',
            status: 'RUNNING',
            region: 'us-west-2'
          },
          {
            id: 'bid-2',
            instance_type: 'gpu.h100.2x',
            status: 'TERMINATED',
            region: 'us-east-1'
          },
          {
            id: 'bid-3',
            instance_type: 'gpu.a100.1x',
            status: 'PENDING',
            region: 'us-west-2'
          }
        ]
      }

      mockAPI.mockBidsList(mockBids)

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

      assert.equal(result.length, 2)
      assert.equal(result[0].id, 'bid-1')
      assert.equal(result[0].status, 'RUNNING')
      assert.equal(result[1].id, 'bid-3')
      assert.equal(result[1].status, 'PENDING')
    })

    it('handles missing fields gracefully', async () => {
      const mockBids = {
        bids: [
          {
            id: 'bid-1',
            status: 'RUNNING'
            // Missing instance_type and region
          }
        ]
      }

      mockAPI.mockBidsList(mockBids)

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

      assert.equal(result.length, 1)
      assert.equal(result[0].type, 'N/A')
      assert.equal(result[0].region, 'N/A')
    })
  })
})