/**
 * mcp-server tests — verifies pure formatting/state helper behavior.
 */

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
  readSessionState,
  writeSessionState,
  deleteSessionState,
  resolveStateDir,
  stateFilePath,
} from './mcp-state-reader.js';
import {
  stateOrEmpty,
  buildGateDiagnostic,
  formatGates,
  buildStatusSummary,
  parseStateDirArg,
} from './mcp-server.js';
import { createSessionState } from '../domain/session-state.js';
import { createFlowSpec } from '../domain/flow-spec.js';
import { createRunNode } from '../domain/flow-node.js';

let tempDir: string;

beforeEach(async () => {
  tempDir = await mkdtemp(join(tmpdir(), 'mcp-test-'));
});

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

describe('readSessionState', () => {
  it('returns null when state file does not exist', async () => {
    const result = await readSessionState(tempDir);
    expect(result).toBeNull();
  });

  it('returns null for a non-existent directory', async () => {
    const result = await readSessionState(join(tempDir, 'missing-dir'));
    expect(result).toBeNull();
  });
});

describe('writeSessionState and readSessionState', () => {
  it('round-trips a session state', async () => {
    const spec = createFlowSpec('Test goal', []);
    const state = createSessionState('session-1', spec);
    await writeSessionState(tempDir, state);
    const loaded = await readSessionState(tempDir);
    expect(loaded).not.toBeNull();
    expect(loaded?.sessionId).toBe('session-1');
    expect(loaded?.flowSpec.goal).toBe('Test goal');
  });

  it('preserves variables through round-trip', async () => {
    const spec = createFlowSpec('Test', []);
    const state = createSessionState('s2', spec);
    const withVar = { ...state, variables: { ...state.variables, myKey: 'myValue' } };
    await writeSessionState(tempDir, withVar);
    const loaded = await readSessionState(tempDir);
    expect(loaded?.variables['myKey']).toBe('myValue');
  });
});

describe('deleteSessionState', () => {
  it('deletes an existing state file', async () => {
    const spec = createFlowSpec('Delete test', []);
    const state = createSessionState('s3', spec);
    await writeSessionState(tempDir, state);
    await deleteSessionState(tempDir);
    const loaded = await readSessionState(tempDir);
    expect(loaded).toBeNull();
  });

  it('does not throw if state file does not exist', async () => {
    await expect(deleteSessionState(tempDir)).resolves.not.toThrow();
  });
});

describe('startMcpServer import', () => {
  it('can import startMcpServer without errors', async () => {
    const mod = await import('./mcp-server.js');
    expect(typeof mod.startMcpServer).toBe('function');
  });
});

// ── resolveStateDir ──────────────────────────────────────────────────

describe('resolveStateDir', () => {
  it('prefers envVar when set', () => {
    expect(resolveStateDir('/env/dir', '/arg/dir', '/cwd')).toBe('/env/dir');
  });

  it('falls back to argVar when envVar is undefined', () => {
    expect(resolveStateDir(undefined, '/arg/dir', '/cwd')).toBe('/arg/dir');
  });

  it('falls back to cwd/.prompt-language when both are undefined', () => {
    const result = resolveStateDir(undefined, undefined, '/my/project');
    expect(result).toContain('.prompt-language');
    expect(result).toContain('my');
  });
});

// ── stateFilePath ────────────────────────────────────────────────────

describe('stateFilePath', () => {
  it('returns path ending in session-state.json', () => {
    const result = stateFilePath('/state/dir');
    expect(result.endsWith('session-state.json')).toBe(true);
  });
});

// ── readSessionState — error and edge cases ──────────────────────────

