#!/usr/bin/env node

import { mkdir as fsMkdir, readFile as fsReadFile, writeFile as fsWriteFile } from 'node:fs/promises';
import { execFile as childExecFile } from 'node:child_process';
import { dirname, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { promisify } from 'node:util';
import { runWorkspaceKtx } from './run-ktx.mjs';

const scriptDir = dirname(fileURLToPath(import.meta.url));
const ktxRootDir = resolve(scriptDir, '..');
const defaultProjectDir = resolve(ktxRootDir, 'examples/orbit-relationship-verification');
const defaultReportPath = resolve(
  ktxRootDir,
  'examples/orbit-relationship-verification/reports/orbit-verification.md',
);
const defaultExecFile = promisify(childExecFile);

class BufferWriter {
  chunks = [];

  write(chunk) {
    this.chunks.push(String(chunk));
  }

  text() {
    return this.chunks.join('');
  }
}

function dateOnly(date) {
  return date.toISOString().slice(0, 10);
}

function trimForReport(value) {
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : 'none';
}

export function defaultOrbitVerificationProjectDir() {
  return defaultProjectDir;
}

function internalScanCommand(input) {
  return `internal runKtxScan connection=${input.connectionId} mode=relationships projectDir=${input.projectDir}`;
}

function firstNonEmptyLine(...values) {
  for (const value of values) {
    const line = value
      .split('\n')
      .map((candidate) => candidate.trim())
      .find((candidate) => candidate.length > 0);
    if (line) {
      return line;
    }
  }
  return 'Orbit relationship scan failed before producing diagnostic output';
}

function parseArgs(argv) {
  const options = {
    connectionId: process.env.KTX_ORBIT_CONNECTION_ID ?? 'orbit',
    projectDir: process.env.KTX_PROJECT_DIR ?? defaultProjectDir,
    reportPath: defaultReportPath,
  };

  for (let index = 0; index < argv.length; index += 1) {
    const arg = argv[index];
    if (arg === '--connection-id' || arg === '--connection') {
      options.connectionId = argv[index + 1];
      index += 1;
      continue;
    }
    if (arg === '--project-dir') {
      options.projectDir = argv[index + 1];
      index += 1;
      continue;
    }
    if (arg === '--report-path') {
      options.reportPath = argv[index + 1];
      index += 1;
      continue;
    }
    throw new Error(`Unknown option: ${arg}`);
  }

  return options;
}

export function buildOrbitScanArgs(input) {
  return {
    command: 'run',
    projectDir: input.projectDir,
    connectionId: input.connectionId,
    mode: 'relationships',
    detectRelationships: true,
    dryRun: false,
  };
}

export function extractRunId(stdout) {
  const match = stdout.match(/^Run:\s*(\S+)/m);
  return match?.[1] ?? null;
}

export function extractReportPath(stdout) {
  const match = stdout.match(/^\s*Report:\s*(\S+)/m);
  return match?.[1] ?? null;
}

function listLines(values) {
  if (!values || values.length === 0) {
    return ['- none'];
  }
  return values.map((value) => `- \`${value}\``);
}

function warningLines(report) {
  if (!Array.isArray(report.warnings) || report.warnings.length === 0) {
    return ['- none'];
  }
  return report.warnings.map((warning) => `- \`${warning.code}\`: ${warning.message}`);
}

function formatSuccess(result) {
  const relationships = result.report.relationships ?? { accepted: 0, review: 0, rejected: 0, skipped: 0 };
  const enrichment = result.report.enrichment ?? {};
  const artifactPaths = result.report.artifactPaths ?? {};

  return [
    '## Outcome',
    '',
    '- Exit code: 0',
    `- Run: \`${result.report.runId ?? 'unknown'}\``,
    `- Connection: \`${result.report.connectionId ?? result.connectionId}\``,
    `- Mode: \`${result.report.mode ?? 'unknown'}\``,
    `- Sync: \`${result.report.syncId ?? 'unknown'}\``,
    '',
    '## Relationship Summary',
    '',
    `- Accepted: ${relationships.accepted ?? 0}`,
    `- Review: ${relationships.review ?? 0}`,
    `- Rejected: ${relationships.rejected ?? 0}`,
    `- Skipped: ${relationships.skipped ?? 0}`,
    '',
    '## Enrichment Summary',
    '',
    `- Deterministic relationships: \`${enrichment.deterministicRelationships ?? 'unknown'}\``,
    `- Statistical validation: \`${enrichment.statisticalValidation ?? 'unknown'}\``,
    `- LLM relationship validation: \`${enrichment.llmRelationshipValidation ?? 'unknown'}\``,
    '',
    '## Artifacts',
    '',
    `- Report: \`${artifactPaths.reportPath ?? 'none'}\``,
    `- Raw sources: \`${artifactPaths.rawSourcesDir ?? 'none'}\``,
    '',
    'Manifest shards:',
    '',
    ...listLines(artifactPaths.manifestShards),
    '',
    'Enrichment artifacts:',
    '',
    ...listLines(artifactPaths.enrichmentArtifacts),
    '',
    'Warnings:',
    '',
    ...warningLines(result.report),
  ];
}

function formatBlocked(result) {
  return [
    '## Outcome',
    '',
    `- Exit code: ${result.scanExitCode}`,
    `- Blocker: \`${result.blocker}\``,
    '',
    '## Evidence',
    '',
    '- Orbit verification was not executed because the current local Orbit relationship scan failed.',
    '- Re-run with `--report-path` to write verification evidence to a custom location.',
    '',
    'Scan stdout:',
    '',
    '```text',
    trimForReport(result.scanStdout),
    '```',
    '',
    'Scan stderr:',
    '',
    '```text',
    trimForReport(result.scanStderr),
    '```',
  ];
}

export function formatOrbitVerificationMarkdown(result) {
  const lines = [
    '# KTX Relationship Discovery Orbit Verification',
    '',
    `Date: ${result.date}`,
    '',
    '## Command',
    '',
    '```bash',
    result.scanCommand,
    '```',
    '',
  ];

  if (result.status === 'success') {
    lines.push(
      '## Scan Report Artifact',
      '',
      `- ${result.reportPath}`,
      '',
      ...formatSuccess(result),
    );
  } else {
    lines.push(...formatBlocked(result));
  }

  return `${lines.join('\n')}\n`;
}

async function runBufferedWorkspaceKtx(runner, argv, rootDir, execFile) {
  const stdout = new BufferWriter();
  const stderr = new BufferWriter();
  const exitCode = await runner(argv, { rootDir, execFile, stdout, stderr });
  return {
    exitCode,
    stdout: stdout.text(),
    stderr: stderr.text(),
  };
}

function cliScanModulePath(rootDir) {
  return resolve(rootDir, 'packages/cli/dist/scan.js');
}

async function loadRunKtxScan(rootDir) {
  const module = await import(pathToFileURL(cliScanModulePath(rootDir)).href);
  return module.runKtxScan;
}

async function runBufferedInternalScan(input) {
  const stdout = new BufferWriter();
  const stderr = new BufferWriter();
  let runKtxScan = input.runKtxScan;

  if (!runKtxScan) {
    const build = await runBufferedWorkspaceKtx(input.runner, ['--version'], input.rootDir, input.execFile);
    if (build.exitCode !== 0) {
      return build;
    }
    runKtxScan = await loadRunKtxScan(input.rootDir);
  }

  const exitCode = await runKtxScan(input.scanArgs, { stdout, stderr });
  return {
    exitCode,
    stdout: stdout.text(),
    stderr: stderr.text(),
  };
}

function orbitVerificationEnv(projectDir) {
  if (projectDir !== defaultProjectDir) {
    return process.env;
  }
  return {
    ...process.env,
    GIT_CEILING_DIRECTORIES: dirname(defaultProjectDir),
  };
}

export async function runOrbitVerification(options = {}) {
  const connectionId = options.connectionId ?? process.env.KTX_ORBIT_CONNECTION_ID ?? 'orbit';
  const projectDir = options.projectDir ?? process.env.KTX_PROJECT_DIR ?? defaultProjectDir;
  const reportPath = options.reportPath ?? defaultReportPath;
  const rootDir = options.rootDir ?? ktxRootDir;
  const runner = options.runWorkspaceKtx ?? runWorkspaceKtx;
  const execFile = options.execFile ?? defaultExecFile;
  const now = options.now ?? (() => new Date());
  const mkdir = options.mkdir ?? fsMkdir;
  const writeFile = options.writeFile ?? fsWriteFile;
  const readFile = options.readFile ?? fsReadFile;
  const date = dateOnly(now());
  const env = options.env ?? orbitVerificationEnv(projectDir);
  const runWithEnv = (argv, runnerOptions) => runner(argv, { ...runnerOptions, env });

  const scanArgs = buildOrbitScanArgs({ connectionId, projectDir });
  const scanCommand = internalScanCommand({ connectionId, projectDir });
  const scan = await runBufferedInternalScan({
    scanArgs,
    rootDir,
    execFile,
    runner: runWithEnv,
    runKtxScan: options.runKtxScan,
  });
  let result;

  if (scan.exitCode !== 0) {
    result = {
      status: 'blocked',
      date,
      connectionId,
      projectDir,
      scanCommand,
      scanExitCode: scan.exitCode,
      blocker: firstNonEmptyLine(scan.stderr, scan.stdout),
      scanStdout: scan.stdout,
      scanStderr: scan.stderr,
    };
  } else {
    const runId = extractRunId(scan.stdout);
    if (!runId) {
      result = {
        status: 'blocked',
        date,
        connectionId,
        projectDir,
        scanCommand,
        scanExitCode: scan.exitCode,
        blocker: 'KTX scan completed without printing a Run id',
        scanStdout: scan.stdout,
        scanStderr: scan.stderr,
      };
    } else {
      const scanReportPath = extractReportPath(scan.stdout);
      if (!scanReportPath) {
        result = {
          status: 'blocked',
          date,
          connectionId,
          projectDir,
          scanCommand,
          scanExitCode: scan.exitCode,
          blocker: 'KTX scan completed without printing a report artifact path',
          scanStdout: scan.stdout,
          scanStderr: scan.stderr,
        };
      } else {
        const fullScanReportPath = resolve(projectDir, scanReportPath);
        result = {
          status: 'success',
          date,
          connectionId,
          projectDir,
          scanCommand,
          reportPath: fullScanReportPath,
          scanExitCode: scan.exitCode,
          scanStdout: scan.stdout,
          scanStderr: scan.stderr,
          report: JSON.parse(await readFile(fullScanReportPath, 'utf8')),
        };
      }
    }
  }

  await mkdir(dirname(reportPath), { recursive: true });
  await writeFile(reportPath, formatOrbitVerificationMarkdown(result));
  return result;
}

if (import.meta.url === pathToFileURL(process.argv[1]).href) {
  const options = parseArgs(process.argv.slice(2));
  const result = await runOrbitVerification(options);
  process.stdout.write(`Wrote ${options.reportPath}\n`);
  process.stdout.write(`Outcome: ${result.status}\n`);
}
