/**
 * Install-time database schema verification (#1392).
 *
 * Fresh standalone (postgres) installs once ended "healthy" with a completely
 * empty database: the resolved dependency set shipped no migrations at all
 * (#1389), the gateway started fine, and nothing failed until the first real
 * query. The issue's own conclusion: the installer must "instruct and verify".
 *
 * This check runs AFTER migrations have been (re-)applied, so the only way it
 * fails is a genuinely broken migration set or database — which is exactly
 * when install must not report success. Failure is fatal by design (fast-fail
 * STANDARDS); callers print the remediation text and exit non-zero.
 */

import { existsSync } from 'node:fs';
import { homedir } from 'node:os';
import { join, resolve } from 'node:path';
import { loadConfig } from '@mosaicstack/config';

export interface SchemaStatusCounts {
  appliedCount: number;
  expectedCount: number;
  expectedLastTag: string;
  complete: boolean;
}

/** Injectable migration surface — keeps this unit-testable without a DB. */
export interface SchemaCheckDeps {
  runMigrations(url: string): Promise<void>;
  getMigrationStatus(url: string): Promise<SchemaStatusCounts>;
}

export type SchemaCheckResult =
  | { status: 'ok'; detail: string }
  | { status: 'skipped'; detail: string }
  | { status: 'fail'; detail: string; remediation: string };

export const SCHEMA_FAIL_REMEDIATION = [
  'The gateway database does not carry the full schema.',
  'Causes seen in the wild: dependency set resolved without migrations (#1389), or a partially-migrated database (#1402).',
  'Remediation:',
  '  1. Re-run: mosaic gateway install  (applies migrations and verifies again)',
  '  2. Check the resolved @mosaicstack/db version is the same pipeline as the gateway (npm ls -g @mosaicstack/db)',
  '  3. Manual apply: run runMigrations() from @mosaicstack/db against the storage URL, then re-verify',
].join('\n');

/**
 * Resolve the config the INSTALLED gateway would use — same priority the
 * daemon applies (apps/gateway/src/env.ts resolveGatewayConfigPath), minus
 * the source-tree anchors that do not exist on an installed host. Verifying
 * against any other config could green-light a database the daemon never
 * reads (#1392: verify what runs, not what happens to lie in cwd).
 */
export function resolveSchemaCheckConfigPath(explicit?: string): string | undefined {
  if (explicit) return resolve(explicit);
  // NOTE: no env-var candidate, deliberately. apps/gateway/src/env.ts gives env
  // NO config authority (a stale MOSAIC_CONFIG could verify a database the
  // daemon never reads — rev-code-02 review 285, note N1). Resolution order
  // mirrors the daemon's file priorities only.
  const candidates = [
    join(homedir(), '.config', 'mosaic', 'gateway', 'mosaic.config.json'), // daemon-written
    resolve(process.cwd(), 'mosaic.config.json'),
    join(homedir(), '.mosaic', 'mosaic.config.json'),
  ];
  for (const c of candidates) {
    if (c && existsSync(c)) return c;
  }
  return undefined; // loadConfig falls back to env-var detection
}

export async function checkDatabaseSchema(
  deps: SchemaCheckDeps,
  configPath?: string,
): Promise<SchemaCheckResult> {
  const config = loadConfig(resolveSchemaCheckConfigPath(configPath));

  // Local tier: the gateway itself runs PGlite migrations at startup (see
  // DatabaseModule.onModuleInit), and a broken local tier fails the health
  // check instead. Nothing for the installer to verify here.
  if (config.storage.type !== 'postgres') {
    return {
      status: 'skipped',
      detail: 'database schema (local tier — migrated by gateway at startup)',
    };
  }

  const url = config.storage.url;
  try {
    await deps.runMigrations(url);
    const status = await deps.getMigrationStatus(url);
    if (status.complete) {
      return {
        status: 'ok',
        detail: `database schema (${status.appliedCount.toString()}/${status.expectedCount.toString()} migrations)`,
      };
    }
    return {
      status: 'fail',
      detail: `database schema incomplete (${status.appliedCount.toString()}/${status.expectedCount.toString()} applied, last expected: ${status.expectedLastTag})`,
      remediation: SCHEMA_FAIL_REMEDIATION,
    };
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    return {
      status: 'fail',
      detail: `database schema check errored: ${msg}`,
      remediation: SCHEMA_FAIL_REMEDIATION,
    };
  }
}
