import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import {
  getCommentStyle,
  buildMarkerBlock,
  hasMarker,
  applyMarker,
  buildManifest,
} from './ai-marker.js';

const META = {
  conversionId: 'conv-001',
  sourceLanguage: 'cobol',
  targetLanguage: 'java',
  timestamp: '2026-01-01T00:00:00.000Z',
  platformVersion: '1.0.0',
};

// ── getCommentStyle ───────────────────────────────────────────────────────────

describe('getCommentStyle', () => {
  it('returns c-block for java, ts, go, cs, rs, php, swift', () => {
    for (const ext of ['Main.java', 'index.ts', 'main.go', 'App.cs', 'lib.rs', 'handler.php', 'view.swift']) {
      assert.equal(getCommentStyle(ext), 'c-block', `expected c-block for ${ext}`);
    }
  });

  it('returns hash for py, rb, sh, yml, r', () => {
    for (const ext of ['script.py', 'app.rb', 'run.sh', 'config.yml', 'analysis.r']) {
      assert.equal(getCommentStyle(ext), 'hash', `expected hash for ${ext}`);
    }
  });

  it('returns sql for .sql, .hs, .lua, .ada, .adb, .ads', () => {
    for (const ext of ['query.sql', 'Main.hs', 'module.lua', 'spec.ada', 'body.adb', 'header.ads']) {
      assert.equal(getCommentStyle(ext), 'sql', `expected sql for ${ext}`);
    }
  });

  it('returns percent for .erl, .hrl, .pro', () => {
    for (const ext of ['app.erl', 'header.hrl', 'rules.pro']) {
      assert.equal(getCommentStyle(ext), 'percent', `expected percent for ${ext}`);
    }
  });

  it('returns semicolon for lisp-family files', () => {
    for (const ext of ['core.lisp', 'ns.clj', 'main.cljs', 'init.el']) {
      assert.equal(getCommentStyle(ext), 'semicolon', `expected semicolon for ${ext}`);
    }
  });

  it('returns xml for .xml, .csproj, .xaml, .config', () => {
    for (const ext of ['pom.xml', 'App.csproj', 'view.xaml', 'app.config']) {
      assert.equal(getCommentStyle(ext), 'xml', `expected xml for ${ext}`);
    }
  });

  it('returns sidecar for .json, .html, .wasm', () => {
    for (const ext of ['package.json', 'index.html', 'module.wasm']) {
      assert.equal(getCommentStyle(ext), 'sidecar', `expected sidecar for ${ext}`);
    }
  });

  it('defaults to c-block for unknown extensions', () => {
    assert.equal(getCommentStyle('file.xyz'), 'c-block');
    assert.equal(getCommentStyle('noextension'), 'c-block');
    assert.equal(getCommentStyle(''), 'c-block');
  });

  it('is case-insensitive for extensions', () => {
    assert.equal(getCommentStyle('Main.JAVA'), 'c-block');
    assert.equal(getCommentStyle('Script.PY'), 'hash');
  });

  it('handles Windows-style paths', () => {
    assert.equal(getCommentStyle('C:\\project\\src\\Main.java'), 'c-block');
  });

  it('handles deeply nested unix paths', () => {
    assert.equal(getCommentStyle('/home/user/project/src/main/App.kt'), 'c-block');
  });
});

// ── buildMarkerBlock ──────────────────────────────────────────────────────────

describe('buildMarkerBlock', () => {
  it('produces c-block comment with all scriba fields', () => {
    const block = buildMarkerBlock('c-block', META);
    assert.ok(block.startsWith('/*'), 'expected /* opening');
    assert.ok(block.includes(' * @scriba-ai-generated: true'));
    assert.ok(block.includes(`@scriba-conversion-id: ${META.conversionId}`));
    assert.ok(block.includes(`@scriba-source-language: ${META.sourceLanguage}`));
    assert.ok(block.includes(`@scriba-target-language: ${META.targetLanguage}`));
    assert.ok(block.endsWith(' */\n'));
  });

  it('produces hash comment for python-style files', () => {
    const block = buildMarkerBlock('hash', META);
    assert.ok(block.includes('# @scriba-ai-generated: true'));
    assert.ok(!block.includes('/*'));
  });

  it('produces sql (--) comment', () => {
    const block = buildMarkerBlock('sql', META);
    assert.ok(block.includes('-- @scriba-ai-generated: true'));
  });

  it('produces percent comment for erlang', () => {
    const block = buildMarkerBlock('percent', META);
    assert.ok(block.includes('% @scriba-ai-generated: true'));
  });

  it('produces semicolon comment for lisp', () => {
    const block = buildMarkerBlock('semicolon', META);
    assert.ok(block.includes('; @scriba-ai-generated: true'));
  });

  it('produces xml comment block', () => {
    const block = buildMarkerBlock('xml', META);
    assert.ok(block.startsWith('<!--'));
    assert.ok(block.includes('@scriba-ai-generated: true'));
    assert.ok(block.includes('-->'));
  });

  it('produces JSON sidecar for sidecar style', () => {
    const block = buildMarkerBlock('sidecar', META);
    const parsed = JSON.parse(block);
    assert.equal(parsed.aiGenerated, true);
    assert.equal(parsed.conversionId, META.conversionId);
    assert.equal(parsed.sourceLanguage, META.sourceLanguage);
    assert.equal(parsed.targetLanguage, META.targetLanguage);
  });

  it('fills in unknown defaults for missing meta fields', () => {
    const block = buildMarkerBlock('c-block', {
      conversionId: '',
      sourceLanguage: '',
      targetLanguage: '',
      timestamp: '',
      platformVersion: '',
    });
    assert.ok(block.includes('@scriba-source-language: unknown'));
    assert.ok(block.includes('@scriba-target-language: unknown'));
    assert.ok(block.includes('@scriba-conversion-id: unknown'));
  });
});

