import { promises as fs } from 'fs';
import * as os from 'os';
import * as path from 'path';

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import {
  OpenCodeRuntimeManifestEvidenceReader,
  getOpenCodeRuntimeManifestPath,
  getOpenCodeLaneScopedRuntimeFilePath,
  getOpenCodeRuntimeLaneIndexPath,
  getOpenCodeTeamRuntimeDirectory,
  inspectOpenCodeRuntimeLaneStorage,
  migrateLegacyOpenCodeRuntimeState,
  prepareOpenCodeRuntimeLaneForLaunchGeneration,
  readCommittedOpenCodeBootstrapSessionEvidence,
  readOpenCodeRuntimeLaneIndex,
  recoverStaleOpenCodeRuntimeLaneIndexEntry,
  setOpenCodeRuntimeActiveRunManifest,
  upsertOpenCodeRuntimeLaneIndexEntry,
} from '../../../../src/main/services/team/opencode/store/OpenCodeRuntimeManifestEvidenceReader';
import {
  createRuntimeStoreManifestStore,
  createRuntimeStoreReceiptStore,
  OPENCODE_RUNTIME_STORE_DESCRIPTORS,
  RuntimeStoreBatchWriter,
  createDefaultRuntimeStoreManifest,
} from '../../../../src/main/services/team/opencode/store/RuntimeStoreManifest';

