const { describe, test, expect, beforeEach, afterEach } = require('bun:test')
const crypto = require('crypto')
const path = require('path')
const os = require('os')

const DEFAULT_CONFIG = {
  wpHost: 'test.example.com',
  wpSshUser: 'wordpress',
  wpSshKey: '/home/user/.ssh/id_rsa',
  wpPath: '/var/www/html',
  wpBaseUrl: 'https://test.example.com',
  enableMultisite: true,
  subsitePrefix: 'test',
  subsiteDomain: '',
  subsiteMode: 'subdirectory',
  maxConcurrentTests: 20,
  queueTimeoutMs: 300000,
  testTimeoutMs: 600000,
  autoCleanup: true,
  cleanupDelayMs: 3600000,
}

function expandHome(p) {
  if (!p || typeof p !== 'string') return p
  if (p.startsWith('~')) {
    return path.join(os.homedir(), p.slice(1))
  }
  return p
}

function getExternalTestingConfig(overrides = {}) {
  const config = { ...DEFAULT_CONFIG, ...(overrides || {}) }
  config.wpSshKey = expandHome(config.wpSshKey)
  config.wpBaseUrl = (config.wpBaseUrl || '').trim()
  if (!config.wpBaseUrl && config.wpHost) {
    config.wpBaseUrl = `https://${config.wpHost}`
  }
  config.maxConcurrentTests = Number.isFinite(config.maxConcurrentTests) && config.maxConcurrentTests > 0
    ? Math.floor(config.maxConcurrentTests)
    : 1
  config.queueTimeoutMs = Number.isFinite(config.queueTimeoutMs) && config.queueTimeoutMs > 0
    ? Math.floor(config.queueTimeoutMs)
    : 300000
  config.testTimeoutMs = Number.isFinite(config.testTimeoutMs) && config.testTimeoutMs > 0
    ? Math.floor(config.testTimeoutMs)
    : 600000
  config.cleanupDelayMs = Number.isFinite(config.cleanupDelayMs) && config.cleanupDelayMs >= 0
    ? Math.floor(config.cleanupDelayMs)
    : 3600000
  config.subsiteMode = config.subsiteMode === 'subdomain' ? 'subdomain' : 'subdirectory'
  return config
}

