import { describe, expect, test, beforeEach, afterEach } from 'bun:test'
import {
  isEnvTruthy,
  isEnvDefinedFalsy,
  parseEnvVars,
  hasNodeOption,
  getAWSRegion,
  getDefaultVertexRegion,
  getVertexRegionForModel,
  isBareMode,
  shouldMaintainProjectWorkingDir,
  getClaudeConfigHomeDir,
} from '../envUtils'

// ─── isEnvTruthy ───────────────────────────────────────────────────────

describe('isEnvTruthy', () => {
  test("returns true for '1'", () => {
    expect(isEnvTruthy('1')).toBe(true)
  })

  test("returns true for 'true'", () => {
    expect(isEnvTruthy('true')).toBe(true)
  })

  test("returns true for 'TRUE'", () => {
    expect(isEnvTruthy('TRUE')).toBe(true)
  })

  test("returns true for 'yes'", () => {
    expect(isEnvTruthy('yes')).toBe(true)
  })

  test("returns true for 'on'", () => {
    expect(isEnvTruthy('on')).toBe(true)
  })

  test('returns true for boolean true', () => {
    expect(isEnvTruthy(true)).toBe(true)
  })

  test("returns false for '0'", () => {
    expect(isEnvTruthy('0')).toBe(false)
  })

  test("returns false for 'false'", () => {
    expect(isEnvTruthy('false')).toBe(false)
  })

  test('returns false for empty string', () => {
    expect(isEnvTruthy('')).toBe(false)
  })

  test('returns false for undefined', () => {
    expect(isEnvTruthy(undefined)).toBe(false)
  })

  test('returns false for boolean false', () => {
    expect(isEnvTruthy(false)).toBe(false)
  })

  test("returns true for ' true ' (trimmed)", () => {
    expect(isEnvTruthy(' true ')).toBe(true)
  })
})

// ─── isEnvDefinedFalsy ─────────────────────────────────────────────────

describe('isEnvDefinedFalsy', () => {
  test("returns true for '0'", () => {
    expect(isEnvDefinedFalsy('0')).toBe(true)
  })

  test("returns true for 'false'", () => {
    expect(isEnvDefinedFalsy('false')).toBe(true)
  })

  test("returns true for 'no'", () => {
    expect(isEnvDefinedFalsy('no')).toBe(true)
  })

  test("returns true for 'off'", () => {
    expect(isEnvDefinedFalsy('off')).toBe(true)
  })

  test('returns true for boolean false', () => {
    expect(isEnvDefinedFalsy(false)).toBe(true)
  })

  test('returns false for undefined', () => {
    expect(isEnvDefinedFalsy(undefined)).toBe(false)
  })

  test("returns false for '1'", () => {
    expect(isEnvDefinedFalsy('1')).toBe(false)
  })

  test("returns false for 'true'", () => {
    expect(isEnvDefinedFalsy('true')).toBe(false)
  })

  test('returns false for empty string', () => {
    expect(isEnvDefinedFalsy('')).toBe(false)
  })
})

// ─── parseEnvVars ──────────────────────────────────────────────────────

describe('parseEnvVars', () => {
  test('parses KEY=VALUE pairs', () => {
    const result = parseEnvVars(['FOO=bar', 'BAZ=qux'])
    expect(result).toEqual({ FOO: 'bar', BAZ: 'qux' })
  })

  test('handles value with equals sign', () => {
    const result = parseEnvVars(['URL=http://host?a=1&b=2'])
    expect(result).toEqual({ URL: 'http://host?a=1&b=2' })
  })

  test('returns empty object for undefined', () => {
    expect(parseEnvVars(undefined)).toEqual({})
  })

  test('returns empty object for empty array', () => {
    expect(parseEnvVars([])).toEqual({})
  })

  test('throws for missing value', () => {
    expect(() => parseEnvVars(['NOVALUE'])).toThrow(
      'Invalid environment variable format',
    )
  })

  test('throws for empty key', () => {
    expect(() => parseEnvVars(['=value'])).toThrow(
      'Invalid environment variable format',
    )
  })
})

// ─── hasNodeOption ─────────────────────────────────────────────────────

describe('hasNodeOption', () => {
  const saved = process.env.NODE_OPTIONS
  afterEach(() => {
    if (saved === undefined) delete process.env.NODE_OPTIONS
    else process.env.NODE_OPTIONS = saved
  })

  test('returns true when flag present', () => {
    process.env.NODE_OPTIONS = '--max-old-space-size=4096 --inspect'
    expect(hasNodeOption('--inspect')).toBe(true)
  })

  test('returns false when flag absent', () => {
    process.env.NODE_OPTIONS = '--max-old-space-size=4096'
    expect(hasNodeOption('--inspect')).toBe(false)
  })

  test('returns false when NODE_OPTIONS not set', () => {
    delete process.env.NODE_OPTIONS
    expect(hasNodeOption('--inspect')).toBe(false)
  })

  test('does not match partial flags', () => {
    process.env.NODE_OPTIONS = '--inspect-brk'
    expect(hasNodeOption('--inspect')).toBe(false)
  })
})

// ─── getAWSRegion ──────────────────────────────────────────────────────