describe('OpenCodeRuntimeManifestEvidenceReader migration', () => {
  let tempDir: string;
  let now: Date;

  beforeEach(async () => {
    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'opencode-runtime-migration-'));
    now = new Date('2026-04-22T10:00:00.000Z');
  });

  afterEach(async () => {
    await fs.rm(tempDir, { recursive: true, force: true });
  });

  async function writeCommittedSessionStore(input: {
    teamName: string;
    laneId: string;
    sessions: unknown[];
  }) {
    const descriptor = OPENCODE_RUNTIME_STORE_DESCRIPTORS.find(
      (candidate) => candidate.schemaName === 'opencode.sessionStore'
    );
    if (!descriptor) throw new Error('session descriptor missing');
    const manifestPath = getOpenCodeRuntimeManifestPath(tempDir, input.teamName, input.laneId);
    const runtimeDirectory = path.dirname(manifestPath);
    await fs.mkdir(runtimeDirectory, { recursive: true });
    const writer = new RuntimeStoreBatchWriter(
      runtimeDirectory,
      createRuntimeStoreManifestStore({ filePath: manifestPath, teamName: input.teamName }),
      createRuntimeStoreReceiptStore({
        filePath: path.join(runtimeDirectory, 'opencode-runtime-receipts.json'),
      }),
      {
        clock: () => now,
        batchIdFactory: () => 'batch-1',
        receiptIdFactory: () => 'receipt-1',
      }
    );
    await writer.writeBatch({
      teamName: input.teamName,
      runId: 'runtime-run-1',
      capabilitySnapshotId: null,
      behaviorFingerprint: null,
      reason: 'launch_checkpoint',
      writes: [{ descriptor, data: { sessions: input.sessions } }],
    });
  }

  it('reads only committed OpenCode bootstrap check-in session evidence', async () => {
    const teamName = 'team-committed-session';
    const laneId = 'secondary:opencode:tom';
    await writeCommittedSessionStore({
      teamName,
      laneId,
      sessions: [
        {
          id: 'ses-tom',
          teamName,
          memberName: 'tom',
          runId: 'runtime-run-1',
          laneId,
          providerId: 'opencode',
          observedAt: '2026-04-22T10:00:00.000Z',
          source: 'runtime_bootstrap_checkin',
        },
        {
          id: 'ses-ignored',
          teamName,
          memberName: 'tom',
          runId: 'runtime-run-1',
          laneId,
          source: 'member_briefing',
        },
      ],
    });

    await expect(
      readCommittedOpenCodeBootstrapSessionEvidence({ teamsBasePath: tempDir, teamName, laneId })
    ).resolves.toMatchObject({
      state: 'healthy',
      committed: true,
      sessions: [
        {
          id: 'ses-tom',
          teamName,
          memberName: 'tom',
          laneId,
          runId: 'runtime-run-1',
          source: 'runtime_bootstrap_checkin',
        },
      ],
    });
  });

  it('does not treat an uncommitted session file as OpenCode bootstrap evidence', async () => {
    const teamName = 'team-uncommitted-session';
    const laneId = 'secondary:opencode:tom';
    const sessionPath = getOpenCodeLaneScopedRuntimeFilePath({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      fileName: 'opencode-sessions.json',
    });
    await fs.mkdir(path.dirname(sessionPath), { recursive: true });
    await fs.writeFile(
      sessionPath,
      JSON.stringify({
        schemaVersion: 1,
        updatedAt: '2026-04-22T10:00:00.000Z',
        data: {
          sessions: [
            {
              id: 'ses-tom',
              teamName,
              memberName: 'tom',
              laneId,
              source: 'runtime_bootstrap_checkin',
            },
          ],
        },
      }),
      'utf8'
    );

    const evidence = await readCommittedOpenCodeBootstrapSessionEvidence({
      teamsBasePath: tempDir,
      teamName,
      laneId,
    });

    expect(evidence.committed).toBe(false);
    expect(evidence.state).toBe('uncommitted_write');
    expect(evidence.sessions).toEqual([]);
  });

  it('migrates legacy team-scoped OpenCode runtime files into the addressed lane', async () => {
    const teamName = 'team-alpha';
    const laneId = 'secondary:opencode:alice';
    const runtimeDir = getOpenCodeTeamRuntimeDirectory(tempDir, teamName);

    await fs.mkdir(runtimeDir, { recursive: true });
    await fs.writeFile(path.join(runtimeDir, 'manifest.json'), '{"highWatermark":7}\n', 'utf8');
    await fs.writeFile(
      path.join(runtimeDir, 'opencode-launch-transaction.json'),
      '{"transactionId":"tx-1"}\n',
      'utf8'
    );

    const result = await migrateLegacyOpenCodeRuntimeState({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      clock: () => now,
    });

    expect(result).toEqual({
      migrated: true,
      degraded: false,
      diagnostics: ['migrated 2 legacy OpenCode runtime files'],
    });

    await expect(fs.readFile(path.join(runtimeDir, 'manifest.json'), 'utf8')).rejects.toThrow();
    await expect(
      fs.readFile(path.join(runtimeDir, 'opencode-launch-transaction.json'), 'utf8')
    ).rejects.toThrow();

    await expect(
      fs.readFile(
        getOpenCodeLaneScopedRuntimeFilePath({
          teamsBasePath: tempDir,
          teamName,
          laneId,
          fileName: 'manifest.json',
        }),
        'utf8'
      )
    ).resolves.toBe('{"highWatermark":7}\n');
    await expect(
      fs.readFile(
        getOpenCodeLaneScopedRuntimeFilePath({
          teamsBasePath: tempDir,
          teamName,
          laneId,
          fileName: 'opencode-launch-transaction.json',
        }),
        'utf8'
      )
    ).resolves.toBe('{"transactionId":"tx-1"}\n');

    await expect(fs.readFile(getOpenCodeRuntimeLaneIndexPath(tempDir, teamName), 'utf8')).resolves.toContain(
      `"${laneId}"`
    );
    await expect(readOpenCodeRuntimeLaneIndex(tempDir, teamName)).resolves.toMatchObject({
      lanes: {
        [laneId]: {
          laneId,
          state: 'active',
          diagnostics: [
            `migrated legacy team-scoped OpenCode runtime state at ${now.toISOString()}`,
          ],
        },
      },
    });
  });

  it('marks ambiguous legacy runtime state as degraded instead of guessing a lane', async () => {
    const teamName = 'team-beta';
    const laneId = 'secondary:opencode:alice';
    const otherLaneId = 'secondary:opencode:bob';
    const runtimeDir = getOpenCodeTeamRuntimeDirectory(tempDir, teamName);

    await fs.mkdir(runtimeDir, { recursive: true });
    await fs.writeFile(path.join(runtimeDir, 'manifest.json'), '{"highWatermark":11}\n', 'utf8');
    await upsertOpenCodeRuntimeLaneIndexEntry({
      teamsBasePath: tempDir,
      teamName,
      laneId: otherLaneId,
      state: 'active',
    });

    const result = await migrateLegacyOpenCodeRuntimeState({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      clock: () => now,
    });

    expect(result.migrated).toBe(false);
    expect(result.degraded).toBe(true);
    expect(result.diagnostics).toEqual([
      `Legacy OpenCode runtime state is ambiguous for ${teamName}; existing lanes: ${otherLaneId}`,
    ]);

    await expect(fs.readFile(path.join(runtimeDir, 'manifest.json'), 'utf8')).resolves.toBe(
      '{"highWatermark":11}\n'
    );
    await expect(
      fs.readFile(
        getOpenCodeLaneScopedRuntimeFilePath({
          teamsBasePath: tempDir,
          teamName,
          laneId,
          fileName: 'manifest.json',
        }),
        'utf8'
      )
    ).rejects.toThrow();

    await expect(readOpenCodeRuntimeLaneIndex(tempDir, teamName)).resolves.toMatchObject({
      lanes: {
        [otherLaneId]: {
          laneId: otherLaneId,
          state: 'active',
        },
        [laneId]: {
          laneId,
          state: 'degraded',
          diagnostics: [
            `Legacy OpenCode runtime state is ambiguous for ${teamName}; existing lanes: ${otherLaneId}`,
          ],
        },
      },
    });
  });

  it('does not fall back to team-scoped legacy manifest when sibling lane metadata already exists', async () => {
    const teamName = 'team-gamma';
    const laneId = 'secondary:opencode:alice';
    const otherLaneId = 'secondary:opencode:bob';
    const runtimeDir = getOpenCodeTeamRuntimeDirectory(tempDir, teamName);
    const reader = new OpenCodeRuntimeManifestEvidenceReader({ teamsBasePath: tempDir });

    await fs.mkdir(runtimeDir, { recursive: true });
    await fs.writeFile(
      path.join(
        runtimeDir,
        'manifest.json'
      ),
      JSON.stringify({
        schemaVersion: 1,
        updatedAt: '2026-04-22T10:00:00.000Z',
        data: {
          schemaVersion: 1,
          teamName,
          activeRunId: 'legacy-run',
          activeCapabilitySnapshotId: 'cap-1',
          activeBehaviorFingerprint: null,
          highWatermark: 11,
          lastCommittedBatchId: null,
          lastPreparingBatchId: null,
          entries: [],
          lastRecoveryPlanId: null,
          updatedAt: '2026-04-22T10:00:00.000Z',
        },
      }),
      'utf8'
    );
    await upsertOpenCodeRuntimeLaneIndexEntry({
      teamsBasePath: tempDir,
      teamName,
      laneId: otherLaneId,
      state: 'active',
    });

    await expect(reader.read(teamName, laneId)).resolves.toEqual({
      highWatermark: 0,
      activeRunId: null,
      capabilitySnapshotId: null,
    });
  });

  it('still falls back to team-scoped legacy manifest for safe single-lane backward compatibility', async () => {
    const teamName = 'team-delta';
    const laneId = 'secondary:opencode:alice';
    const runtimeDir = getOpenCodeTeamRuntimeDirectory(tempDir, teamName);
    const reader = new OpenCodeRuntimeManifestEvidenceReader({ teamsBasePath: tempDir });

    await fs.mkdir(runtimeDir, { recursive: true });
    await fs.writeFile(
      path.join(runtimeDir, 'manifest.json'),
      JSON.stringify({
        schemaVersion: 1,
        updatedAt: '2026-04-22T10:00:00.000Z',
        data: {
          schemaVersion: 1,
          teamName,
          activeRunId: 'legacy-run',
          activeCapabilitySnapshotId: 'cap-1',
          activeBehaviorFingerprint: null,
          highWatermark: 11,
          lastCommittedBatchId: null,
          lastPreparingBatchId: null,
          entries: [],
          lastRecoveryPlanId: null,
          updatedAt: '2026-04-22T10:00:00.000Z',
        },
      }),
      'utf8'
    );

    await expect(reader.read(teamName, laneId)).resolves.toEqual({
      highWatermark: 11,
      activeRunId: 'legacy-run',
      capabilitySnapshotId: 'cap-1',
    });
  });

  it('reports missing lane storage when an active lane index entry has no lane dir or state', async () => {
    const teamName = 'team-epsilon';
    const laneId = 'secondary:opencode:alice';

    await upsertOpenCodeRuntimeLaneIndexEntry({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      state: 'active',
    });

    await expect(
      inspectOpenCodeRuntimeLaneStorage({
        teamsBasePath: tempDir,
        teamName,
        laneId,
      })
    ).resolves.toEqual({
      laneDirectoryExists: false,
      hasStateOnDisk: false,
      hasRuntimeEvidenceOnDisk: false,
      manifestEntryCount: null,
      manifestUpdatedAt: null,
      fileNames: [],
    });

    const result = await recoverStaleOpenCodeRuntimeLaneIndexEntry({
      teamsBasePath: tempDir,
      teamName,
      laneId,
    });

    expect(result).toEqual({
      stale: true,
      degraded: true,
      diagnostics: [
        `OpenCode lane ${laneId} is marked active in lanes.json, but no lane state exists on disk.`,
      ],
    });
    await expect(readOpenCodeRuntimeLaneIndex(tempDir, teamName)).resolves.toMatchObject({
      lanes: {
        [laneId]: {
          laneId,
          state: 'degraded',
          diagnostics: [
            `OpenCode lane ${laneId} is marked active in lanes.json, but no lane state exists on disk.`,
          ],
        },
      },
    });
  });

  it('degrades an active lane that only has a stale empty runtime manifest', async () => {
    const teamName = 'team-empty-manifest';
    const laneId = 'secondary:opencode:bob';

    await upsertOpenCodeRuntimeLaneIndexEntry({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      state: 'active',
    });
    await setOpenCodeRuntimeActiveRunManifest({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      runId: 'run-empty',
      clock: () => new Date('2026-04-22T09:55:00.000Z'),
    });
    await fs.writeFile(
      getOpenCodeLaneScopedRuntimeFilePath({
        teamsBasePath: tempDir,
        teamName,
        laneId,
        fileName: 'opencode-prompt-delivery-ledger.json',
      }),
      JSON.stringify({ records: [] }),
      'utf8'
    );

    await expect(
      inspectOpenCodeRuntimeLaneStorage({
        teamsBasePath: tempDir,
        teamName,
        laneId,
      })
    ).resolves.toMatchObject({
      laneDirectoryExists: true,
      hasStateOnDisk: true,
      hasRuntimeEvidenceOnDisk: false,
      manifestEntryCount: 0,
      fileNames: ['manifest.json', 'opencode-prompt-delivery-ledger.json'],
    });

    const result = await recoverStaleOpenCodeRuntimeLaneIndexEntry({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      clock: () => now,
      emptyLaneStaleAfterMs: 150_000,
    });

    expect(result).toEqual({
      stale: true,
      degraded: true,
      diagnostics: [
        `OpenCode lane ${laneId} is marked active in lanes.json, but its runtime manifest has no committed runtime evidence after launch grace.`,
      ],
    });
    await expect(readOpenCodeRuntimeLaneIndex(tempDir, teamName)).resolves.toMatchObject({
      lanes: {
        [laneId]: {
          laneId,
          state: 'degraded',
          diagnostics: [
            `OpenCode lane ${laneId} is marked active in lanes.json, but its runtime manifest has no committed runtime evidence after launch grace.`,
          ],
        },
      },
    });
  });

  it('does not degrade a fresh active lane while the empty runtime manifest is still inside launch grace', async () => {
    const teamName = 'team-fresh-empty-manifest';
    const laneId = 'secondary:opencode:bob';

    await upsertOpenCodeRuntimeLaneIndexEntry({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      state: 'active',
    });
    await setOpenCodeRuntimeActiveRunManifest({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      runId: 'run-fresh',
      clock: () => new Date('2026-04-22T09:59:00.000Z'),
    });

    const result = await recoverStaleOpenCodeRuntimeLaneIndexEntry({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      clock: () => now,
      emptyLaneStaleAfterMs: 150_000,
    });

    expect(result).toEqual({
      stale: false,
      degraded: false,
      diagnostics: [],
    });
    await expect(readOpenCodeRuntimeLaneIndex(tempDir, teamName)).resolves.toMatchObject({
      lanes: {
        [laneId]: {
          laneId,
          state: 'active',
        },
      },
    });
  });

  it('quarantines malformed lanes.json and falls back to an empty index', async () => {
    const teamName = 'team-zeta';
    const runtimeDir = getOpenCodeTeamRuntimeDirectory(tempDir, teamName);
    const filePath = getOpenCodeRuntimeLaneIndexPath(tempDir, teamName);
    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

    try {
      await fs.mkdir(runtimeDir, { recursive: true });
      await fs.writeFile(
        filePath,
        ['{', '  "version": 1,', '  "updatedAt": "2026-04-22T10:00:00.000Z",', '  "lanes": {}', '}', '}'].join('\n'),
        'utf8'
      );

      await expect(readOpenCodeRuntimeLaneIndex(tempDir, teamName)).resolves.toEqual({
        version: 1,
        updatedAt: expect.any(String),
        lanes: {},
      });
      await expect(fs.readFile(filePath, 'utf8')).rejects.toThrow();

      const runtimeEntries = await fs.readdir(runtimeDir);
      expect(runtimeEntries.some((entry) => /^lanes\.invalid\.\d+\.json$/.test(entry))).toBe(true);
    } finally {
      warnSpy.mockRestore();
    }
  });

  it('serializes concurrent lane index upserts without losing sibling lanes', async () => {
    const teamName = 'team-eta';

    await Promise.all([
      upsertOpenCodeRuntimeLaneIndexEntry({
        teamsBasePath: tempDir,
        teamName,
        laneId: 'secondary:opencode:bob',
        state: 'active',
      }),
      upsertOpenCodeRuntimeLaneIndexEntry({
        teamsBasePath: tempDir,
        teamName,
        laneId: 'secondary:opencode:jack',
        state: 'active',
      }),
      upsertOpenCodeRuntimeLaneIndexEntry({
        teamsBasePath: tempDir,
        teamName,
        laneId: 'secondary:opencode:tom',
        state: 'active',
      }),
    ]);

    await expect(readOpenCodeRuntimeLaneIndex(tempDir, teamName)).resolves.toMatchObject({
      lanes: {
        'secondary:opencode:bob': { state: 'active' },
        'secondary:opencode:jack': { state: 'active' },
        'secondary:opencode:tom': { state: 'active' },
      },
    });
  });

  it('persists lane-scoped activeRunId for runtime evidence after app restart', async () => {
    const teamName = 'team-theta';
    const laneId = 'secondary:opencode:jack';
    const reader = new OpenCodeRuntimeManifestEvidenceReader({ teamsBasePath: tempDir });

    await setOpenCodeRuntimeActiveRunManifest({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      runId: 'run-opencode-jack',
      clock: () => now,
    });

    await expect(reader.read(teamName, laneId)).resolves.toMatchObject({
      activeRunId: 'run-opencode-jack',
      highWatermark: 0,
    });
  });

  it('updates raw legacy runtime manifests without dropping existing capability metadata', async () => {
    const teamName = 'team-iota';
    const laneId = 'secondary:opencode:alice';
    const manifestPath = getOpenCodeRuntimeManifestPath(tempDir, teamName, laneId);
    const legacyManifest = {
      ...createDefaultRuntimeStoreManifest(teamName, '2026-04-22T10:00:00.000Z'),
      activeRunId: 'run-old',
      activeCapabilitySnapshotId: 'cap-existing',
      activeBehaviorFingerprint: 'behavior-existing',
      highWatermark: 5,
    };
    await fs.mkdir(path.dirname(manifestPath), { recursive: true });
    await fs.writeFile(manifestPath, `${JSON.stringify(legacyManifest, null, 2)}\n`, 'utf8');

    await setOpenCodeRuntimeActiveRunManifest({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      runId: 'run-new',
      clock: () => now,
    });

    await expect(
      new OpenCodeRuntimeManifestEvidenceReader({ teamsBasePath: tempDir }).read(teamName, laneId)
    ).resolves.toMatchObject({
      activeRunId: 'run-new',
      capabilitySnapshotId: 'cap-existing',
      highWatermark: 0,
    });
  });

  it('preserves committed manifest highWatermark when persisting activeRunId', async () => {
    const teamName = 'team-kappa';
    const laneId = 'secondary:opencode:bob';
    const manifestPath = getOpenCodeRuntimeManifestPath(tempDir, teamName, laneId);
    const committedManifest = {
      ...createDefaultRuntimeStoreManifest(teamName, '2026-04-22T10:00:00.000Z'),
      activeRunId: 'run-old',
      highWatermark: 5,
      lastCommittedBatchId: 'batch-1',
      entries: [
        {
          schemaName: 'opencode.launchState',
          schemaVersion: 1,
          relativePath: 'launch-state.json',
          contentHash: 'sha256:test',
          fileSize: 12,
          mtimeMs: 123,
          runId: 'run-old',
          capabilitySnapshotId: null,
          behaviorFingerprint: null,
          lastWriteReceiptId: 'receipt-1',
          state: 'healthy',
        },
      ],
    };
    await fs.mkdir(path.dirname(manifestPath), { recursive: true });
    await fs.writeFile(manifestPath, `${JSON.stringify(committedManifest, null, 2)}\n`, 'utf8');

    await setOpenCodeRuntimeActiveRunManifest({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      runId: 'run-new',
      clock: () => now,
    });

    await expect(
      new OpenCodeRuntimeManifestEvidenceReader({ teamsBasePath: tempDir }).read(teamName, laneId)
    ).resolves.toMatchObject({
      activeRunId: 'run-new',
      highWatermark: 5,
    });
  });
});

