import 'reflect-metadata';
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
import * as nodeOs from 'node:os';
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
import * as nodeUrl from 'node:url';
import type { MosaicConfig } from '@mosaicstack/config';
import type * as MosaicStorage from '@mosaicstack/storage';
import { describe, expect, it, vi, type MockInstance } from 'vitest';

// Each case uses vi.resetModules() and re-imports the full gateway graph for distinct ambient FS/env; CI needs headroom, while this still guards genuine hangs.
const MODULE_IMPORT_TIMEOUT_MS = 120_000;

function snapshotProcessEnv(): Record<string, string | undefined> {
  return { ...process.env };
}

function restoreProcessEnv(snapshot: Record<string, string | undefined>): void {
  for (const key of Object.keys(process.env)) {
    if (!(key in snapshot)) {
      delete process.env[key];
    }
  }

  for (const [key, value] of Object.entries(snapshot)) {
    if (value === undefined) {
      delete process.env[key];
      continue;
    }

    process.env[key] = value;
  }
}

function expectPathUnderTempRoot(path: string, tempRoot: string): void {
  const relativePath = relative(tempRoot, path);
  expect(relativePath === '' || (!relativePath.startsWith('..') && !isAbsolute(relativePath))).toBe(
    true,
  );
}

async function writeFixture(path: string, contents: string, tempRoot: string): Promise<void> {
  expectPathUnderTempRoot(path, tempRoot);
  await mkdir(dirname(path), { recursive: true });
  await writeFile(path, contents, 'utf8');
}

interface BootstrapPreflightResult {
  capturedConfig: MosaicConfig | undefined;
}

async function runBootstrapPreflight(
  anchoredConfigContents: string,
  ambientConfigContents: string,
): Promise<BootstrapPreflightResult> {
  const originalEnv = snapshotProcessEnv();
  const tempRoot = await mkdtemp(join(nodeOs.tmpdir(), 'mosaic-gateway-main-preflight-'));
  let cwdSpy: ReturnType<typeof vi.spyOn> | undefined;
  let exitSpy: MockInstance<typeof process.exit> | undefined;
  let consoleInfoSpy: ReturnType<typeof vi.spyOn> | undefined;
  let capturedConfig: MosaicConfig | undefined;

  try {
    const anchor = join(tempRoot, 'anchored', 'apps', 'gateway', 'src');
    const homePath = join(tempRoot, 'home');
    const cwdPath = join(tempRoot, 'ambient', 'cwd');
    const monorepoRootConfigPath = resolve(anchor, '../../..', 'mosaic.config.json');

    await mkdir(anchor, { recursive: true });
    await mkdir(cwdPath, { recursive: true });

    await writeFixture(monorepoRootConfigPath, anchoredConfigContents, tempRoot);
    await writeFixture(join(cwdPath, 'mosaic.config.json'), ambientConfigContents, tempRoot);

    process.env['HOME'] = homePath;
    process.env['BETTER_AUTH_SECRET'] = 'fixture-secret';
    delete process.env['MOSAIC_STORAGE_TIER'];
    delete process.env['DATABASE_URL'];
    delete process.env['VALKEY_URL'];

    consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation((): void => undefined);
    const exitMock = vi.fn<typeof process.exit>();
    exitSpy = vi.spyOn(process, 'exit').mockImplementation(exitMock);

    vi.resetModules();
    vi.doMock('node:os', () => ({ ...nodeOs, homedir: (): string => homePath }));
    vi.doMock('node:url', () => ({
      ...nodeUrl,
      fileURLToPath: (url: string | URL): string => {
        const actualPath = nodeUrl.fileURLToPath(url);
        if (
          actualPath.endsWith('/apps/gateway/src/env.ts') ||
          actualPath.endsWith('/apps/gateway/src/env.js')
        ) {
          return join(anchor, 'env.ts');
        }
        return actualPath;
      },
    }));
    cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(cwdPath);
    vi.doMock('./tracing.js', () => ({}));

    const preflightSentinel = new Error('preflight-capture-sentinel');
    vi.doMock('@mosaicstack/storage', async () => {
      const actual = await vi.importActual<typeof MosaicStorage>('@mosaicstack/storage');
      return {
        ...actual,
        detectAndAssertTier: vi.fn((config: MosaicConfig): Promise<void> => {
          capturedConfig = config;
          throw preflightSentinel;
        }),
      };
    });

    await import('./main.js');
    await vi.waitFor((): void => {
      expect(exitSpy).toHaveBeenCalled();
    });

    return { capturedConfig };
  } finally {
    cwdSpy?.mockRestore();
    exitSpy?.mockRestore();
    consoleInfoSpy?.mockRestore();
    vi.doUnmock('@mosaicstack/storage');
    vi.doUnmock('./tracing.js');
    vi.doUnmock('node:url');
    vi.doUnmock('node:os');
    vi.resetModules();
    restoreProcessEnv(originalEnv);
    await rm(tempRoot, { recursive: true, force: true });
  }
}

describe('main bootstrap preflight config anchoring', (): void => {
  it(
    'passes the anchored monorepo-root config to detectAndAssertTier, not an ambient cwd config',
    async (): Promise<void> => {
      const anchoredConfig = JSON.stringify({
        tier: 'local',
        storage: { type: 'pglite', dataDir: '.mosaic/storage-pglite' },
        queue: { type: 'local', dataDir: '.mosaic/queue' },
        memory: { type: 'keyword' },
      });
      const ambientConfig = JSON.stringify({
        tier: 'federated',
        storage: {
          type: 'postgres',
          url: 'postgresql://ambient-attacker.invalid/mosaic',
          enableVector: true,
        },
        queue: { type: 'bullmq' },
        memory: { type: 'pgvector' },
      });

      const { capturedConfig } = await runBootstrapPreflight(anchoredConfig, ambientConfig);

      expect(capturedConfig?.tier).toBe('local');
      expect(capturedConfig?.storage).not.toEqual(
        expect.objectContaining({ url: 'postgresql://ambient-attacker.invalid/mosaic' }),
      );
    },
    MODULE_IMPORT_TIMEOUT_MS,
  );
});