describe('getAWSRegion', () => {
  const savedRegion = process.env.AWS_REGION
  const savedDefault = process.env.AWS_DEFAULT_REGION

  afterEach(() => {
    if (savedRegion === undefined) delete process.env.AWS_REGION
    else process.env.AWS_REGION = savedRegion
    if (savedDefault === undefined) delete process.env.AWS_DEFAULT_REGION
    else process.env.AWS_DEFAULT_REGION = savedDefault
  })

  test('uses AWS_REGION when set', () => {
    process.env.AWS_REGION = 'eu-west-1'
    expect(getAWSRegion()).toBe('eu-west-1')
  })

  test('falls back to AWS_DEFAULT_REGION', () => {
    delete process.env.AWS_REGION
    process.env.AWS_DEFAULT_REGION = 'ap-northeast-1'
    expect(getAWSRegion()).toBe('ap-northeast-1')
  })

  test('defaults to us-east-1', () => {
    delete process.env.AWS_REGION
    delete process.env.AWS_DEFAULT_REGION
    expect(getAWSRegion()).toBe('us-east-1')
  })
})

// ─── getDefaultVertexRegion ────────────────────────────────────────────

describe('getDefaultVertexRegion', () => {
  const saved = process.env.CLOUD_ML_REGION
  afterEach(() => {
    if (saved === undefined) delete process.env.CLOUD_ML_REGION
    else process.env.CLOUD_ML_REGION = saved
  })

  test('uses CLOUD_ML_REGION when set', () => {
    process.env.CLOUD_ML_REGION = 'europe-west4'
    expect(getDefaultVertexRegion()).toBe('europe-west4')
  })

  test('defaults to us-east5', () => {
    delete process.env.CLOUD_ML_REGION
    expect(getDefaultVertexRegion()).toBe('us-east5')
  })
})

// ─── getVertexRegionForModel ───────────────────────────────────────────

describe('getVertexRegionForModel', () => {
  const envKeys = [
    'VERTEX_REGION_CLAUDE_HAIKU_4_5',
    'VERTEX_REGION_CLAUDE_4_0_SONNET',
    'VERTEX_REGION_CLAUDE_4_6_SONNET',
    'CLOUD_ML_REGION',
  ]
  const saved: Record<string, string | undefined> = {}

  beforeEach(() => {
    for (const k of envKeys) saved[k] = process.env[k]
  })
  afterEach(() => {
    for (const k of envKeys) {
      if (saved[k] === undefined) delete process.env[k]
      else process.env[k] = saved[k]
    }
  })

  test('returns model-specific override when set', () => {
    process.env.VERTEX_REGION_CLAUDE_HAIKU_4_5 = 'us-central1'
    expect(getVertexRegionForModel('claude-haiku-4-5-20251001')).toBe(
      'us-central1',
    )
  })

  test('falls back to default vertex region when override not set', () => {
    delete process.env.VERTEX_REGION_CLAUDE_4_0_SONNET
    delete process.env.CLOUD_ML_REGION
    expect(getVertexRegionForModel('claude-sonnet-4-some-variant')).toBe(
      'us-east5',
    )
  })

  test('returns default region for unknown model prefix', () => {
    delete process.env.CLOUD_ML_REGION
    expect(getVertexRegionForModel('unknown-model-123')).toBe('us-east5')
  })

  test('returns default region for undefined model', () => {
    delete process.env.CLOUD_ML_REGION
    expect(getVertexRegionForModel(undefined)).toBe('us-east5')
  })
})

// ─── isBareMode ────────────────────────────────────────────────────────

describe('isBareMode', () => {
  const saved = process.env.CLAUDE_CODE_SIMPLE
  const originalArgv = [...process.argv]

  afterEach(() => {
    if (saved === undefined) delete process.env.CLAUDE_CODE_SIMPLE
    else process.env.CLAUDE_CODE_SIMPLE = saved
    process.argv.length = 0
    process.argv.push(...originalArgv)
  })

  test('returns true when CLAUDE_CODE_SIMPLE=1', () => {
    process.env.CLAUDE_CODE_SIMPLE = '1'
    expect(isBareMode()).toBe(true)
  })

  test('returns true when --bare in argv', () => {
    process.argv.push('--bare')
    expect(isBareMode()).toBe(true)
  })

  test('returns false when neither set', () => {
    delete process.env.CLAUDE_CODE_SIMPLE
    // argv doesn't have --bare by default
    expect(isBareMode()).toBe(false)
  })
})

// ─── shouldMaintainProjectWorkingDir ───────────────────────────────────

describe('shouldMaintainProjectWorkingDir', () => {
  const saved = process.env.CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR

  afterEach(() => {
    if (saved === undefined)
      delete process.env.CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR
    else process.env.CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR = saved
  })

  test('returns true when set to truthy', () => {
    process.env.CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR = '1'
    expect(shouldMaintainProjectWorkingDir()).toBe(true)
  })

  test('returns false when not set', () => {
    delete process.env.CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR
    expect(shouldMaintainProjectWorkingDir()).toBe(false)
  })
})

// ─── getClaudeConfigHomeDir ────────────────────────────────────────────

describe('getClaudeConfigHomeDir', () => {
  const saved = process.env.CLAUDE_CONFIG_DIR

  afterEach(() => {
    if (saved === undefined) delete process.env.CLAUDE_CONFIG_DIR
    else process.env.CLAUDE_CONFIG_DIR = saved
  })

  test('uses CLAUDE_CONFIG_DIR when set', () => {
    process.env.CLAUDE_CONFIG_DIR = '/tmp/test-claude'
    // Memoized by CLAUDE_CONFIG_DIR key, so changing env gives fresh value
    expect(getClaudeConfigHomeDir()).toBe('/tmp/test-claude')
  })

  test('returns a string ending with .claude by default', () => {
    delete process.env.CLAUDE_CONFIG_DIR
    const result = getClaudeConfigHomeDir()
    expect(result).toMatch(/\.claude$/)
  })
})