describe('prepareOpenCodeRuntimeLaneForLaunchGeneration', () => {
  let tempDir: string;
  const teamName = 'team-launch-generation';
  const laneId = 'secondary:opencode:bob';
  const now = new Date('2026-05-09T10:00:00.000Z');

  beforeEach(async () => {
    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'opencode-runtime-generation-'));
  });

  afterEach(async () => {
    await fs.rm(tempDir, { recursive: true, force: true });
  });

  async function writeSessionStoreForRun(runId: string): Promise<void> {
    const descriptor = OPENCODE_RUNTIME_STORE_DESCRIPTORS.find(
      (candidate) => candidate.schemaName === 'opencode.sessionStore'
    );
    if (!descriptor) throw new Error('session descriptor missing');
    const manifestPath = getOpenCodeRuntimeManifestPath(tempDir, teamName, laneId);
    const runtimeDirectory = path.dirname(manifestPath);
    await fs.mkdir(runtimeDirectory, { recursive: true });
    const writer = new RuntimeStoreBatchWriter(
      runtimeDirectory,
      createRuntimeStoreManifestStore({
        filePath: manifestPath,
        teamName,
        clock: () => now,
      }),
      createRuntimeStoreReceiptStore({
        filePath: path.join(runtimeDirectory, 'opencode-runtime-receipts.json'),
      }),
      {
        clock: () => now,
        batchIdFactory: () => `batch-${runId}`,
        receiptIdFactory: () => `receipt-${runId}`,
      }
    );
    await writer.writeBatch({
      teamName,
      runId,
      capabilitySnapshotId: null,
      behaviorFingerprint: null,
      reason: 'launch_checkpoint',
      writes: [
        {
          descriptor,
          data: {
            sessions: [
              {
                id: `session-${runId}`,
                teamName,
                memberName: 'bob',
                runId,
                laneId,
                providerId: 'opencode',
                source: 'runtime_bootstrap_checkin',
                observedAt: now.toISOString(),
              },
            ],
          },
        },
      ],
    });
  }

  async function readManifest() {
    return createRuntimeStoreManifestStore({
      filePath: getOpenCodeRuntimeManifestPath(tempDir, teamName, laneId),
      teamName,
    }).read();
  }

  it('creates a fresh active manifest when the lane has no manifest', async () => {
    const result = await prepareOpenCodeRuntimeLaneForLaunchGeneration({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      runId: 'run-new',
      reason: 'test_launch',
      clock: () => now,
    });

    await expect(readManifest()).resolves.toMatchObject({
      activeRunId: 'run-new',
      highWatermark: 0,
      entries: [],
    });
    await expect(readOpenCodeRuntimeLaneIndex(tempDir, teamName)).resolves.toMatchObject({
      lanes: {
        [laneId]: {
          laneId,
          state: 'active',
        },
      },
    });
    expect(result).toMatchObject({ reset: false, reason: 'fresh_manifest_created' });
  });

  it('reuses a same-generation manifest without clearing runtime evidence', async () => {
    await writeSessionStoreForRun('run-current');
    await setOpenCodeRuntimeActiveRunManifest({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      runId: 'run-current',
      clock: () => now,
    });

    const result = await prepareOpenCodeRuntimeLaneForLaunchGeneration({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      runId: 'run-current',
      reason: 'test_launch',
      clock: () => now,
    });

    await expect(readManifest()).resolves.toMatchObject({
      activeRunId: 'run-current',
      highWatermark: 1,
      entries: [expect.objectContaining({ runId: 'run-current' })],
    });
    await expect(
      fs.readFile(
        getOpenCodeLaneScopedRuntimeFilePath({
          teamsBasePath: tempDir,
          teamName,
          laneId,
          fileName: 'opencode-sessions.json',
        }),
        'utf8'
      )
    ).resolves.toContain('session-run-current');
    expect(result).toMatchObject({ reset: false, reason: 'same_generation_reused' });
  });

  it('resets runtime evidence when activeRunId belongs to an older run', async () => {
    await writeSessionStoreForRun('run-old');
    await setOpenCodeRuntimeActiveRunManifest({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      runId: 'run-old',
      clock: () => now,
    });

    const result = await prepareOpenCodeRuntimeLaneForLaunchGeneration({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      runId: 'run-new',
      reason: 'test_launch',
      clock: () => now,
    });

    await expect(readManifest()).resolves.toMatchObject({
      activeRunId: 'run-new',
      highWatermark: 0,
      entries: [],
    });
    await expect(
      fs.readFile(
        getOpenCodeLaneScopedRuntimeFilePath({
          teamsBasePath: tempDir,
          teamName,
          laneId,
          fileName: 'opencode-sessions.json',
        }),
        'utf8'
      )
    ).rejects.toThrow();
    expect(result).toMatchObject({ reset: true, reason: 'active_run_mismatch' });
  });

  it('resets when manifest entries belong to an older run even if activeRunId was advanced', async () => {
    await writeSessionStoreForRun('run-old');
    await setOpenCodeRuntimeActiveRunManifest({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      runId: 'run-new',
      clock: () => now,
    });

    const result = await prepareOpenCodeRuntimeLaneForLaunchGeneration({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      runId: 'run-new',
      reason: 'test_launch',
      clock: () => now,
    });

    await expect(readManifest()).resolves.toMatchObject({
      activeRunId: 'run-new',
      highWatermark: 0,
      entries: [],
    });
    expect(result).toMatchObject({ reset: true, reason: 'stale_manifest_entries' });
  });

  it('resets entries without a run id because they cannot prove the current generation', async () => {
    const manifestPath = getOpenCodeRuntimeManifestPath(tempDir, teamName, laneId);
    await fs.mkdir(path.dirname(manifestPath), { recursive: true });
    await fs.writeFile(
      manifestPath,
      `${JSON.stringify(
        {
          schemaVersion: 1,
          updatedAt: now.toISOString(),
          data: {
            schemaVersion: 1,
            teamName,
            activeRunId: 'run-new',
            activeCapabilitySnapshotId: null,
            activeBehaviorFingerprint: null,
            highWatermark: 1,
            lastCommittedBatchId: null,
            lastPreparingBatchId: null,
            entries: [
              {
                schemaName: 'opencode.runtimeDiagnostics',
                schemaVersion: 1,
                relativePath: 'opencode-diagnostics.json',
                contentHash: null,
                fileSize: null,
                mtimeMs: null,
                runId: null,
                capabilitySnapshotId: null,
                behaviorFingerprint: null,
                lastWriteReceiptId: null,
                state: 'healthy',
              },
            ],
            lastRecoveryPlanId: null,
            updatedAt: now.toISOString(),
          },
        },
        null,
        2
      )}\n`,
      'utf8'
    );

    const result = await prepareOpenCodeRuntimeLaneForLaunchGeneration({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      runId: 'run-new',
      reason: 'test_launch',
      clock: () => now,
    });

    await expect(readManifest()).resolves.toMatchObject({
      activeRunId: 'run-new',
      highWatermark: 0,
      entries: [],
    });
    expect(result).toMatchObject({ reset: true, reason: 'stale_manifest_entries' });
  });

  it('resets unreadable manifests safely', async () => {
    const manifestPath = getOpenCodeRuntimeManifestPath(tempDir, teamName, laneId);
    await fs.mkdir(path.dirname(manifestPath), { recursive: true });
    await fs.writeFile(manifestPath, '{not-json', 'utf8');

    const result = await prepareOpenCodeRuntimeLaneForLaunchGeneration({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      runId: 'run-new',
      reason: 'test_launch',
      clock: () => now,
    });

    await expect(readManifest()).resolves.toMatchObject({
      activeRunId: 'run-new',
      highWatermark: 0,
      entries: [],
    });
    expect(result).toMatchObject({ reset: true, reason: 'manifest_unreadable' });
  });

  it('resets degraded or stopped lane index state before launch', async () => {
    await writeSessionStoreForRun('run-current');
    await setOpenCodeRuntimeActiveRunManifest({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      runId: 'run-current',
      clock: () => now,
    });
    await upsertOpenCodeRuntimeLaneIndexEntry({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      state: 'degraded',
      diagnostics: ['previous launch failed'],
    });

    const result = await prepareOpenCodeRuntimeLaneForLaunchGeneration({
      teamsBasePath: tempDir,
      teamName,
      laneId,
      runId: 'run-current',
      reason: 'test_launch',
      clock: () => now,
    });

    await expect(readManifest()).resolves.toMatchObject({
      activeRunId: 'run-current',
      highWatermark: 0,
      entries: [],
    });
    await expect(readOpenCodeRuntimeLaneIndex(tempDir, teamName)).resolves.toMatchObject({
      lanes: {
        [laneId]: {
          laneId,
          state: 'active',
        },
      },
    });
    expect(result).toMatchObject({ reset: true, reason: 'lane_index_terminal' });
  });
});