describe('External WordPress Testing', () => {
  describe('Configuration Management', () => {
    test('provides default configuration', () => {
      const config = getExternalTestingConfig()

      expect(config.wpHost).toBe('test.example.com')
      expect(config.wpSshUser).toBe('wordpress')
      expect(config.enableMultisite).toBe(true)
      expect(config.maxConcurrentTests).toBe(20)
    })

    test('overrides configuration values', () => {
      const config = getExternalTestingConfig({
        wpHost: 'custom.example.com',
        maxConcurrentTests: 10,
      })

      expect(config.wpHost).toBe('custom.example.com')
      expect(config.maxConcurrentTests).toBe(10)
      expect(config.wpSshUser).toBe('wordpress')
    })

    test('expands home directory in SSH key path', () => {
      const config = getExternalTestingConfig({
        wpSshKey: '~/.ssh/custom_key',
      })

      expect(config.wpSshKey).toContain('.ssh')
      expect(config.wpSshKey).toContain('custom_key')
    })

    test('generates base URL from host if not provided', () => {
      const config = getExternalTestingConfig({
        wpHost: 'mytest.com',
        wpBaseUrl: '',
      })

      expect(config.wpBaseUrl).toBe('https://mytest.com')
    })

    test('normalizes subsite mode', () => {
      expect(getExternalTestingConfig({ subsiteMode: 'subdomain' }).subsiteMode).toBe('subdomain')
      expect(getExternalTestingConfig({ subsiteMode: 'subdirectory' }).subsiteMode).toBe('subdirectory')
      expect(getExternalTestingConfig({ subsiteMode: 'invalid' }).subsiteMode).toBe('subdirectory')
    })

    test('enforces minimum maxConcurrentTests', () => {
      expect(getExternalTestingConfig({ maxConcurrentTests: 0 }).maxConcurrentTests).toBe(1)
      expect(getExternalTestingConfig({ maxConcurrentTests: -5 }).maxConcurrentTests).toBe(1)
      expect(getExternalTestingConfig({ maxConcurrentTests: 'invalid' }).maxConcurrentTests).toBe(1)
    })

    test('enforces minimum timeouts', () => {
      expect(getExternalTestingConfig({ queueTimeoutMs: 0 }).queueTimeoutMs).toBe(300000)
      expect(getExternalTestingConfig({ testTimeoutMs: -100 }).testTimeoutMs).toBe(600000)
    })
  })

  describe('SSH Command Building', () => {
    function buildSshBaseArgs(config) {
      const args = []
      if (config.wpSshKey) {
        args.push('-i', config.wpSshKey)
      }
      if (!config.strictHostKeyChecking) {
        args.push('-o', 'StrictHostKeyChecking=no')
      }
      args.push('-o', 'BatchMode=yes')
      return args
    }

    test('builds SSH arguments with key', () => {
      const config = getExternalTestingConfig({
        wpSshKey: '/path/to/key',
        strictHostKeyChecking: false,
      })
      const args = buildSshBaseArgs(config)

      expect(args).toContain('-i')
      expect(args).toContain('/path/to/key')
      expect(args).toContain('StrictHostKeyChecking=no')
      expect(args).toContain('BatchMode=yes')
    })

    test('builds SSH arguments without key', () => {
      const config = getExternalTestingConfig({
        wpSshKey: null,
        strictHostKeyChecking: false,
      })
      const args = buildSshBaseArgs(config)

      expect(args).not.toContain('-i')
      expect(args).toContain('StrictHostKeyChecking=no')
    })

    test('enables strict host key checking when configured', () => {
      const config = getExternalTestingConfig({
        strictHostKeyChecking: true,
      })
      const args = buildSshBaseArgs(config)

      expect(args).not.toContain('StrictHostKeyChecking=no')
    })
  })

  describe('Test Queue Management', () => {
    const testQueue = {
      queue: [],
      active: new Map(),
      completed: [],
      maxConcurrent: 5,

      add(testRun) {
        this.queue.push({
          ...testRun,
          id: crypto.randomUUID(),
          status: 'queued',
          queuedAt: Date.now(),
        })
      },

      canStart() {
        return this.active.size < this.maxConcurrent
      },

      start(testId) {
        const index = this.queue.findIndex(t => t.id === testId)
        if (index === -1) return false
        if (!this.canStart()) return false

        const test = this.queue.splice(index, 1)[0]
        test.status = 'running'
        test.startedAt = Date.now()
        this.active.set(test.id, test)
        return true
      },

      complete(testId, result) {
        const test = this.active.get(testId)
        if (!test) return false

        test.status = 'completed'
        test.completedAt = Date.now()
        test.result = result
        this.active.delete(testId)
        this.completed.push(test)
        return true
      },

      fail(testId, error) {
        const test = this.active.get(testId)
        if (!test) return false

        test.status = 'failed'
        test.completedAt = Date.now()
        test.error = error
        this.active.delete(testId)
        this.completed.push(test)
        return true
      },
    }

    beforeEach(() => {
      testQueue.queue = []
      testQueue.active.clear()
      testQueue.completed = []
    })

    test('adds test runs to queue', () => {
      testQueue.add({ appId: 'app-1', subsiteId: 'test-1' })

      expect(testQueue.queue.length).toBe(1)
      expect(testQueue.queue[0].status).toBe('queued')
    })

    test('generates unique test IDs', () => {
      testQueue.add({ appId: 'app-1' })
      testQueue.add({ appId: 'app-2' })

      expect(testQueue.queue[0].id).not.toBe(testQueue.queue[1].id)
    })

    test('starts queued tests', () => {
      testQueue.add({ appId: 'app-1' })
      const testId = testQueue.queue[0].id
      const started = testQueue.start(testId)

      expect(started).toBe(true)
      expect(testQueue.queue.length).toBe(0)
      expect(testQueue.active.has(testId)).toBe(true)
    })

    test('enforces maximum concurrent tests', () => {
      for (let i = 0; i < 5; i++) {
        testQueue.add({ appId: `app-${i}` })
        const testId = testQueue.queue.find(t => t.status === 'queued')?.id
        if (testId) testQueue.start(testId)
      }

      expect(testQueue.active.size).toBe(5)
      expect(testQueue.canStart()).toBe(false)
    })

    test('completes running tests', () => {
      testQueue.add({ appId: 'app-1' })
      const testId = testQueue.queue[0].id
      testQueue.start(testId)
      const completed = testQueue.complete(testId, { success: true })

      expect(completed).toBe(true)
      expect(testQueue.active.size).toBe(0)
      expect(testQueue.completed[0].status).toBe('completed')
    })

    test('fails running tests', () => {
      testQueue.add({ appId: 'app-1' })
      const testId = testQueue.queue[0].id
      testQueue.start(testId)
      const failed = testQueue.fail(testId, 'Test failed')

      expect(failed).toBe(true)
      expect(testQueue.completed[0].status).toBe('failed')
      expect(testQueue.completed[0].error).toBe('Test failed')
    })
  })

  describe('Subsite Management', () => {
    function generateSubsiteId(prefix = 'test') {
      return `${prefix}-${crypto.randomBytes(4).toString('hex')}`
    }

    function buildSubsiteUrl(baseUrl, subsiteId, mode = 'subdirectory') {
      if (mode === 'subdomain') {
        const url = new URL(baseUrl)
        url.hostname = `${subsiteId}.${url.hostname}`
        return url.toString()
      }
      return `${baseUrl.replace(/\/$/, '')}/${subsiteId}`
    }

    test('generates unique subsite IDs', () => {
      const ids = new Set()
      for (let i = 0; i < 100; i++) {
        ids.add(generateSubsiteId())
      }
      expect(ids.size).toBe(100)
    })

    test('builds subdirectory URL', () => {
      const url = buildSubsiteUrl('https://example.com', 'test-abc123', 'subdirectory')
      expect(url).toBe('https://example.com/test-abc123')
    })

    test('builds subdomain URL', () => {
      const url = buildSubsiteUrl('https://example.com', 'test-abc123', 'subdomain')
      expect(url).toBe('https://test-abc123.example.com/')
    })

    test('handles trailing slashes in base URL', () => {
      const url = buildSubsiteUrl('https://example.com/', 'test-123', 'subdirectory')
      expect(url).toBe('https://example.com/test-123')
    })
  })

  describe('Command Execution', () => {
    function parseCommandOutput(stdout, stderr, code) {
      return {
        success: code === 0,
        output: stdout?.trim() || '',
        error: stderr?.trim() || '',
        exitCode: code,
      }
    }

    test('parses successful command output', () => {
      const result = parseCommandOutput('Command succeeded', '', 0)

      expect(result.success).toBe(true)
      expect(result.output).toBe('Command succeeded')
      expect(result.error).toBe('')
    })

    test('parses failed command output', () => {
      const result = parseCommandOutput('', 'Error: command failed', 1)

      expect(result.success).toBe(false)
      expect(result.error).toBe('Error: command failed')
    })

    test('handles empty output', () => {
      const result = parseCommandOutput(null, null, 0)

      expect(result.output).toBe('')
      expect(result.error).toBe('')
    })
  })

  describe('Cleanup Operations', () => {
    const cleanupQueue = {
      items: [],

      schedule(subsiteId, delayMs) {
        this.items.push({
          subsiteId,
          scheduledAt: Date.now(),
          cleanupAt: Date.now() + delayMs,
        })
      },

      getDue() {
        const now = Date.now()
        return this.items.filter(item => item.cleanupAt <= now)
      },

      remove(subsiteId) {
        this.items = this.items.filter(item => item.subsiteId !== subsiteId)
      },
    }

    beforeEach(() => {
      cleanupQueue.items = []
    })

    test('schedules cleanup', () => {
      cleanupQueue.schedule('test-123', 3600000)

      expect(cleanupQueue.items.length).toBe(1)
      expect(cleanupQueue.items[0].subsiteId).toBe('test-123')
    })

    test('identifies due cleanups', () => {
      cleanupQueue.schedule('old-site', -1000)
      cleanupQueue.schedule('new-site', 3600000)

      const due = cleanupQueue.getDue()
      expect(due.length).toBe(1)
      expect(due[0].subsiteId).toBe('old-site')
    })

    test('removes items from cleanup queue', () => {
      cleanupQueue.schedule('test-123', 3600000)
      cleanupQueue.remove('test-123')

      expect(cleanupQueue.items.length).toBe(0)
    })
  })

  describe('Test Limits by Plan', () => {
    const EXTERNAL_TEST_LIMITS = {
      hobby: 3,
      starter: 50,
      professional: Infinity,
      enterprise: Infinity,
    }

    function getExternalTestLimit(plan) {
      const normalized = String(plan || '').toLowerCase()
      return EXTERNAL_TEST_LIMITS[normalized] || EXTERNAL_TEST_LIMITS.hobby
    }

    function canRunExternalTest(plan, usedTests) {
      const limit = getExternalTestLimit(plan)
      if (limit === Infinity) return true
      return usedTests < limit
    }

    test('hobby plan has 3 test limit', () => {
      expect(getExternalTestLimit('hobby')).toBe(3)
    })

    test('starter plan has 50 test limit', () => {
      expect(getExternalTestLimit('starter')).toBe(50)
    })

    test('professional plan has unlimited tests', () => {
      expect(getExternalTestLimit('professional')).toBe(Infinity)
    })

    test('enterprise plan has unlimited tests', () => {
      expect(getExternalTestLimit('enterprise')).toBe(Infinity)
    })

    test('blocks test for hobby plan at limit', () => {
      expect(canRunExternalTest('hobby', 2)).toBe(true)
      expect(canRunExternalTest('hobby', 3)).toBe(false)
    })

    test('allows unlimited tests for professional', () => {
      expect(canRunExternalTest('professional', 1000)).toBe(true)
    })

    test('defaults to hobby limit for unknown plan', () => {
      expect(getExternalTestLimit('unknown')).toBe(3)
    })
  })
})
