import { createHash } from 'node:crypto';

const ACTIVE = new Set(['queued', 'pending', 'in_progress', 'waiting', 'requested']);
const SUCCESS = new Set(['success', 'neutral', 'skipped']);
const FAILURE = new Set(['failure', 'timed_out', 'action_required', 'startup_failure', 'stale']);
const CANCELLED = new Set(['cancelled']);

export const UPDATER_VERSION = '3.4.0';
export const SESSION_SCHEMA = 'vendoo-updater-session-v2';
export const PACKAGE_MANIFEST_SCHEMA = 'vendoo-release-package-v2';
export const REQUIRED_CHECKS = Object.freeze([
  'Syntax, Sicherheit und Release-Gates',
  'Windows – vollständiger Releasevertrag',
  'Updater-3.4-Paket nach Abnahme',
]);
export const REQUIRED_WORKFLOWS = Object.freeze([
  'Vendoo CI',
  'Vendoo Release Manifest Sync',
]);

const lower = value => String(value || '').trim().toLowerCase();
const time = value => {
  const parsed = Date.parse(value || '');
  return Number.isFinite(parsed) ? parsed : 0;
};
const stablePayload = manifest => ({
  schema: PACKAGE_MANIFEST_SCHEMA,
  targetVersion: String(manifest.targetVersion || manifest.version || ''),
  updaterVersion: String(manifest.updaterVersion || ''),
  sourceCommit: String(manifest.sourceCommit || ''),
  baseCommit: String(manifest.baseCommit || ''),
  createdAt: String(manifest.createdAt || ''),
  mode: String(manifest.mode || ''),
  requiredFiles: [...(manifest.requiredFiles || [])],
  removedFiles: [...(manifest.removedFiles || [])],
  files: [...(manifest.files || [])],
});

export function computePackagePayloadHash(manifest) {
  return createHash('sha256').update(JSON.stringify(stablePayload(manifest))).digest('hex');
}

export function createPackageManifest(sourceManifest, { sourceCommit, baseCommit, createdAt }) {
  const manifest = {
    ...sourceManifest,
    schema: PACKAGE_MANIFEST_SCHEMA,
    targetVersion: String(sourceManifest.version || sourceManifest.targetVersion || ''),
    sourceCommit: String(sourceCommit || ''),
    baseCommit: String(baseCommit || ''),
    createdAt: String(createdAt || ''),
  };
  manifest.payloadHash = computePackagePayloadHash(manifest);
  return manifest;
}

export function payloadIdentity(manifest) {
  const targetVersion = String(manifest?.targetVersion || manifest?.version || '');
  const payloadHash = String(manifest?.payloadHash || '');
  return `${targetVersion}:${payloadHash}`;
}

export function createSession({ repository, targetVersion, updaterVersion = UPDATER_VERSION, payloadHash, branch }) {
  return {
    schema: SESSION_SCHEMA,
    repository,
    targetVersion,
    updaterVersion,
    payloadHash,
    branch,
    releaseState: 'new',
    prNumber: null,
    prUrl: '',
    finalPrHeadSha: '',
    mergeCommit: '',
    productionVersion: '',
    completedAt: '',
    updatedAt: new Date().toISOString(),
  };
}

export function sessionMatches(session, { repository, targetVersion, payloadHash }) {
  return Boolean(session && session.schema === SESSION_SCHEMA && session.repository === repository && session.targetVersion === targetVersion && session.payloadHash === payloadHash);
}

export function decideReleasePath({ targetVersion, mainVersion, productionReady, productionVersion, pullRequests = [], session = null }) {
  const productionAtTarget = Boolean(productionReady && productionVersion === targetVersion);
  if (session?.releaseState === 'completed' && productionAtTarget) return { action: 'complete', reason: 'completed-session' };
  if (mainVersion === targetVersion) {
    return productionAtTarget ? { action: 'complete', reason: 'main-and-production-at-target' } : { action: 'deploy', reason: 'main-at-target-production-behind' };
  }
  const relevant = pullRequests.filter(pr => pr && pr.targetVersion === targetVersion);
  const open = relevant.filter(pr => lower(pr.state) === 'open').sort((a, b) => time(b.updatedAt) - time(a.updatedAt))[0];
  if (open) return { action: 'resume-pr', reason: 'matching-open-pr', pullRequest: open };
  const merged = relevant.filter(pr => Boolean(pr.mergedAt || pr.merged)).sort((a, b) => time(b.mergedAt || b.updatedAt) - time(a.mergedAt || a.updatedAt))[0];
  if (merged) return { action: 'deploy', reason: 'matching-pr-already-merged', pullRequest: merged };
  return { action: 'source', reason: 'source-update-required' };
}

function normalizeCheck(check) {
  const status = lower(check?.status);
  const conclusion = lower(check?.conclusion);
  const updatedAt = check?.completedAt || check?.startedAt || check?.updatedAt || '';
  let state = 'missing';
  if (ACTIVE.has(status) || (!conclusion && status && status !== 'completed')) state = 'pending';
  else if (SUCCESS.has(conclusion)) state = 'success';
  else if (FAILURE.has(conclusion)) state = 'failure';
  else if (CANCELLED.has(conclusion)) state = 'cancelled';
  else if (status === 'completed' && !conclusion) state = 'pending';
  return { name: String(check?.name || ''), workflowName: String(check?.workflowName || ''), state, status, conclusion, updatedAt, detailsUrl: String(check?.detailsUrl || '') };
}

