#!/usr/bin/env node
/**
 * Unit tests for un.js - tests internal functions without API calls
 */

const crypto = require('crypto');
const path = require('path');
const fs = require('fs');
const os = require('os');
const assert = require('assert');

// Test counters
let passed = 0;
let failed = 0;

function test(name, fn) {
  try {
    fn();
    console.log(`  ✓ ${name}`);
    passed++;
  } catch (e) {
    console.log(`  ✗ ${name}`);
    console.log(`    ${e.message}`);
    failed++;
  }
}

function assertEqual(actual, expected, msg = '') {
  if (actual !== expected) {
    throw new Error(`Expected "${expected}" but got "${actual}" ${msg}`);
  }
}

function assertIncludes(str, substr) {
  if (!str.includes(substr)) {
    throw new Error(`Expected "${str}" to include "${substr}"`);
  }
}

function assertNotEqual(a, b) {
  if (a === b) {
    throw new Error(`Expected values to be different but both were "${a}"`);
  }
}

// ============================================================================
// Extension Mapping Tests
// ============================================================================

console.log('\n=== Extension Mapping Tests ===');

const EXT_MAP = {
  ".py": "python", ".js": "javascript", ".ts": "typescript",
  ".rb": "ruby", ".php": "php", ".pl": "perl", ".lua": "lua",
  ".sh": "bash", ".go": "go", ".rs": "rust", ".c": "c",
  ".cpp": "cpp", ".cc": "cpp", ".cxx": "cpp",
  ".java": "java", ".kt": "kotlin", ".cs": "csharp", ".fs": "fsharp",
  ".hs": "haskell", ".ml": "ocaml", ".clj": "clojure", ".scm": "scheme",
  ".lisp": "commonlisp", ".erl": "erlang", ".ex": "elixir", ".exs": "elixir",
  ".jl": "julia", ".r": "r", ".R": "r", ".cr": "crystal",
  ".d": "d", ".nim": "nim", ".zig": "zig", ".v": "v",
  ".dart": "dart", ".groovy": "groovy", ".scala": "scala",
  ".f90": "fortran", ".f95": "fortran", ".cob": "cobol",
  ".pro": "prolog", ".forth": "forth", ".4th": "forth",
  ".tcl": "tcl", ".raku": "raku", ".m": "objc",
};

test('Python extension maps correctly', () => {
  assertEqual(EXT_MAP['.py'], 'python');
});

test('JavaScript extensions map correctly', () => {
  assertEqual(EXT_MAP['.js'], 'javascript');
  assertEqual(EXT_MAP['.ts'], 'typescript');
});

test('Ruby extension maps correctly', () => {
  assertEqual(EXT_MAP['.rb'], 'ruby');
});

test('Go extension maps correctly', () => {
  assertEqual(EXT_MAP['.go'], 'go');
});

test('Rust extension maps correctly', () => {
  assertEqual(EXT_MAP['.rs'], 'rust');
});

test('C/C++ extensions map correctly', () => {
  assertEqual(EXT_MAP['.c'], 'c');
  assertEqual(EXT_MAP['.cpp'], 'cpp');
  assertEqual(EXT_MAP['.cc'], 'cpp');
  assertEqual(EXT_MAP['.cxx'], 'cpp');
});

test('JVM extensions map correctly', () => {
  assertEqual(EXT_MAP['.java'], 'java');
  assertEqual(EXT_MAP['.kt'], 'kotlin');
  assertEqual(EXT_MAP['.groovy'], 'groovy');
  assertEqual(EXT_MAP['.scala'], 'scala');
});

test('.NET extensions map correctly', () => {
  assertEqual(EXT_MAP['.cs'], 'csharp');
  assertEqual(EXT_MAP['.fs'], 'fsharp');
});

