import { readFileSync, existsSync } from 'node:fs';
import { resolve } from 'node:path';
import type { StorageConfig } from '@mosaicstack/storage';
import type { QueueAdapterConfig as QueueConfig } from '@mosaicstack/queue';

/* ------------------------------------------------------------------ */
/*  Types                                                              */
/* ------------------------------------------------------------------ */

export type StorageTier = 'local' | 'standalone' | 'federated';

export interface MemoryConfigRef {
  type: 'pgvector' | 'sqlite-vec' | 'keyword';
}

export interface MosaicConfig {
  tier: StorageTier;
  storage: StorageConfig;
  queue: QueueConfig;
  memory: MemoryConfigRef;
}

/* ------------------------------------------------------------------ */
/*  Defaults                                                           */
/* ------------------------------------------------------------------ */

export const DEFAULT_LOCAL_CONFIG: MosaicConfig = {
  tier: 'local',
  storage: { type: 'pglite', dataDir: '.mosaic/storage-pglite' },
  queue: { type: 'local', dataDir: '.mosaic/queue' },
  memory: { type: 'keyword' },
};

export const DEFAULT_STANDALONE_CONFIG: MosaicConfig = {
  tier: 'standalone',
  storage: { type: 'postgres', url: 'postgresql://mosaic:mosaic@localhost:5432/mosaic' },
  queue: { type: 'bullmq' },
  memory: { type: 'keyword' },
};

export const DEFAULT_FEDERATED_CONFIG: MosaicConfig = {
  tier: 'federated',
  storage: {
    type: 'postgres',
    url: 'postgresql://mosaic:mosaic@localhost:5433/mosaic',
    enableVector: true,
  },
  queue: { type: 'bullmq' },
  memory: { type: 'pgvector' },
};

/* ------------------------------------------------------------------ */
/*  Validation                                                         */
/* ------------------------------------------------------------------ */

const VALID_TIERS = new Set<string>(['local', 'standalone', 'federated']);
const VALID_STORAGE_TYPES = new Set<string>(['postgres', 'pglite', 'files']);
const VALID_QUEUE_TYPES = new Set<string>(['bullmq', 'local']);
const VALID_MEMORY_TYPES = new Set<string>(['pgvector', 'sqlite-vec', 'keyword']);

export function validateConfig(raw: unknown): MosaicConfig {
  if (typeof raw !== 'object' || raw === null) {
    throw new Error('MosaicConfig must be a non-null object');
  }

  const obj = raw as Record<string, unknown>;

  // tier
  let tier = obj['tier'];
  // Deprecated alias: 'team' → 'standalone' (kept for backward-compat with 0.0.x installs)
  if (tier === 'team') {
    process.stderr.write(
      '[mosaic] DEPRECATED: tier="team" is deprecated — use "standalone" instead. ' +
        'Update your mosaic.config.json.\n',
    );
    tier = 'standalone';
  }
  if (typeof tier !== 'string' || !VALID_TIERS.has(tier)) {
    throw new Error(
      `Invalid tier "${String(tier)}" — expected "local", "standalone", or "federated"`,
    );
  }

  // storage
  const storage = obj['storage'];
  if (typeof storage !== 'object' || storage === null) {
    throw new Error('config.storage must be a non-null object');
  }
  const storageType = (storage as Record<string, unknown>)['type'];
  if (typeof storageType !== 'string' || !VALID_STORAGE_TYPES.has(storageType)) {
    throw new Error(`Invalid storage.type "${String(storageType)}"`);
  }

  // queue
  const queue = obj['queue'];
  if (typeof queue !== 'object' || queue === null) {
    throw new Error('config.queue must be a non-null object');
  }
  const queueType = (queue as Record<string, unknown>)['type'];
  if (typeof queueType !== 'string' || !VALID_QUEUE_TYPES.has(queueType)) {
    throw new Error(`Invalid queue.type "${String(queueType)}"`);
  }

  // memory
  const memory = obj['memory'];
  if (typeof memory !== 'object' || memory === null) {
    throw new Error('config.memory must be a non-null object');
  }
  const memoryType = (memory as Record<string, unknown>)['type'];
  if (typeof memoryType !== 'string' || !VALID_MEMORY_TYPES.has(memoryType)) {
    throw new Error(`Invalid memory.type "${String(memoryType)}"`);
  }

  return {
    tier: tier as StorageTier,
    storage: storage as StorageConfig,
    queue: queue as QueueConfig,
    memory: memory as MemoryConfigRef,
  };
}

/* ------------------------------------------------------------------ */
/*  Loader                                                             */
/* ------------------------------------------------------------------ */

export function detectFromEnv(): MosaicConfig {
  const tier = process.env['MOSAIC_STORAGE_TIER'];

  if (tier === 'federated') {
    if (process.env['DATABASE_URL']) {
      return {
        ...DEFAULT_FEDERATED_CONFIG,
        storage: {
          type: 'postgres',
          url: process.env['DATABASE_URL'],
          enableVector: true,
        },
        queue: {
          type: 'bullmq',
          url: process.env['VALKEY_URL'],
        },
      };
    }
    // MOSAIC_STORAGE_TIER=federated without DATABASE_URL — use the default
    // federated config (port 5433, enableVector: true, pgvector memory).
    return DEFAULT_FEDERATED_CONFIG;
  }

  if (tier === 'standalone') {
    if (process.env['DATABASE_URL']) {
      return {
        ...DEFAULT_STANDALONE_CONFIG,
        storage: {
          type: 'postgres',
          url: process.env['DATABASE_URL'],
        },
        queue: {
          type: 'bullmq',
          url: process.env['VALKEY_URL'],
        },
      };
    }
    // MOSAIC_STORAGE_TIER=standalone without DATABASE_URL — use the default
    // standalone config instead of silently falling back to local.
    return DEFAULT_STANDALONE_CONFIG;
  }

  // Legacy: DATABASE_URL set without MOSAIC_STORAGE_TIER — treat as standalone.
  if (process.env['DATABASE_URL']) {
    return {
      ...DEFAULT_STANDALONE_CONFIG,
      storage: {
        type: 'postgres',
        url: process.env['DATABASE_URL'],
      },
      queue: {
        type: 'bullmq',
        url: process.env['VALKEY_URL'],
      },
    };
  }

  return DEFAULT_LOCAL_CONFIG;
}

export function loadConfig(configPath?: string): MosaicConfig {
  // 1. Explicit path or default location
  const paths = configPath
    ? [resolve(configPath)]
    : [
        resolve(process.cwd(), 'mosaic.config.json'),
        resolve(process.cwd(), '../../mosaic.config.json'), // monorepo root when cwd is apps/gateway
      ];

  for (const p of paths) {
    if (existsSync(p)) {
      const raw: unknown = JSON.parse(readFileSync(p, 'utf-8'));
      return validateConfig(raw);
    }
  }

  // 2. Fall back to env-var detection
  return detectFromEnv();
}
