import { describe, it, beforeEach, afterEach } from 'node:test'
import assert from 'node:assert'
import { createCLIRunner } from '../helpers/cli-runner.js'
import { createTempFileManager } from '../helpers/temp-files.js'
import { getFixture } from '../fixtures/api-responses.js'
import { createMockServer } from '../helpers/mock-server.js'

describe('CLI End-to-End Tests', () => {
  let cliRunner
  let tempManager
  let originalEnv
  let mockServer
  let mockBaseUrl

  beforeEach(async () => {
    cliRunner = createCLIRunner()
    tempManager = createTempFileManager()
    originalEnv = { ...process.env }
    
    // Create and start mock server
    const mock = await createMockServer()
    mockServer = mock.server
    mockBaseUrl = mock.baseUrl
    
    // Set test environment
    process.env.NODE_ENV = 'test'
    process.env.FCP_API_KEY = 'test-api-key'
    process.env.FCP_PROJECT = 'test-project'
    process.env.FCP_API_URL = mockBaseUrl
    
    // Set up common mocks
    mockServer.setupCommonMocks()
  })

  afterEach(async () => {
    process.env = originalEnv
    tempManager.cleanup()
    await mockServer.stop()
  })

  describe('Environment validation', () => {
    it('shows error when FCP_API_KEY is missing', async () => {
      delete process.env.FCP_API_KEY
      
      const result = await cliRunner.runFailure(['list'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stderr.includes('FCP_API_KEY not set'))
      assert(result.stderr.includes('export FCP_API_KEY='))
      assert.equal(result.exitCode, 1)
    })

    it('shows error when FCP_PROJECT is missing', async () => {
      delete process.env.FCP_PROJECT
      
      const result = await cliRunner.runFailure(['list'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stderr.includes('FCP_PROJECT not set'))
      assert(result.stderr.includes('export FCP_PROJECT='))
      assert.equal(result.exitCode, 1)
    })
  })

  describe('Help and usage', () => {
    it('shows help when no command provided', async () => {
      const result = await cliRunner.run([])
      
      const output = result.stdout + result.stderr
      assert(output.includes('mithril'))
      assert(output.includes('Usage:'))
      assert(output.includes('Commands:'))
      assert(output.includes('list'))
      assert(output.includes('create'))
      assert(output.includes('status'))
      assert(output.includes('ssh'))
      assert(output.includes('delete'))
    })

    it('shows version', async () => {
      const result = await cliRunner.runSuccess(['--version'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stdout.includes('0.1.0'))
    })

    it('shows command-specific help', async () => {
      const result = await cliRunner.runSuccess(['create', '--help'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stdout.includes('<type>'))
      assert(result.stdout.includes('-r, --region'))
    })
  })

  describe('List command', () => {
    it('lists available instances', async () => {
      mockServer.mock('GET', '/v2/spot/availability', getFixture('availability.withInstances'))

      const result = await cliRunner.runSuccess(['list'], {
        FCP_API_URL: mockBaseUrl
      })
      
      // Verify table output
      assert(result.stdout.includes('TYPE'))
      assert(result.stdout.includes('REGION'))
      assert(result.stdout.includes('GPU'))
      assert(result.stdout.includes('PRICE'))
      assert(result.stdout.includes('AVAILABLE'))
      
      // Verify data
      assert(result.stdout.includes('gpu.a100.1x'))
      assert(result.stdout.includes('gpu.h100.2x'))
      assert(result.stdout.includes('us-west-2'))
      assert(result.stdout.includes('us-east-1'))
      assert(result.stdout.includes('$25.60'))
    })

    it('filters available instances with --available flag', async () => {
      const mixedAvailability = {
        auctions: [
          { ...getFixture('availability.withInstances').auctions[0], quantity: 0 },
          { ...getFixture('availability.withInstances').auctions[1], quantity: 5 }
        ]
      }

      mockServer.mock('GET', '/v2/spot/availability', mixedAvailability)

      const result = await cliRunner.runSuccess(['list', '--available'], { FCP_API_URL: mockBaseUrl })
      
      // Should only show instances with quantity > 0
      assert(!result.stdout.includes('gpu.a100.1x'))
      assert(result.stdout.includes('gpu.h100.2x'))
    })

    it('lists user instances with --mine flag', async () => {
      mockServer.mock('GET', '/v2/spot/bids?project=test-project', getFixture('bidsList.mixed'), 200)

      const result = await cliRunner.runSuccess(['list', '--mine'], { FCP_API_URL: mockBaseUrl })
      
      // Verify table headers
      assert(result.stdout.includes('ID'))
      assert(result.stdout.includes('TYPE'))
      assert(result.stdout.includes('STATUS'))
      assert(result.stdout.includes('REGION'))
      
      // Verify only active instances shown
      assert(result.stdout.includes('bid_running_1'))
      assert(result.stdout.includes('bid_pending_2'))
      assert(!result.stdout.includes('bid_terminated_3'))
      assert(!result.stdout.includes('bid_failed_4'))
    })

    it('handles empty results gracefully', async () => {
      mockServer.mock('GET', '/v2/spot/availability', getFixture('availability.empty'), 200)

      const result = await cliRunner.runSuccess(['list'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stdout.includes('No results'))
    })

    it('handles API errors', async () => {
      const serverError = getFixture('errors.serverError')
      mockServer.mock('GET', '/v2/spot/availability', serverError.body, serverError.status)

      const result = await cliRunner.runFailure(['list'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stderr.includes('Error: Internal server error'))
      assert.equal(result.exitCode, 1)
    })
  })

  describe('Create command', () => {
    it('creates instance successfully', async () => {
      const tempDir = tempManager.createTempDir()
      const { publicKeyPath } = tempManager.createTestSSHKeys(tempDir)
      process.env.FCP_SSH_KEY = publicKeyPath

      // Mock the full creation flow
      mockServer.mock('GET', '/v2/spot/availability', getFixture('availability.withInstances'), 200)

      mockServer.mock('GET', '/v2/projects/test-project/ssh-keys', getFixture('sshKeys.empty'), 200)

      mockServer.mock('POST', '/v2/projects/test-project/ssh-keys', { id: 'key_new' }, 201)

      mockServer.mock('GET', '/v2/spot/availability', getFixture('availability.withInstances'), 200)

      mockServer.mock('POST', '/v2/spot/bids', getFixture('bids.created'), 201)

      const result = await cliRunner.runSuccess(['create', 'gpu.a100.1x'], { FCP_API_URL: mockBaseUrl })
      
      // Verify output (spinner output goes to stderr)
      assert(result.stderr.includes('✓ Found ') && result.stderr.includes('gpu.a100.1x'))
      assert(result.stderr.includes('us-west-2'))
      assert(result.stderr.includes('$25.60/hr'))
      assert(result.stderr.includes('✓ Bid accepted!'))
      assert(result.stdout.includes('bid_new_123'))
      assert(result.stdout.includes('PROVISIONING'))
      assert(result.stdout.includes('8-12 minutes'))
      assert(result.stdout.includes('mithril status'))
    })

    it('creates instance in specific region', async () => {
      const tempDir = tempManager.createTempDir()
      const { publicKeyPath } = tempManager.createTestSSHKeys(tempDir)
      process.env.FCP_SSH_KEY = publicKeyPath

      mockServer.mock('GET', '/v2/spot/availability', getFixture('availability.withInstances'), 200)

      mockServer.mock('GET', '/v2/projects/test-project/ssh-keys', getFixture('sshKeys.empty'), 200)

      mockServer.mock('POST', '/v2/projects/test-project/ssh-keys', { id: 'key_new' }, 201)

      mockServer.mock('GET', '/v2/spot/availability', getFixture('availability.withInstances'), 200)

      mockServer.mock('POST', '/v2/spot/bids', getFixture('bids.created'), 201)

      const result = await cliRunner.runSuccess([
        'create', 'gpu.a100.1x',
        '--region', 'us-east-1'
      ], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stderr.includes('us-east-1'))
    })

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

      mockServer.mock('GET', '/v2/spot/availability', getFixture('availability.empty'), 200)

      const result = await cliRunner.runFailure(['create', 'gpu.a100.1x'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stderr.includes('No availability for gpu.a100.1x'))
      assert(result.stdout.includes('mithril list --available'))
      assert.equal(result.exitCode, 1)
    })
  })

  describe('Status command', () => {
    it('shows running instance status', async () => {
      mockServer.mock('GET', '/v2/spot/bids/bid-123', getFixture('bids.running'), 200)

      const result = await cliRunner.runSuccess(['status', 'bid-123'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stdout.includes('Instance: bid-123'))
      assert(result.stdout.includes('Type: gpu.a100.1x'))
      assert(result.stdout.includes('Status: RUNNING'))
      assert(result.stdout.includes('IP: 54.123.45.67'))
      assert(result.stdout.includes('SSH: ubuntu@54.123.45.67'))
      assert(result.stdout.includes('Region: us-west-2'))
    })

    it('shows provisioning status', async () => {
      mockServer.mock('GET', '/v2/spot/bids/bid-prov', getFixture('bids.provisioning'), 200)

      const result = await cliRunner.runSuccess(['status', 'bid-prov'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stdout.includes('Status: PROVISIONING'))
      assert(result.stdout.includes('Progress:'))
      assert(result.stdout.includes('Estimated remaining:'))
    })

    it('handles not found instance', async () => {
      const notFound = getFixture('errors.notFound')
      mockServer.mock('GET', '/v2/spot/bids/bid-missing', notFound.body, notFound.status)

      const result = await cliRunner.runSuccess(['status', 'bid-missing'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stdout.includes('Status: NOT FOUND'))
    })
  })

  describe('Delete command', () => {
    it('deletes instance successfully', async () => {
      mockServer.mock('DELETE', '/v2/spot/bids/bid-123', {}, 204)

      const result = await cliRunner.runSuccess(['delete', 'bid-123'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stderr.includes('✓ Instance deleted: ') && result.stderr.includes('bid-123'))
    })

    it('handles deleting non-existent instance', async () => {
      mockServer.mock('DELETE', '/v2/spot/bids/bid-missing', {}, 404)

      const result = await cliRunner.runSuccess(['delete', 'bid-missing'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stderr.includes('Instance not found: bid-missing'))
    })

    it('handles deletion errors', async () => {
      const unauthorized = getFixture('errors.unauthorized')
      mockServer.mock('DELETE', '/v2/spot/bids/bid-123', unauthorized.body, unauthorized.status)

      const result = await cliRunner.runFailure(['delete', 'bid-123'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stderr.includes('Error:'))
      assert.equal(result.exitCode, 1)
    })
  })

  describe('Full workflow', () => {
    it('completes create -> status -> delete lifecycle', async () => {
      const tempDir = tempManager.createTempDir()
      const { publicKeyPath } = tempManager.createTestSSHKeys(tempDir)
      process.env.FCP_SSH_KEY = publicKeyPath

      // 1. List available
      mockServer.mock('GET', '/v2/spot/availability', getFixture('availability.withInstances'), 200)

      let result = await cliRunner.runSuccess(['list'], { FCP_API_URL: mockBaseUrl })
      assert(result.stdout.includes('gpu.a100.1x'))

      // 2. Create instance
      mockServer.mock('GET', '/v2/spot/availability', getFixture('availability.withInstances'), 200)

      mockServer.mock('GET', '/v2/projects/test-project/ssh-keys', getFixture('sshKeys.empty'), 200)

      mockServer.mock('POST', '/v2/projects/test-project/ssh-keys', { id: 'key_new' }, 201)

      mockServer.mock('GET', '/v2/spot/availability', getFixture('availability.withInstances'), 200)

      mockServer.mock('POST', '/v2/spot/bids', getFixture('bids.created'), 201)

      result = await cliRunner.runSuccess(['create', 'gpu.a100.1x'], { FCP_API_URL: mockBaseUrl })
      assert(result.stdout.includes('bid_new_123'))

      // 3. Check status
      mockServer.mock('GET', '/v2/spot/bids/bid_new_123', {
        ...getFixture('bids.running'),
        id: 'bid_new_123'
      }, 200)

      result = await cliRunner.runSuccess(['status', 'bid_new_123'], { FCP_API_URL: mockBaseUrl })
      assert(result.stdout.includes('RUNNING'))

      // 4. List my instances
      mockServer.mock('GET', '/v2/spot/bids?project=test-project', {
        bids: [{
          id: 'bid_new_123',
          status: 'RUNNING',
          instance_type: 'gpu.a100.1x',
          region: 'us-west-2'
        }]
      }, 200)

      result = await cliRunner.runSuccess(['list', '--mine'], { FCP_API_URL: mockBaseUrl })
      assert(result.stdout.includes('bid_new_123'))

      // 5. Delete instance
      mockServer.mock('DELETE', '/v2/spot/bids/bid_new_123', {}, 204)

      result = await cliRunner.runSuccess(['delete', 'bid_new_123'], { FCP_API_URL: mockBaseUrl })
      assert(result.stderr.includes('✓ Instance deleted'))
    })
  })
})