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

describe('Resource Management', () => {
  describe('Memory Management', () => {
    const SESSION_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000
    const MESSAGE_HISTORY_LIMIT = 50
    const PARTIAL_OUTPUT_MAX_LENGTH = 50000

    function parseMemoryValue(raw) {
      if (raw === undefined || raw === null) return 0
      const str = String(raw).trim()
      const match = str.match(/^([0-9.]+)\s*([kKmMgGtT]i?)?(?:[bB])?$/)
      if (!match) return Number(str) || 0
      const value = parseFloat(match[1])
      const unit = (match[2] || '').toLowerCase()
      const KIB = 1024
      const MIB = KIB * 1024
      const GIB = MIB * 1024
      const TIB = GIB * 1024
      const multipliers = { '': 1, k: KIB, ki: KIB, m: MIB, mi: MIB, g: GIB, gi: GIB, t: TIB, ti: TIB }
      if (!Number.isFinite(value) || value <= 0) return 0
      return Math.round(value * (multipliers[unit] || 1))
    }

    function truncateOutput(output, maxLength = PARTIAL_OUTPUT_MAX_LENGTH) {
      if (!output || typeof output !== 'string') return ''
      if (output.length <= maxLength) return output
      return output.slice(0, maxLength) + '...[truncated]'
    }

    test('parses memory values', () => {
      expect(parseMemoryValue('512M')).toBe(512 * 1024 * 1024)
      expect(parseMemoryValue('1G')).toBe(1024 * 1024 * 1024)
      expect(parseMemoryValue('256')).toBe(256)
    })

    test('handles invalid memory values', () => {
      expect(parseMemoryValue(null)).toBe(0)
      expect(parseMemoryValue(undefined)).toBe(0)
      expect(parseMemoryValue('invalid')).toBe(0)
    })

    test('truncates long outputs', () => {
      const longOutput = 'x'.repeat(60000)
      const truncated = truncateOutput(longOutput)

      expect(truncated.length).toBe(50014)
      expect(truncated.endsWith('...[truncated]')).toBe(true)
    })

    test('keeps short outputs intact', () => {
      const shortOutput = 'Hello world'
      const result = truncateOutput(shortOutput)

      expect(result).toBe(shortOutput)
    })

    test('session max age is 7 days', () => {
      expect(SESSION_MAX_AGE_MS).toBe(7 * 24 * 60 * 60 * 1000)
    })

    test('message history limit is 50', () => {
      expect(MESSAGE_HISTORY_LIMIT).toBe(50)
    })
  })

  describe('Session Cleanup', () => {
    const sessions = new Map()
    const SESSION_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000

    function cleanupStaleSessions() {
      const now = Date.now()
      let cleaned = 0

      for (const [id, session] of sessions.entries()) {
        const age = now - session.createdAt
        if (age > SESSION_MAX_AGE_MS && !session.active) {
          sessions.delete(id)
          cleaned++
        }
      }

      return cleaned
    }

    beforeEach(() => {
      sessions.clear()
    })

    test('cleans up stale sessions', () => {
      sessions.set('old-1', { createdAt: Date.now() - SESSION_MAX_AGE_MS - 1000, active: false })
      sessions.set('old-2', { createdAt: Date.now() - SESSION_MAX_AGE_MS - 1000, active: false })
      sessions.set('new-1', { createdAt: Date.now(), active: false })

      const cleaned = cleanupStaleSessions()

      expect(cleaned).toBe(2)
      expect(sessions.size).toBe(1)
    })

    test('keeps active sessions', () => {
      sessions.set('active-1', { createdAt: Date.now() - SESSION_MAX_AGE_MS - 1000, active: true })

      const cleaned = cleanupStaleSessions()

      expect(cleaned).toBe(0)
      expect(sessions.size).toBe(1)
    })

    test('keeps recent sessions', () => {
      sessions.set('recent-1', { createdAt: Date.now() - 1000, active: false })

      const cleaned = cleanupStaleSessions()

      expect(cleaned).toBe(0)
    })
  })

  describe('Message Cleanup', () => {
    const MESSAGE_HISTORY_LIMIT = 50

    function cleanupOldMessages(messages) {
      if (messages.length <= MESSAGE_HISTORY_LIMIT) return messages

      const toRemove = messages.length - MESSAGE_HISTORY_LIMIT
      const completedMessages = messages.filter(m => m.status === 'done' || m.status === 'error')
      
      if (completedMessages.length > 0) {
        const idsToRemove = completedMessages.slice(0, toRemove).map(m => m.id)
        return messages.filter(m => !idsToRemove.includes(m.id))
      }

      return messages.slice(-MESSAGE_HISTORY_LIMIT)
    }

    test('keeps messages under limit', () => {
      const messages = Array.from({ length: 30 }, (_, i) => ({ id: String(i), status: 'done' }))
      const result = cleanupOldMessages(messages)

      expect(result.length).toBe(30)
    })

    test('removes oldest completed messages first', () => {
      const messages = Array.from({ length: 60 }, (_, i) => ({
        id: String(i),
        status: i < 10 ? 'done' : 'running',
      }))

      const result = cleanupOldMessages(messages)
      expect(result.length).toBe(50)
    })

    test('prefers keeping running messages', () => {
      const messages = [
        { id: '1', status: 'done' },
        { id: '2', status: 'done' },
        { id: '3', status: 'running' },
      ]

      const result = cleanupOldMessages(messages, 2)
      expect(result.length).toBe(3)
    })
  })

  describe('Concurrency Control', () => {
    const concurrencyLimit = {
      maxConcurrent: 5,
      current: 0,
      queue: [],

      async acquire() {
        if (this.current < this.maxConcurrent) {
          this.current++
          return true
        }
        return new Promise((resolve) => {
          this.queue.push(resolve)
        })
      },

      release() {
        this.current--
        if (this.queue.length > 0) {
          const next = this.queue.shift()
          this.current++
          next(true)
        }
      },

      getStatus() {
        return {
          current: this.current,
          queued: this.queue.length,
          available: this.maxConcurrent - this.current,
        }
      },
    }

    beforeEach(() => {
      concurrencyLimit.current = 0
      concurrencyLimit.queue = []
    })

    test('acquires slot when available', async () => {
      const acquired = await concurrencyLimit.acquire()
      expect(acquired).toBe(true)
      expect(concurrencyLimit.current).toBe(1)
    })

    test('enforces maximum concurrent', async () => {
      for (let i = 0; i < 5; i++) {
        await concurrencyLimit.acquire()
      }

      expect(concurrencyLimit.current).toBe(5)
      const status = concurrencyLimit.getStatus()
      expect(status.available).toBe(0)
    })

    test('queues requests when at limit', async () => {
      for (let i = 0; i < 5; i++) {
        await concurrencyLimit.acquire()
      }

      const acquirePromise = concurrencyLimit.acquire()
      expect(concurrencyLimit.queue.length).toBe(1)
    })

    test('releases slot and processes queue', async () => {
      for (let i = 0; i < 5; i++) {
        await concurrencyLimit.acquire()
      }

      const promise = concurrencyLimit.acquire()
      concurrencyLimit.release()

      expect(concurrencyLimit.current).toBe(5)
    })

    test('reports correct status', async () => {
      await concurrencyLimit.acquire()
      await concurrencyLimit.acquire()

      const status = concurrencyLimit.getStatus()
      expect(status.current).toBe(2)
      expect(status.available).toBe(3)
      expect(status.queued).toBe(0)
    })
  })

  describe('Rate Limiting', () => {
    const rateLimiter = {
      requests: new Map(),
      windowMs: 60000,
      maxRequests: 100,

      check(key) {
        const now = Date.now()
        let record = this.requests.get(key)

        if (!record || now - record.windowStart > this.windowMs) {
          record = { count: 0, windowStart: now }
          this.requests.set(key, record)
        }

        record.count++

        return {
          allowed: record.count <= this.maxRequests,
          remaining: Math.max(0, this.maxRequests - record.count),
          resetAt: record.windowStart + this.windowMs,
        }
      },

      reset(key) {
        this.requests.delete(key)
      },
    }

    beforeEach(() => {
      rateLimiter.requests.clear()
    })

    test('allows requests within limit', () => {
      for (let i = 0; i < 50; i++) {
        const result = rateLimiter.check('user-1')
        expect(result.allowed).toBe(true)
      }
    })

    test('blocks requests over limit', () => {
      for (let i = 0; i < 100; i++) {
        rateLimiter.check('user-2')
      }

      const result = rateLimiter.check('user-2')
      expect(result.allowed).toBe(false)
      expect(result.remaining).toBe(0)
    })

    test('resets after window', () => {
      rateLimiter.requests.set('user-3', {
        count: 100,
        windowStart: Date.now() - 70000,
      })

      const result = rateLimiter.check('user-3')
      expect(result.allowed).toBe(true)
    })

    test('tracks remaining requests', () => {
      for (let i = 0; i < 10; i++) {
        rateLimiter.check('user-4')
      }

      const result = rateLimiter.check('user-4')
      expect(result.remaining).toBe(89)
    })
  })

  describe('Priority Queue', () => {
    const priorityQueue = {
      queue: [],

      add(request) {
        this.queue.push({
          ...request,
          addedAt: Date.now(),
        })
        this.queue.sort((a, b) => {
          if (a.priority !== b.priority) return a.priority - b.priority
          return a.addedAt - b.addedAt
        })
      },

      next() {
        return this.queue.shift()
      },

      length() {
        return this.queue.length
      },

      clear() {
        this.queue = []
      },
    }

    beforeEach(() => {
      priorityQueue.clear()
    })

    test('orders by priority', () => {
      priorityQueue.add({ id: '1', priority: 5 })
      priorityQueue.add({ id: '2', priority: 1 })
      priorityQueue.add({ id: '3', priority: 3 })

      expect(priorityQueue.next().id).toBe('2')
      expect(priorityQueue.next().id).toBe('3')
      expect(priorityQueue.next().id).toBe('1')
    })

    test('processes FIFO for same priority', () => {
      priorityQueue.add({ id: '1', priority: 1 })
      priorityQueue.add({ id: '2', priority: 1 })

      expect(priorityQueue.next().id).toBe('1')
      expect(priorityQueue.next().id).toBe('2')
    })

    test('reports queue length', () => {
      priorityQueue.add({ id: '1', priority: 1 })
      priorityQueue.add({ id: '2', priority: 2 })

      expect(priorityQueue.length()).toBe(2)
    })
  })

  describe('Resource Reservation', () => {
    let resourceManager

    beforeEach(() => {
      resourceManager = {
        memory: {
          total: 512 * 1024 * 1024,
          used: 0,
        },
        cpu: {
          total: 2,
          used: 0,
        },

        reserve(memory, cpu) {
          if (this.memory.used + memory > this.memory.total) {
            return { success: false, reason: 'insufficient_memory' }
          }
          if (this.cpu.used + cpu > this.cpu.total) {
            return { success: false, reason: 'insufficient_cpu' }
          }

          this.memory.used += memory
          this.cpu.used += cpu
          return { success: true }
        },

        release(memory, cpu) {
          this.memory.used = Math.max(0, this.memory.used - memory)
          this.cpu.used = Math.max(0, this.cpu.used - cpu)
        },

        getUsage() {
          return {
            memory: {
              total: this.memory.total,
              used: this.memory.used,
              available: this.memory.total - this.memory.used,
            },
            cpu: {
              total: this.cpu.total,
              used: this.cpu.used,
              available: this.cpu.total - this.cpu.used,
            },
          }
        },
      }
    })

    test('reserves resources', () => {
      const result = resourceManager.reserve(100 * 1024 * 1024, 0.5)

      expect(result.success).toBe(true)
      const usage = resourceManager.getUsage()
      expect(usage.memory.used).toBe(100 * 1024 * 1024)
      expect(usage.cpu.used).toBe(0.5)
    })

    test('rejects memory over limit', () => {
      const result = resourceManager.reserve(1024 * 1024 * 1024, 0.1)

      expect(result.success).toBe(false)
      expect(result.reason).toBe('insufficient_memory')
    })

    test('rejects CPU over limit', () => {
      const result = resourceManager.reserve(100, 3)

      expect(result.success).toBe(false)
      expect(result.reason).toBe('insufficient_cpu')
    })

    test('releases resources', () => {
      resourceManager.reserve(100 * 1024 * 1024, 0.5)
      resourceManager.release(100 * 1024 * 1024, 0.5)

      const usage = resourceManager.getUsage()
      expect(usage.memory.used).toBe(0)
      expect(usage.cpu.used).toBe(0)
    })

    test('tracks available resources', () => {
      resourceManager.reserve(200 * 1024 * 1024, 1)

      const usage = resourceManager.getUsage()
      expect(usage.memory.available).toBe(312 * 1024 * 1024)
      expect(usage.cpu.available).toBe(1)
    })
  })
})
