/**
 * Integration tests for Memory Enhancements (v5.11.0)
 *
 * End-to-end tests verifying:
 * - Container detection works correctly
 * - Memory limits are applied correctly
 * - getMemoryStats() provides accurate information
 * - All features work together seamlessly
 */

import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import { ValidationConfig, resetLimitWarningCache } from '../../src/utils/paramValidation.js'
import { mkdtempSync, rmSync } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'

describe('Memory Enhancements Integration (v5.11.0)', () => {
  let testDir: string
  let originalEnv: Record<string, string | undefined>

  beforeEach(() => {
    testDir = mkdtempSync(join(tmpdir(), 'brainy-memory-integration-'))

    originalEnv = {
      CLOUD_RUN_MEMORY: process.env.CLOUD_RUN_MEMORY,
      MEMORY_LIMIT: process.env.MEMORY_LIMIT
    }

    // ValidationConfig is a process-wide singleton that only rebuilds when a brain
    // passes maxQueryLimit/reservedQueryMemory (brainy.ts init reconfigure). Tests
    // here rely on auto-detection (container/free-memory tiers), which read live
    // process state when the singleton is first constructed. Reset both the config
    // singleton and the limit-warning dedup before every test so each one observes
    // the env it sets — without this, basis/maxLimit bleed from earlier tests and
    // results become order-dependent.
    ValidationConfig.reset()
    resetLimitWarningCache()
  })

  afterEach(() => {
    rmSync(testDir, { recursive: true, force: true })

    Object.keys(originalEnv).forEach(key => {
      if (originalEnv[key] === undefined) {
        delete process.env[key]
      } else {
        process.env[key] = originalEnv[key]
      }
    })

    // Leave the singleton clean for any other integration file sharing this process.
    ValidationConfig.reset()
    resetLimitWarningCache()
  })

  describe('Production Workflow: Cloud Run Deployment', () => {
    it('should detect 4GB Cloud Run container and set optimal limits', async () => {
      process.env.CLOUD_RUN_MEMORY = '4Gi'

      const brain = new Brainy({ requireSubtype: false,
        storage: { type: 'memory' },
        silent: true
      })

      await brain.init()

      const stats = brain.getMemoryStats()

      // Verify container detected
      expect(stats.memory.containerLimit).toBe(4 * 1024 * 1024 * 1024)

      // Verify optimal limits.
      // containerMemory tier reserves 25% of the container for queries, then
      // divides by 25 KB/result (MAX_LIMIT_KB_PER_RESULT) and floors to 1k steps:
      //   4GiB * 0.25 = 1 GiB; floor(1 GiB / (25 KB)) = 40_960 -> 40_000.
      expect(stats.limits.basis).toBe('containerMemory')
      expect(stats.limits.maxQueryLimit).toBe(40000)

      // Verify can query with these limits
      for (let i = 0; i < 100; i++) {
        await brain.add({ type: 'document', data: `Entity ${i}` })
      }

      const results = await brain.find({ limit: 10000 }) // Should not throw
      expect(results.length).toBe(100)

      await brain.close()
    })

    it('should allow manual override for power users in containers', async () => {
      process.env.CLOUD_RUN_MEMORY = '4Gi'

      const brain = new Brainy({ requireSubtype: false,
        storage: { type: 'memory' },
        maxQueryLimit: 50000, // Override auto-detection
        silent: true
      })

      await brain.init()

      const stats = brain.getMemoryStats()

      // Container detected but override used
      expect(stats.memory.containerLimit).toBe(4 * 1024 * 1024 * 1024)
      expect(stats.limits.basis).toBe('override')
      expect(stats.limits.maxQueryLimit).toBe(50000)

      await brain.close()
    })

    it('should use reservedQueryMemory for fine-grained control', async () => {
      const brain = new Brainy({ requireSubtype: false,
        storage: { type: 'memory' },
        reservedQueryMemory: 2 * 1024 * 1024 * 1024, // Reserve 2GB
        silent: true
      })

      await brain.init()

      const stats = brain.getMemoryStats()

      // reservedMemory tier divides the reserved bytes by 25 KB/result and floors
      // to 1k steps: floor(2 GiB / (25 KB)) = 81_920 -> 81_000.
      expect(stats.limits.basis).toBe('reservedMemory')
      expect(stats.limits.maxQueryLimit).toBe(81000)

      await brain.close()
    })
  })

  describe('Memory Stats API', () => {
    it('should provide actionable recommendations', async () => {
      process.env.CLOUD_RUN_MEMORY = '4Gi'

      const brain = new Brainy({ requireSubtype: false,
        storage: { type: 'memory' },
        silent: true
      })

      await brain.init()

      const stats = brain.getMemoryStats()

      // Should have recommendations array
      expect(stats.recommendations).toBeDefined()
      expect(Array.isArray(stats.recommendations)).toBe(true)

      // Should not have error recommendations for well-configured system
      const errorRecommendations = stats.recommendations?.filter(r =>
        r.toLowerCase().includes('error') || r.toLowerCase().includes('warning')
      )

      expect(errorRecommendations?.length || 0).toBe(0)

      await brain.close()
    })

    it('should help debug low limits in production', async () => {
      // Simulate production issue: container detected but low free memory
      process.env.CLOUD_RUN_MEMORY = '4Gi'

      const brain = new Brainy({ requireSubtype: false,
        storage: { type: 'memory' },
        silent: true
      })

      await brain.init()

      const stats = brain.getMemoryStats()

      // User can see why limits are what they are
      expect(stats.limits.basis).toBeDefined()
      expect(stats.memory.containerLimit).toBeDefined()
      expect(stats.config).toBeDefined()

      // Can debug: "Why is my limit what it is?"
      // Answer: basis='containerMemory', container=4GB, 4GB * 0.25 = 1GB / 25KB -> 40k

      await brain.close()
    })
  })

  describe('Zero-Config Behavior', () => {
    it('should work optimally with no configuration', async () => {
      process.env.CLOUD_RUN_MEMORY = '4Gi'

      const brain = new Brainy({ requireSubtype: false,
        storage: { type: 'memory' },
        silent: true
      })

      await brain.init()

      // Zero config, optimal behavior
      const stats = brain.getMemoryStats()

      // Same calc as the 4GiB container test above: 25% of 4 GiB / 25 KB -> 40_000.
      expect(stats.limits.maxQueryLimit).toBe(40000)
      expect(stats.limits.basis).toBe('containerMemory')

      // Should work without issues
      for (let i = 0; i < 100; i++) {
        await brain.add({ type: 'document', data: `Test ${i}` })
      }

      const results = await brain.find({ limit: 100 })
      expect(results.length).toBe(100)

      await brain.close()
    })

    it('should work on bare metal without containers', async () => {
      // No container env vars

      const brain = new Brainy({ requireSubtype: false,
        storage: { type: 'memory' },
        silent: true
      })

      await brain.init()

      const stats = brain.getMemoryStats()

      expect(stats.memory.containerLimit).toBeNull()
      expect(stats.limits.basis).toBe('freeMemory')
      expect(stats.limits.maxQueryLimit).toBeGreaterThan(0)

      await brain.close()
    })
  })

  describe('Backward Compatibility', () => {
    it('should not break existing code', async () => {
      const brain = new Brainy({ requireSubtype: false,
        storage: { type: 'memory' },
        silent: true
      })

      await brain.init()

      // Existing APIs work
      await brain.add({ type: 'document', data: 'Test' })
      const results = await brain.find({ limit: 10 })

      expect(results.length).toBe(1)

      await brain.close()
    })
  })
})