test('Functional language extensions map correctly', () => {
  assertEqual(EXT_MAP['.hs'], 'haskell');
  assertEqual(EXT_MAP['.ml'], 'ocaml');
  assertEqual(EXT_MAP['.clj'], 'clojure');
  assertEqual(EXT_MAP['.scm'], 'scheme');
  assertEqual(EXT_MAP['.lisp'], 'commonlisp');
  assertEqual(EXT_MAP['.erl'], 'erlang');
  assertEqual(EXT_MAP['.ex'], 'elixir');
  assertEqual(EXT_MAP['.exs'], 'elixir');
});

test('Scientific language extensions map correctly', () => {
  assertEqual(EXT_MAP['.jl'], 'julia');
  assertEqual(EXT_MAP['.r'], 'r');
  assertEqual(EXT_MAP['.R'], 'r');
  assertEqual(EXT_MAP['.f90'], 'fortran');
  assertEqual(EXT_MAP['.f95'], 'fortran');
});

test('Systems language extensions map correctly', () => {
  assertEqual(EXT_MAP['.d'], 'd');
  assertEqual(EXT_MAP['.nim'], 'nim');
  assertEqual(EXT_MAP['.zig'], 'zig');
  assertEqual(EXT_MAP['.v'], 'v');
  assertEqual(EXT_MAP['.cr'], 'crystal');
  assertEqual(EXT_MAP['.dart'], 'dart');
});

test('Legacy/exotic extensions map correctly', () => {
  assertEqual(EXT_MAP['.cob'], 'cobol');
  assertEqual(EXT_MAP['.pro'], 'prolog');
  assertEqual(EXT_MAP['.forth'], 'forth');
  assertEqual(EXT_MAP['.4th'], 'forth');
  assertEqual(EXT_MAP['.tcl'], 'tcl');
  assertEqual(EXT_MAP['.raku'], 'raku');
  assertEqual(EXT_MAP['.m'], 'objc');
});

// ============================================================================
// HMAC Signature Tests
// ============================================================================

console.log('\n=== HMAC Signature Tests ===');

test('HMAC-SHA256 generates 64 character hex string', () => {
  const secret = 'test-secret-key';
  const message = '1234567890:POST:/execute:{}';

  const signature = crypto.createHmac('sha256', secret)
    .update(message)
    .digest('hex');

  assertEqual(signature.length, 64);
});

test('Same input produces same signature', () => {
  const secret = 'test-secret-key';
  const message = '1234567890:POST:/execute:{}';

  const sig1 = crypto.createHmac('sha256', secret).update(message).digest('hex');
  const sig2 = crypto.createHmac('sha256', secret).update(message).digest('hex');

  assertEqual(sig1, sig2);
});

test('Different secrets produce different signatures', () => {
  const message = '1234567890:POST:/execute:{}';

  const sig1 = crypto.createHmac('sha256', 'secret1').update(message).digest('hex');
  const sig2 = crypto.createHmac('sha256', 'secret2').update(message).digest('hex');

  assertNotEqual(sig1, sig2);
});

test('Different messages produce different signatures', () => {
  const secret = 'test-secret';

  const sig1 = crypto.createHmac('sha256', secret).update('message1').digest('hex');
  const sig2 = crypto.createHmac('sha256', secret).update('message2').digest('hex');

  assertNotEqual(sig1, sig2);
});

test('Signature format is timestamp:METHOD:path:body', () => {
  const timestamp = '1704067200';
  const method = 'POST';
  const endpoint = '/execute';
  const body = '{"language":"python","code":"print(1)"}';

  const message = `${timestamp}:${method}:${endpoint}:${body}`;

  // Verify format: starts with timestamp, has method and path
  assertEqual(message.startsWith(timestamp), true);
  assertIncludes(message, ':POST:');
  assertIncludes(message, ':/execute:');
});

// ============================================================================
// Language Detection Tests
// ============================================================================

console.log('\n=== Language Detection Tests ===');

test('Detect language from .py extension', () => {
  const filename = 'script.py';
  const ext = path.extname(filename).toLowerCase();
  assertEqual(EXT_MAP[ext], 'python');
});

test('Detect language from .js extension', () => {
  const filename = 'app.js';
  const ext = path.extname(filename).toLowerCase();
  assertEqual(EXT_MAP[ext], 'javascript');
});