describe('readSessionState — invalid content', () => {
  it('returns null when state file has invalid JSON', async () => {
    await writeFile(join(tempDir, 'session-state.json'), 'NOT JSON', 'utf-8');
    const result = await readSessionState(tempDir);
    expect(result).toBeNull();
  });

  it('returns null when state file has valid JSON but invalid structure (no checksum)', async () => {
    await writeFile(
      join(tempDir, 'session-state.json'),
      JSON.stringify({ not: 'a session state' }),
      'utf-8',
    );
    const result = await readSessionState(tempDir);
    expect(result).toBeNull();
  });

  it('returns sanitized state when checksum mismatches (clears gateResults)', async () => {
    const spec = createFlowSpec('Test', []);
    const state = createSessionState('s1', spec);
    // Write with correct checksum
    await writeSessionState(tempDir, state);

    // Read back the raw file and tamper with it to cause checksum mismatch
    const { readFile: readFileFn } = await import('node:fs/promises');
    const raw = await readFileFn(join(tempDir, 'session-state.json'), 'utf-8');
    const parsed = JSON.parse(raw) as Record<string, unknown>;
    // Tamper with a field to invalidate the checksum
    parsed['status'] = 'tampered_value_not_matching_checksum';
    await writeFile(join(tempDir, 'session-state.json'), JSON.stringify(parsed), 'utf-8');

    // Should return sanitized state (gateResults/gateDiagnostics cleared)
    const result = await readSessionState(tempDir);
    // With tampered structure still valid (sessionId etc. still present), returns sanitized
    if (result !== null) {
      expect(result.gateResults).toEqual({});
    } else {
      // Also acceptable — structure may fail validation after tampering
      expect(result).toBeNull();
    }
  });
});

// ── stateOrEmpty ─────────────────────────────────────────────────────

describe('stateOrEmpty', () => {
  it('returns "No active session" when state is null', () => {
    expect(stateOrEmpty(null)).toBe('No active session');
  });

  it('returns JSON of state when state is present', () => {
    const spec = createFlowSpec('Test goal', []);
    const state = createSessionState('s1', spec);
    const result = stateOrEmpty(state);
    expect(result).toContain('Test goal');
    expect(result).toContain('s1');
    const parsed = JSON.parse(result) as Record<string, unknown>;
    expect(parsed['sessionId']).toBe('s1');
  });
});

// ── buildGateDiagnostic ──────────────────────────────────────────────

describe('buildGateDiagnostic', () => {
  it('uses provided values when all are defined', () => {
    const result = buildGateDiagnostic('npm test', 1, 'test failed');
    expect(result.command).toBe('npm test');
    expect(result.exitCode).toBe(1);
    expect(result.stderr).toBe('test failed');
  });

  it('defaults missing values to empty string and -1', () => {
    const result = buildGateDiagnostic(undefined, undefined, undefined);
    expect(result.command).toBe('');
    expect(result.exitCode).toBe(-1);
    expect(result.stderr).toBe('');
  });

  it('handles partial undefined values', () => {
    const result = buildGateDiagnostic('cmd', undefined, 'err');
    expect(result.command).toBe('cmd');
    expect(result.exitCode).toBe(-1);
    expect(result.stderr).toBe('err');
  });
});

// ── formatGates ──────────────────────────────────────────────────────

describe('formatGates', () => {
  it('returns "No completion gates defined" when spec has no gates', () => {
    const spec = createFlowSpec('Test', []);
    const state = createSessionState('s1', spec);
    expect(formatGates(state)).toBe('No completion gates defined');
  });

  it('returns pending status when gate has not been evaluated', () => {
    const spec = createFlowSpec('Test', [], [{ predicate: 'tests_pass' }]);
    const state = createSessionState('s1', spec);
    const result = JSON.parse(formatGates(state)) as { predicate: string; status: string }[];
    expect(result[0]?.predicate).toBe('tests_pass');
    expect(result[0]?.status).toBe('pending');
  });

  it('returns pass status when gate result is true', () => {
    const spec = createFlowSpec('Test', [], [{ predicate: 'tests_pass' }]);
    const state = { ...createSessionState('s1', spec), gateResults: { tests_pass: true } };
    const result = JSON.parse(formatGates(state)) as { predicate: string; status: string }[];
    expect(result[0]?.status).toBe('pass');
  });

  it('returns fail status when gate result is false', () => {
    const spec = createFlowSpec('Test', [], [{ predicate: 'tests_pass' }]);
    const state = { ...createSessionState('s1', spec), gateResults: { tests_pass: false } };
    const result = JSON.parse(formatGates(state)) as { predicate: string; status: string }[];
    expect(result[0]?.status).toBe('fail');
  });

  it('includes diagnostic when gate fails with diagnostic data', () => {
    const spec = createFlowSpec('Test', [], [{ predicate: 'tests_pass' }]);
    const baseState = createSessionState('s1', spec);
    const state = {
      ...baseState,
      gateResults: { tests_pass: false },
      gateDiagnostics: {
        tests_pass: { passed: false, command: 'npm test', exitCode: 1, stdout: '', stderr: 'FAIL' },
      },
    };
    const result = JSON.parse(formatGates(state)) as {
      predicate: string;
      status: string;
      diagnostic?: { command: string; exitCode: number; stderr: string };
    }[];
    expect(result[0]?.status).toBe('fail');
    expect(result[0]?.diagnostic?.command).toBe('npm test');
    expect(result[0]?.diagnostic?.exitCode).toBe(1);
    expect(result[0]?.diagnostic?.stderr).toBe('FAIL');
  });

  it('handles multiple gates with mixed statuses', () => {
    const spec = createFlowSpec(
      'Test',
      [],
      [{ predicate: 'tests_pass' }, { predicate: 'lint_pass' }, { predicate: 'build_pass' }],
    );
    const state = {
      ...createSessionState('s1', spec),
      gateResults: { tests_pass: true, lint_pass: false } as Record<string, boolean>,
    };
    const result = JSON.parse(formatGates(state)) as { predicate: string; status: string }[];
    expect(result).toHaveLength(3);
    expect(result.find((r) => r.predicate === 'tests_pass')?.status).toBe('pass');
    expect(result.find((r) => r.predicate === 'lint_pass')?.status).toBe('fail');
    expect(result.find((r) => r.predicate === 'build_pass')?.status).toBe('pending');
  });
});

