import { describe, it, beforeEach, afterEach } from 'node:test'
import assert from 'node:assert'
import { spawn } from 'child_process'
import { createTempFileManager } from '../helpers/temp-files.js'
import { createCLIRunner } from '../helpers/cli-runner.js'
import { getFixture } from '../fixtures/api-responses.js'
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs'
import { join } from 'path'
import { homedir } from 'os'
import { createMockServer } from '../helpers/mock-server.js'

describe('SSH Integration Tests', () => {
  let tempManager
  let cliRunner
  let originalEnv
  let originalPath
  let mockServer
  let mockBaseUrl

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

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

  describe('SSH command construction', () => {
    it('builds correct SSH command with all options', async () => {
      const tempDir = tempManager.createTempDir()
      const mockSSHPath = tempManager.createMockSSHBinary(tempDir)
      const { privateKeyPath } = tempManager.createTestSSHKeys(tempDir)
      
      // Add mock SSH to PATH
      process.env.PATH = `${tempDir}:${originalPath}`
      process.env.FCP_SSH_KEY = privateKeyPath

      // Create a capture script that logs SSH arguments
      const captureScript = `#!/usr/bin/env node
const fs = require('fs');
const args = process.argv.slice(2);
fs.writeFileSync('${join(tempDir, 'ssh-args.json')}', JSON.stringify(args));
console.log('Mock SSH executed');
process.exit(0);
`
      writeFileSync(mockSSHPath, captureScript, { mode: 0o755 })

      // Mock API response for running instance
      mockServer.mock('GET', '/v2/spot/bids/bid-123', getFixture('bids.running'))

      // Run SSH command
      const result = await cliRunner.run(['ssh', 'bid-123'], { FCP_API_URL: mockBaseUrl })
      
      assert.equal(result.exitCode, 0)
      
      // Read captured SSH arguments
      const capturedArgs = JSON.parse(
        readFileSync(join(tempDir, 'ssh-args.json'), 'utf8')
      )

      // Verify SSH arguments
      assert(capturedArgs.includes('-o'))
      assert(capturedArgs.includes('StrictHostKeyChecking=no'))
      assert(capturedArgs.includes('UserKnownHostsFile=/dev/null'))
      assert(capturedArgs.includes('ConnectTimeout=10'))
      assert(capturedArgs.includes('-i'))
      assert(capturedArgs.includes(privateKeyPath))
      assert(capturedArgs.includes('ubuntu@54.123.45.67'))
    })

    it('uses custom SSH key when specified', async () => {
      const tempDir = tempManager.createTempDir()
      const mockSSHPath = tempManager.createMockSSHBinary(tempDir)
      const { privateKeyPath } = tempManager.createTestSSHKeys(tempDir)
      
      // Create custom key
      const customKeyPath = join(tempDir, 'custom_key')
      writeFileSync(customKeyPath, 'custom-key-content', { mode: 0o600 })
      
      process.env.PATH = `${tempDir}:${originalPath}`

      // Create capture script
      const captureScript = `#!/usr/bin/env node
const fs = require('fs');
const args = process.argv.slice(2);
fs.writeFileSync('${join(tempDir, 'ssh-args.json')}', JSON.stringify(args));
process.exit(0);
`
      writeFileSync(mockSSHPath, captureScript, { mode: 0o755 })

      // Mock API response
      mockServer.mock('GET', '/v2/spot/bids/bid-123', getFixture('bids.running'))

      // Run with custom key
      const result = await cliRunner.run(['ssh', 'bid-123', '--key', customKeyPath], { FCP_API_URL: mockBaseUrl })
      
      assert.equal(result.exitCode, 0)
      
      // Verify custom key was used
      const capturedArgs = JSON.parse(
        readFileSync(join(tempDir, 'ssh-args.json'), 'utf8')
      )
      
      const keyIndex = capturedArgs.indexOf('-i')
      assert(keyIndex !== -1)
      assert.equal(capturedArgs[keyIndex + 1], customKeyPath)
    })
  })

  describe('SSH error handling', () => {
    it('handles instance without IP address', async () => {
      // Mock API response for pending instance
      mockServer.mock('GET', '/v2/spot/bids/bid-pending', getFixture('bids.created'))

      const result = await cliRunner.runFailure(['ssh', 'bid-pending'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stderr.includes('has no IP address yet'))
      assert(result.exitCode !== 0)
    })

    it('handles SSH connection timeout', async () => {
      const tempDir = tempManager.createTempDir()
      
      // Create a mock SSH that simulates timeout
      const mockSSHPath = join(tempDir, 'ssh')
      const timeoutScript = `#!/usr/bin/env node
setTimeout(() => {
  console.error('ssh: connect to host 54.123.45.67 port 22: Operation timed out');
  process.exit(255);
}, 100);
`
      writeFileSync(mockSSHPath, timeoutScript, { mode: 0o755 })
      
      process.env.PATH = `${tempDir}:${originalPath}`

      // Mock API response
      mockServer.mock('GET', '/v2/spot/bids/bid-123', getFixture('bids.running'))

      const result = await cliRunner.runFailure(['ssh', 'bid-123'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stderr.includes('Operation timed out'))
      assert.equal(result.exitCode, 255)
    })

    it('handles SSH authentication failure', async () => {
      const tempDir = tempManager.createTempDir()
      
      // Create a mock SSH that simulates auth failure
      const mockSSHPath = join(tempDir, 'ssh')
      const authFailScript = `#!/usr/bin/env node
console.error('Permission denied (publickey).');
process.exit(255);
`
      writeFileSync(mockSSHPath, authFailScript, { mode: 0o755 })
      
      process.env.PATH = `${tempDir}:${originalPath}`

      // Mock API response
      mockServer.mock('GET', '/v2/spot/bids/bid-123', getFixture('bids.running'))

      const result = await cliRunner.runFailure(['ssh', 'bid-123'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stderr.includes('Permission denied'))
      assert.equal(result.exitCode, 255)
    })

    it('handles instance not found', async () => {
      // Mock 404 response
      mockServer.mock('GET', '/v2/spot/bids/bid-nonexistent', getFixture('errors.notFound'), 404)

      const result = await cliRunner.runFailure(['ssh', 'bid-nonexistent'], { FCP_API_URL: mockBaseUrl })
      
      assert(result.stderr.includes('not found') || result.stderr.includes('Not found'))
      assert(result.exitCode !== 0)
    })
  })

  describe('Interactive SSH session', () => {
    it('handles interactive session properly', async () => {
      const tempDir = tempManager.createTempDir()
      
      // Create a simple mock SSH that exits immediately
      const mockSSHPath = join(tempDir, 'ssh')
      const simpleScript = `#!/usr/bin/env node
console.log('Connected to instance');
process.exit(0);
`
      writeFileSync(mockSSHPath, simpleScript, { mode: 0o755 })
      
      process.env.PATH = `${tempDir}:${originalPath}`

      // Mock API response
      mockServer.mock('GET', '/v2/spot/bids/bid-123', getFixture('bids.running'))

      // Just verify the SSH command can be invoked successfully
      const result = await cliRunner.run(['ssh', 'bid-123'], { FCP_API_URL: mockBaseUrl })
      
      assert.equal(result.exitCode, 0)
      assert(result.stdout.includes('Connected to instance'))
    })
  })

  describe('SSH key file handling', () => {
    it('falls back to default SSH key locations', async () => {
      const tempDir = tempManager.createTempDir()
      const mockSSHPath = tempManager.createMockSSHBinary(tempDir)
      
      // Create SSH keys in default location
      const homeSSHDir = join(tempDir, '.ssh')
      mkdirSync(homeSSHDir, { recursive: true })
      const defaultKeyPath = join(homeSSHDir, 'id_rsa')
      writeFileSync(defaultKeyPath, 'default-key', { mode: 0o600 })
      
      process.env.PATH = `${tempDir}:${originalPath}`
      process.env.HOME = tempDir
      delete process.env.FCP_SSH_KEY

      // Create capture script
      const captureScript = `#!/usr/bin/env node
const fs = require('fs');
const args = process.argv.slice(2);
fs.writeFileSync('${join(tempDir, 'ssh-args.json')}', JSON.stringify(args));
process.exit(0);
`
      writeFileSync(mockSSHPath, captureScript, { mode: 0o755 })

      // Mock API response
      mockServer.mock('GET', '/v2/spot/bids/bid-123', getFixture('bids.running'))

      const result = await cliRunner.run(['ssh', 'bid-123'], { FCP_API_URL: mockBaseUrl })
      
      assert.equal(result.exitCode, 0)
      
      // Verify default key was used
      const capturedArgs = JSON.parse(
        readFileSync(join(tempDir, 'ssh-args.json'), 'utf8')
      )
      
      const keyIndex = capturedArgs.indexOf('-i')
      assert(keyIndex !== -1)
      assert(capturedArgs[keyIndex + 1].endsWith('id_rsa'))
    })

    it('validates SSH key file exists', async () => {
      const tempDir = tempManager.createTempDir()
      process.env.PATH = `${tempDir}:${originalPath}`

      // Mock API response
      mockServer.mock('GET', '/v2/spot/bids/bid-123', getFixture('bids.running'))

      // Try to SSH with non-existent key file
      const result = await cliRunner.runFailure([
        'ssh', 'bid-123', 
        '--key', '/nonexistent/key/path'
      ], { FCP_API_URL: mockBaseUrl })
      
      // Note: The actual error would depend on SSH binary behavior
      // Our mock won't catch this, but real SSH would fail
      assert(result.exitCode !== 0)
    })
  })
})