function bestCheck(checks) {
  return [...checks].sort((a, b) => {
    const dateDelta = time(b.updatedAt) - time(a.updatedAt);
    if (dateDelta) return dateDelta;
    const rank = { success: 4, pending: 3, failure: 2, cancelled: 1, missing: 0 };
    return (rank[b.state] || 0) - (rank[a.state] || 0);
  })[0];
}

function normalizeRun(run) {
  const status = lower(run?.status);
  const conclusion = lower(run?.conclusion);
  let state = 'missing';
  if (ACTIVE.has(status)) state = 'pending';
  else if (SUCCESS.has(conclusion)) state = 'success';
  else if (FAILURE.has(conclusion)) state = 'failure';
  else if (CANCELLED.has(conclusion)) state = 'cancelled';
  return { name: String(run?.name || run?.workflowName || ''), state, status, conclusion, updatedAt: run?.updated_at || run?.updatedAt || run?.run_started_at || '', url: String(run?.html_url || run?.url || '') };
}

function bestRun(runs) {
  return [...runs].sort((a, b) => {
    const dateDelta = time(b.updatedAt) - time(a.updatedAt);
    if (dateDelta) return dateDelta;
    const rank = { success: 4, pending: 3, failure: 2, cancelled: 1, missing: 0 };
    return (rank[b.state] || 0) - (rank[a.state] || 0);
  })[0];
}

export function classifyChecks({ checks = [], workflowRuns = [], requiredChecks = REQUIRED_CHECKS, requiredWorkflows = REQUIRED_WORKFLOWS, elapsedSeconds = 0, gracePeriodSeconds = 90, timedOut = false }) {
  const normalizedChecks = checks.map(normalizeCheck);
  const normalizedRuns = workflowRuns.map(normalizeRun);
  const checkRows = requiredChecks.map(name => {
    const candidates = normalizedChecks.filter(check => check.name === name);
    return candidates.length ? bestCheck(candidates) : { name, state: 'missing', status: '', conclusion: '', updatedAt: '', detailsUrl: '' };
  });
  const workflowRows = requiredWorkflows.map(name => {
    const candidates = normalizedRuns.filter(run => run.name === name);
    return candidates.length ? bestRun(candidates) : { name, state: 'missing', status: '', conclusion: '', updatedAt: '', url: '' };
  });
  const rows = [...checkRows, ...workflowRows];
  const terminalFailure = rows.find(row => row.state === 'failure');
  if (terminalFailure) return { state: 'failed', reason: `${terminalFailure.name}: ${terminalFailure.conclusion || terminalFailure.status}`, rows };
  const terminalCancelled = rows.find(row => row.state === 'cancelled');
  if (terminalCancelled) return { state: 'failed', reason: `${terminalCancelled.name}: cancelled`, rows };
  if (rows.every(row => row.state === 'success')) return { state: 'success', reason: 'all-required-checks-successful', rows };
  const missing = rows.filter(row => row.state === 'missing');
  if (timedOut) return { state: 'timeout', reason: 'required-checks-not-terminal-before-timeout', rows };
  if (missing.length && elapsedSeconds < gracePeriodSeconds) return { state: 'waiting-registration', reason: 'checks-within-registration-grace-period', rows };
  if (missing.length) return { state: 'waiting-missing', reason: 'required-checks-not-yet-registered', rows };
  return { state: 'pending', reason: 'required-checks-still-running', rows };
}

export function formatStatusMatrix(rows = []) {
  return rows.map(row => `${row.name}: ${row.state} (${row.conclusion || row.status || 'not-reported'})`).join('\n');
}

export function evaluatePayloadGuard({ baseCommit, currentMainCommit, baseIsAncestor, conflictingPaths = [] }) {
  if (!baseCommit || !currentMainCommit) return { allowed: false, reason: 'missing-commit-provenance', conflicts: [] };
  if (!baseIsAncestor) return { allowed: false, reason: 'base-is-not-ancestor-of-main', conflicts: [] };
  if (conflictingPaths.length) return { allowed: false, reason: 'payload-would-overwrite-newer-main', conflicts: [...conflictingPaths].sort() };
  return { allowed: true, reason: baseCommit === currentMainCommit ? 'exact-base' : 'non-conflicting-main-advance', conflicts: [] };
}

function parseStdin() {
  return new Promise((resolve, reject) => {
    let input = '';
    process.stdin.setEncoding('utf8');
    process.stdin.on('data', chunk => { input += chunk; });
    process.stdin.on('end', () => { try { resolve(input.trim() ? JSON.parse(input) : {}); } catch (error) { reject(error); } });
    process.stdin.on('error', reject);
  });
}

if (process.argv[1] && import.meta.url === new URL(`file://${process.argv[1].replaceAll('\\', '/')}`).href) {
  const command = process.argv[2];
  try {
    const input = await parseStdin();
    let output;
    if (command === 'decide') output = decideReleasePath(input);
    else if (command === 'classify-checks') output = classifyChecks(input);
    else if (command === 'payload-guard') output = evaluatePayloadGuard(input);
    else throw new Error(`Unbekannter Lifecycle-Befehl: ${command}`);
    process.stdout.write(`${JSON.stringify(output)}\n`);
  } catch (error) {
    process.stderr.write(`[FEHLER] ${error.message}\n`);
    process.exit(1);
  }
}