// ── buildStatusSummary ───────────────────────────────────────────────

describe('buildStatusSummary', () => {
  it('returns correct goal and status', () => {
    const spec = createFlowSpec('My goal', []);
    const state = createSessionState('s1', spec);
    const summary = buildStatusSummary(state);
    expect(summary.goal).toBe('My goal');
    expect(summary.status).toBe('active');
    expect(summary.completed).toBe(false);
  });

  it('returns completed=true when status is completed', () => {
    const spec = createFlowSpec('Test', []);
    const state = { ...createSessionState('s1', spec), status: 'completed' as const };
    const summary = buildStatusSummary(state);
    expect(summary.completed).toBe(true);
  });

  it('counts passing gates correctly', () => {
    const spec = createFlowSpec('Test', [], [{ predicate: 'tests_pass' }, { predicate: 'lint' }]);
    const state = {
      ...createSessionState('s1', spec),
      gateResults: { tests_pass: true, lint: false } as Record<string, boolean>,
    };
    const summary = buildStatusSummary(state);
    expect(summary.gateCount).toBe(2);
    expect(summary.gatesPassing).toBe(1);
  });

  it('computes iterationCount from nodeProgress', () => {
    const spec = createFlowSpec('Test', []);
    const state = {
      ...createSessionState('s1', spec),
      nodeProgress: {
        n1: { iteration: 3, maxIterations: 0, status: 'running' as const, startedAt: 0 },
        n2: { iteration: 2, maxIterations: 0, status: 'running' as const, startedAt: 0 },
      },
    };
    const summary = buildStatusSummary(state);
    expect(summary.iterationCount).toBe(5);
  });

  it('returns currentNodePath from state', () => {
    const spec = createFlowSpec('Test', []);
    const state = { ...createSessionState('s1', spec), currentNodePath: [1, 2] };
    const summary = buildStatusSummary(state);
    expect(summary.currentNodePath).toEqual([1, 2]);
  });

  it('includes timingReport after the flow completes', () => {
    const spec = createFlowSpec('Test', [createRunNode('r1', 'npm test')]);
    const state = {
      ...createSessionState('s1', spec),
      status: 'completed' as const,
      nodeProgress: {
        r1: {
          iteration: 1,
          maxIterations: 1,
          status: 'completed' as const,
          startedAt: 0,
          completedAt: 35_000,
        },
      },
    };
    const summary = buildStatusSummary(state);
    expect(summary.timingReport).toContain('| 35.0s | run: npm test | 0 |');
  });
});

// ── parseStateDirArg ─────────────────────────────────────────────────

describe('parseStateDirArg', () => {
  it('returns undefined when --state-dir is not in process.argv', () => {
    // In test environment process.argv does not contain --state-dir
    const result = parseStateDirArg();
    expect(result === undefined || typeof result === 'string').toBe(true);
  });
});