// ── hasMarker ─────────────────────────────────────────────────────────────────

describe('hasMarker', () => {
  it('detects @scriba-ai-generated: true in c-block comment', () => {
    const content = buildMarkerBlock('c-block', META) + '\npublic class Foo {}';
    assert.equal(hasMarker(content), true);
  });

  it('detects @scriba-ai-generated: true in hash comment', () => {
    const content = buildMarkerBlock('hash', META) + '\ndef foo(): pass';
    assert.equal(hasMarker(content), true);
  });

  it('detects "aiGenerated": true in JSON sidecar blocks', () => {
    const sidecar = buildMarkerBlock('sidecar', META);
    assert.equal(hasMarker(sidecar), true);
  });

  it('returns false for clean source code', () => {
    assert.equal(hasMarker('public class Foo { void bar() {} }'), false);
    assert.equal(hasMarker('def foo():\n    return 42'), false);
  });

  it('returns false for empty or falsy input', () => {
    assert.equal(hasMarker(''), false);
    assert.equal(hasMarker(null as unknown as string), false);
    assert.equal(hasMarker(undefined as unknown as string), false);
  });
});

// ── applyMarker ───────────────────────────────────────────────────────────────

describe('applyMarker', () => {
  it('prepends c-block marker to a java file', () => {
    const result = applyMarker('Main.java', 'public class Main {}', META);
    assert.equal(result.usedSidecar, false);
    assert.ok(result.marked.startsWith('/*'), 'marker should be at top');
    assert.ok(result.marked.includes('public class Main {}'));
  });

  it('does not double-mark already marked content', () => {
    const first = applyMarker('Main.java', 'public class Main {}', META);
    const second = applyMarker('Main.java', first.marked, META);
    assert.equal(first.marked, second.marked);
  });

  it('returns usedSidecar=true for JSON files', () => {
    const result = applyMarker('package.json', '{"name":"test"}', META);
    assert.equal(result.usedSidecar, true);
    assert.ok(result.sidecarPath?.endsWith('.scriba-marker.json'));
    assert.ok(result.sidecarContent);
    assert.equal(result.marked, '{"name":"test"}');
  });

  it('returns usedSidecar=true for HTML files', () => {
    const result = applyMarker('index.html', '<html></html>', META);
    assert.equal(result.usedSidecar, true);
  });

  it('inserts hash marker for python files', () => {
    const result = applyMarker('script.py', 'def foo(): pass', META);
    assert.ok(result.marked.includes('# @scriba-ai-generated: true'));
  });

  it('places marker after shebang line', () => {
    const content = '#!/usr/bin/env python3\nimport os\n';
    const result = applyMarker('script.py', content, META);
    const lines = result.marked.split('\n');
    assert.ok(lines[0]!.startsWith('#!'), 'shebang should stay first');
    assert.ok(result.marked.includes('# @scriba-ai-generated: true'));
    const shebangIdx = result.marked.indexOf('#!/usr/bin/env python3');
    const markerIdx = result.marked.indexOf('# @scriba-ai-generated');
    assert.ok(markerIdx > shebangIdx, 'marker should come after shebang');
  });

  it('places marker after license block (hash-comment style)', () => {
    // The license detector matches "# Copyright" but not "// Copyright" (the
    // regex only consumes a single character from [/*#;%-], so "//" leaves a
    // trailing "/" before "Copyright" which breaks the match).
    const content = '# Copyright 2025 Acme Corp\n# Licensed under MIT\n\ndef foo(): pass';
    const result = applyMarker('script.py', content, META);
    const licenseIdx = result.marked.indexOf('Copyright');
    const markerIdx = result.marked.indexOf('@scriba-ai-generated');
    assert.ok(markerIdx > licenseIdx, 'marker should come after license');
  });

  it('handles empty content', () => {
    const result = applyMarker('Main.java', '', META);
    assert.equal(result.usedSidecar, false);
    assert.ok(result.marked.includes('@scriba-ai-generated: true'));
  });

  it('handles null content gracefully', () => {
    const result = applyMarker('Main.java', null as unknown as string, META);
    assert.equal(result.usedSidecar, false);
    assert.ok(result.marked.includes('@scriba-ai-generated: true'));
  });
});

// ── buildManifest ─────────────────────────────────────────────────────────────

describe('buildManifest', () => {
  it('produces valid JSON with all expected fields', () => {
    const manifest = buildManifest(META, ['src/Foo.java', 'src/Bar.java']);
    const parsed = JSON.parse(manifest);
    assert.equal(parsed.aiGenerated, true);
    assert.equal(parsed.conversionId, META.conversionId);
    assert.equal(parsed.sourceLanguage, META.sourceLanguage);
    assert.equal(parsed.targetLanguage, META.targetLanguage);
    assert.equal(parsed.timestamp, META.timestamp);
    assert.equal(parsed.platformVersion, META.platformVersion);
    assert.ok(Array.isArray(parsed.files));
    assert.deepEqual(parsed.files, ['src/Bar.java', 'src/Foo.java']);
  });

  it('sorts file paths alphabetically', () => {
    const manifest = buildManifest(META, ['z.java', 'a.java', 'm.java']);
    const parsed = JSON.parse(manifest);
    assert.deepEqual(parsed.files, ['a.java', 'm.java', 'z.java']);
  });

  it('handles empty file list', () => {
    const manifest = buildManifest(META, []);
    const parsed = JSON.parse(manifest);
    assert.deepEqual(parsed.files, []);
  });

  it('does not mutate the input array', () => {
    const files = ['z.java', 'a.java'];
    buildManifest(META, files);
    assert.deepEqual(files, ['z.java', 'a.java']);
  });
});