test('Python shebang detection', () => {
  const content = '#!/usr/bin/env python3\nprint("hello")';
  const firstLine = content.split('\n')[0];

  assertEqual(firstLine.startsWith('#!'), true);
  assertEqual(firstLine.includes('python'), true);
});

test('Node shebang detection', () => {
  const content = '#!/usr/bin/env node\nconsole.log("hello")';
  const firstLine = content.split('\n')[0];

  assertEqual(firstLine.startsWith('#!'), true);
  assertEqual(firstLine.includes('node'), true);
});

test('Bash shebang detection', () => {
  const content = '#!/bin/bash\necho hello';
  const firstLine = content.split('\n')[0];

  assertEqual(firstLine.startsWith('#!'), true);
  assertEqual(firstLine.includes('bash') || firstLine.includes('/sh'), true);
});

// ============================================================================
// Argument Parsing Tests
// ============================================================================

console.log('\n=== Argument Parsing Tests ===');

test('Parse -e KEY=VALUE format', () => {
  const arg = 'DEBUG=1';
  const [key, ...rest] = arg.split('=');
  const value = rest.join('=');

  assertEqual(key, 'DEBUG');
  assertEqual(value, '1');
});

test('Parse -e KEY=VALUE with equals in value', () => {
  const arg = 'URL=https://example.com?foo=bar';
  const [key, ...rest] = arg.split('=');
  const value = rest.join('=');

  assertEqual(key, 'URL');
  assertEqual(value, 'https://example.com?foo=bar');
});

test('Valid network modes', () => {
  const validModes = ['zerotrust', 'semitrusted'];

  assertEqual(validModes.includes('zerotrust'), true);
  assertEqual(validModes.includes('semitrusted'), true);
  assertEqual(validModes.includes('invalid'), false);
});

test('Subcommand detection', () => {
  const args = ['session', '--shell', 'python3'];
  const subcommands = ['session', 'service', 'key', 'restore'];
  const subcommand = subcommands.includes(args[0]) ? args[0] : null;

  assertEqual(subcommand, 'session');
});

// ============================================================================
// File Operations Tests
// ============================================================================

console.log('\n=== File Operations Tests ===');

test('Read text file', () => {
  const tempFile = path.join(os.tmpdir(), 'test_un_js_' + Date.now() + '.py');
  fs.writeFileSync(tempFile, 'print("hello world")');

  try {
    const content = fs.readFileSync(tempFile, 'utf-8');
    assertEqual(content, 'print("hello world")');
  } finally {
    fs.unlinkSync(tempFile);
  }
});

test('Base64 encoding/decoding', () => {
  const content = 'print("hello world")';
  const encoded = Buffer.from(content).toString('base64');
  const decoded = Buffer.from(encoded, 'base64').toString();

  assertEqual(decoded, content);
});

test('Extract file basename', () => {
  const filepath = '/home/user/project/script.py';
  const basename = path.basename(filepath);

  assertEqual(basename, 'script.py');
});

test('Extract file extension', () => {
  const filepath = '/home/user/project/script.py';
  const ext = path.extname(filepath);

  assertEqual(ext, '.py');
});

// ============================================================================
// API Constants Tests
// ============================================================================

console.log('\n=== API Constants Tests ===');

test('API base URL format', () => {
  const API_BASE = 'https://api.unsandbox.com';

  assertEqual(API_BASE.startsWith('https://'), true);
  assertIncludes(API_BASE, 'unsandbox.com');
});

test('Portal base URL format', () => {
  const PORTAL_BASE = 'https://unsandbox.com';

  assertEqual(PORTAL_BASE.startsWith('https://'), true);
});

// ============================================================================
// Summary
// ============================================================================

console.log('\n=== Summary ===');
console.log(`Passed: ${passed}`);
console.log(`Failed: ${failed}`);
console.log(`Total:  ${passed + failed}`);

process.exit(failed > 0 ? 1 : 0);
