import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mkdtempSync, writeFileSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';

// We need to mock child_process before importing the module under test.
vi.mock('child_process', () => ({
  execSync: vi.fn(),
}));

import { execSync } from 'child_process';
import { detectDrift } from './drift-detect.js';

const mockExecSync = execSync as ReturnType<typeof vi.fn>;

const FAKE_SHA_A = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
const FAKE_SHA_B = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';

function makeYaml(forkedFromCommit?: string): string {
  if (!forkedFromCommit) return 'name: test-piece\ndescription: A piece\n';
  return `---\nforked_from_commit: ${forkedFromCommit}\n---\nname: test-piece\ndescription: A piece\n`;
}

describe('detectDrift', () => {
  let dir: string;

  beforeEach(() => {
    dir = mkdtempSync(join(tmpdir(), 'drift-test-'));
    vi.resetAllMocks();
  });

  // After each test clean up temp dir — not strictly required for correctness
  // but keeps the tmpdir tidy during long test runs.

  it('returns not drifted when custom path does not exist', () => {
    const customPath = join(dir, 'custom.yaml');
    const builtinPath = join(dir, 'builtin.yaml');
    writeFileSync(builtinPath, makeYaml());

    const result = detectDrift(customPath, builtinPath);

    expect(result).toEqual({ drifted: false, forkedFromCommit: null, latestCommit: null });
  });

  it('returns not drifted when builtin path does not exist', () => {
    const customPath = join(dir, 'custom.yaml');
    const builtinPath = join(dir, 'builtin.yaml');
    writeFileSync(customPath, makeYaml(FAKE_SHA_A));

    const result = detectDrift(customPath, builtinPath);

    expect(result).toEqual({ drifted: false, forkedFromCommit: null, latestCommit: null });
  });

  it('returns not drifted when SHAs match', () => {
    const customPath = join(dir, 'custom.yaml');
    const builtinPath = join(dir, 'builtin.yaml');
    writeFileSync(customPath, makeYaml(FAKE_SHA_A));
    writeFileSync(builtinPath, makeYaml());
    mockExecSync.mockReturnValue(`${FAKE_SHA_A}\n`);

    const result = detectDrift(customPath, builtinPath);

    expect(result.drifted).toBe(false);
    expect(result.forkedFromCommit).toBe(FAKE_SHA_A);
    expect(result.latestCommit).toBe(FAKE_SHA_A);
  });

  it('returns drifted when SHAs differ', () => {
    const customPath = join(dir, 'custom.yaml');
    const builtinPath = join(dir, 'builtin.yaml');
    writeFileSync(customPath, makeYaml(FAKE_SHA_A));
    writeFileSync(builtinPath, makeYaml());
    mockExecSync.mockReturnValue(`${FAKE_SHA_B}\n`);

    const result = detectDrift(customPath, builtinPath);

    expect(result.drifted).toBe(true);
    expect(result.forkedFromCommit).toBe(FAKE_SHA_A);
    expect(result.latestCommit).toBe(FAKE_SHA_B);
  });

  it('returns not drifted with both SHAs null when git is unavailable', () => {
    const customPath = join(dir, 'custom.yaml');
    const builtinPath = join(dir, 'builtin.yaml');
    writeFileSync(customPath, makeYaml(FAKE_SHA_A));
    writeFileSync(builtinPath, makeYaml());
    mockExecSync.mockImplementation(() => { throw new Error('git: command not found'); });

    const result = detectDrift(customPath, builtinPath);

    expect(result.drifted).toBe(false);
    expect(result.forkedFromCommit).toBe(FAKE_SHA_A);
    expect(result.latestCommit).toBeNull();
  });

  it('returns not drifted when custom has no forked_from_commit frontmatter', () => {
    const customPath = join(dir, 'custom.yaml');
    const builtinPath = join(dir, 'builtin.yaml');
    // Write custom WITHOUT the frontmatter field.
    writeFileSync(customPath, makeYaml());
    writeFileSync(builtinPath, makeYaml());
    mockExecSync.mockReturnValue(`${FAKE_SHA_B}\n`);

    const result = detectDrift(customPath, builtinPath);

    expect(result.drifted).toBe(false);
    expect(result.forkedFromCommit).toBeNull();
    expect(result.latestCommit).toBe(FAKE_SHA_B);
  });
});
