/**
 * Bug 3 regression — migration resume semantics.
 *
 * Covers:
 *   - statusForVersion prefers 'complete' over 'partial' (never regresses).
 *   - Three consecutive 'partial' entries flip a migration to 'wedged'.
 *   - 'retry' marker resets the counter; next run treats it as fresh.
 *   - appendCompletedMigration no-ops on double 'complete' (idempotency).
 *
 * Infrastructure: point HOME at a tmpdir so the ledger writes don't
 * stomp the real ~/.gbrain/migrations/completed.jsonl.
 */

import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
import { mkdtempSync, rmSync, readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';

let tmpHome: string;
const originalHome = process.env.HOME;
const originalGbrainHome = process.env.GBRAIN_HOME;

beforeEach(() => {
  tmpHome = mkdtempSync(join(tmpdir(), 'gbrain-migration-resume-'));
  // preferences.ts's gbrainDir() returns `$HOME/.gbrain` when GBRAIN_HOME
  // is unset. Set HOME only; clear any inherited GBRAIN_HOME so the test
  // body matches the migrations dir at `$tmpHome/.gbrain/migrations/`.
  process.env.HOME = tmpHome;
  delete process.env.GBRAIN_HOME;
});

afterEach(() => {
  if (originalHome) process.env.HOME = originalHome;
  else delete process.env.HOME;
  if (originalGbrainHome) process.env.GBRAIN_HOME = originalGbrainHome;
  else delete process.env.GBRAIN_HOME;
  try { rmSync(tmpHome, { recursive: true, force: true }); } catch { /* ignore */ }
});

describe('Bug 3 — statusForVersion semantics', () => {
  test("complete wins over partial regardless of order", async () => {
    const { __testing } = await import('../src/commands/apply-migrations.ts');
    const idx = __testing.indexCompleted([
      { version: '0.13.0', status: 'complete' },
      { version: '0.13.0', status: 'partial' },
    ] as any);
    expect(__testing.statusForVersion('0.13.0', idx)).toBe('complete');

    const idx2 = __testing.indexCompleted([
      { version: '0.13.0', status: 'partial' },
      { version: '0.13.0', status: 'complete' },
    ] as any);
    expect(__testing.statusForVersion('0.13.0', idx2)).toBe('complete');
  });

  test('two consecutive partials stay at partial', async () => {
    const { __testing } = await import('../src/commands/apply-migrations.ts');
    const idx = __testing.indexCompleted([
      { version: '0.13.0', status: 'partial' },
      { version: '0.13.0', status: 'partial' },
    ] as any);
    expect(__testing.statusForVersion('0.13.0', idx)).toBe('partial');
  });

  test('three consecutive partials flip to wedged', async () => {
    const { __testing } = await import('../src/commands/apply-migrations.ts');
    const idx = __testing.indexCompleted([
      { version: '0.13.0', status: 'partial' },
      { version: '0.13.0', status: 'partial' },
      { version: '0.13.0', status: 'partial' },
    ] as any);
    expect(__testing.statusForVersion('0.13.0', idx)).toBe('wedged');
  });

  test("retry marker resets the counter", async () => {
    const { __testing } = await import('../src/commands/apply-migrations.ts');
    const idx = __testing.indexCompleted([
      { version: '0.13.0', status: 'partial' },
      { version: '0.13.0', status: 'partial' },
      { version: '0.13.0', status: 'partial' },
      { version: '0.13.0', status: 'retry' },
    ] as any);
    // After 'retry', the version is pending (fresh start).
    expect(__testing.statusForVersion('0.13.0', idx)).toBe('pending');
  });

  test('complete after wedge is still complete (terminal)', async () => {
    const { __testing } = await import('../src/commands/apply-migrations.ts');
    const idx = __testing.indexCompleted([
      { version: '0.13.0', status: 'partial' },
      { version: '0.13.0', status: 'partial' },
      { version: '0.13.0', status: 'partial' },
      { version: '0.13.0', status: 'retry' },
      { version: '0.13.0', status: 'complete' },
    ] as any);
    expect(__testing.statusForVersion('0.13.0', idx)).toBe('complete');
  });
});

describe('Bug 3 — appendCompletedMigration idempotency', () => {
  test('writing complete when last entry is already complete is a no-op', async () => {
    const { appendCompletedMigration, loadCompletedMigrations } = await import('../src/core/preferences.ts');
    appendCompletedMigration({ version: '9.9.9', status: 'complete' });
    const first = loadCompletedMigrations().filter(e => e.version === '9.9.9');
    expect(first.length).toBe(1);

    appendCompletedMigration({ version: '9.9.9', status: 'complete' });
    const second = loadCompletedMigrations().filter(e => e.version === '9.9.9');
    expect(second.length).toBe(1);
  });

  test('partial always appends (needed for attempt-cap counter)', async () => {
    const { appendCompletedMigration, loadCompletedMigrations } = await import('../src/core/preferences.ts');
    appendCompletedMigration({ version: '9.9.9', status: 'partial' });
    appendCompletedMigration({ version: '9.9.9', status: 'partial' });
    const entries = loadCompletedMigrations().filter(e => e.version === '9.9.9');
    expect(entries.length).toBe(2);
  });

  test("'retry' status is accepted", async () => {
    const { appendCompletedMigration, loadCompletedMigrations } = await import('../src/core/preferences.ts');
    appendCompletedMigration({ version: '9.9.9', status: 'retry' } as any);
    const entries = loadCompletedMigrations().filter(e => e.version === '9.9.9');
    expect(entries.length).toBe(1);
    expect(entries[0].status).toBe('retry');
  });
});

describe('Bug 3 — orchestrator no longer writes the ledger directly', () => {
  test('v0_13_0 does not import appendCompletedMigration', async () => {
    const source = await Bun.file(new URL('../src/commands/migrations/v0_13_0.ts', import.meta.url)).text();
    expect(source).not.toContain('import { appendCompletedMigration }');
  });
  test('v0_13_1 does not import appendCompletedMigration', async () => {
    const source = await Bun.file(new URL('../src/commands/migrations/v0_13_1.ts', import.meta.url)).text();
    expect(source).not.toContain('import { appendCompletedMigration }');
  });
  test('v0_12_0 does not import appendCompletedMigration', async () => {
    const source = await Bun.file(new URL('../src/commands/migrations/v0_12_0.ts', import.meta.url)).text();
    expect(source).not.toContain('import { appendCompletedMigration }');
  });
  test('v0_12_2 does not import appendCompletedMigration', async () => {
    const source = await Bun.file(new URL('../src/commands/migrations/v0_12_2.ts', import.meta.url)).text();
    expect(source).not.toContain('import { appendCompletedMigration }');
  });
  test('v0_11_0 does not import appendCompletedMigration', async () => {
    const source = await Bun.file(new URL('../src/commands/migrations/v0_11_0.ts', import.meta.url)).text();
    // Import statement should not reference appendCompletedMigration; the
    // old call site is replaced with a comment.
    expect(source).not.toMatch(/import .*appendCompletedMigration.*from/);
  });

  test('apply-migrations.ts runner writes the ledger', async () => {
    const source = await Bun.file(new URL('../src/commands/apply-migrations.ts', import.meta.url)).text();
    expect(source).toContain("import { loadCompletedMigrations, appendCompletedMigration");
    expect(source).toContain("appendCompletedMigration({");
    expect(source).toContain("'retry'");
    expect(source).toContain('--force-retry');
    expect(source).toContain('MAX_CONSECUTIVE_PARTIALS');
  });
});

describe('Bug 3 — buildPlan surfaces wedged migrations', () => {
  test('wedged bucket exists in the plan', async () => {
    const { __testing } = await import('../src/commands/apply-migrations.ts');
    const idx = __testing.indexCompleted([
      { version: '0.13.0', status: 'partial' },
      { version: '0.13.0', status: 'partial' },
      { version: '0.13.0', status: 'partial' },
    ] as any);
    const plan = __testing.buildPlan(idx, '0.13.0', '0.13.0'); // filter to just this version
    expect(plan.wedged.length).toBe(1);
    expect(plan.wedged[0].version).toBe('0.13.0');
    expect(plan.pending.length).toBe(0);
    expect(plan.partial.length).toBe(0);
  });
});
