// src/core/config/config.ts
// Configuration system for OmegaLLM.

import * as fs from "fs";
import * as path from "path";

export type OracleProvider = string;

export type LLMConfig = {
  provider: OracleProvider;
  model: string;
  apiKey?: string;
  baseUrl?: string;
  timeoutMs: number;
  maxTokens: number;
  systemPrompt?: string;
};

export type RuntimeMiddlewareRef = {
  name: string;
  config?: Record<string, unknown>;
};

export type RuntimeConfig = {
  maxOracleTurns: number;
  maxEvalSteps: number;
  maxToolCalls: number;
  maxNestedDepth: number;
  strategy?: string;
  security: RuntimeMiddlewareRef[];
  middleware: RuntimeMiddlewareRef[];
};

export type PackageConfig = Record<string, unknown>;

export type OmegaConfig = {
  llm: LLMConfig;
  runtime: RuntimeConfig;
  packages: Record<string, PackageConfig>;
};

export const DEFAULT_MODELS: Record<string, string> = {
  "inference-fast": "default",
  "inference-reentrant": "default",
};

export const DEFAULT_BASE_URLS: Record<string, string> = {};

export const DEFAULT_LLM_CONFIG: LLMConfig = {
  provider: "inference-fast",
  model: "default",
  timeoutMs: 120_000,
  maxTokens: 4096,
};

export const DEFAULT_RUNTIME_CONFIG: RuntimeConfig = {
  maxOracleTurns: 10_000,
  maxEvalSteps: 500_000,
  maxToolCalls: 1000,
  maxNestedDepth: 16,
  strategy: "deterministic",
  security: [],
  middleware: [],
};

export const DEFAULT_CONFIG: OmegaConfig = {
  llm: DEFAULT_LLM_CONFIG,
  runtime: DEFAULT_RUNTIME_CONFIG,
  packages: {},
};

export function getApiKeyFromEnv(provider: OracleProvider): string | undefined {
  const generic = process.env.OMEGA_API_KEY;
  if (generic) {
    return generic;
  }

  const envKey = `${provider.toUpperCase().replace(/-/g, "_")}_API_KEY`;
  return process.env[envKey];
}

function parseMiddlewareList(value: unknown): RuntimeMiddlewareRef[] {
  if (!value) {
    return [];
  }

  if (typeof value === "string") {
    return value
      .split(",")
      .map((name) => name.trim())
      .filter(Boolean)
      .map((name) => ({ name, config: {} }));
  }

  if (!Array.isArray(value)) {
    return [];
  }

  const refs: RuntimeMiddlewareRef[] = [];
  for (const entry of value) {
    if (typeof entry === "string") {
      const name = entry.trim();
      if (name) {
        refs.push({ name, config: {} });
      }
      continue;
    }

    if (entry && typeof entry === "object" && "name" in entry) {
      const obj = entry as Record<string, unknown>;
      const name = String(obj.name ?? "").trim();
      if (name) {
        refs.push({
          name,
          config: obj.config && typeof obj.config === "object" ? (obj.config as Record<string, unknown>) : {},
        });
      }
    }
  }

  return refs;
}

function cloneRuntimeRefs(refs: RuntimeMiddlewareRef[] | undefined): RuntimeMiddlewareRef[] {
  return (refs ?? []).map((ref) => ({
    name: ref.name,
    config: ref.config ? { ...ref.config } : {},
  }));
}

function clonePackageConfigMap(value: Record<string, PackageConfig> | undefined): Record<string, PackageConfig> {
  if (!value) {
    return {};
  }

  const cloned: Record<string, PackageConfig> = {};
  for (const [pkg, pkgCfg] of Object.entries(value)) {
    cloned[pkg] = { ...(pkgCfg ?? {}) };
  }
  return cloned;
}

function parsePackageConfigMap(value: unknown): Record<string, PackageConfig> {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return {};
  }

  const map = value as Record<string, unknown>;
  const parsed: Record<string, PackageConfig> = {};

  for (const [pkgName, pkgConfig] of Object.entries(map)) {
    if (!pkgConfig || typeof pkgConfig !== "object" || Array.isArray(pkgConfig)) {
      continue;
    }
    parsed[pkgName] = { ...(pkgConfig as Record<string, unknown>) };
  }

  return parsed;
}

export function configFromEnv(prefix = "OMEGA"): OmegaConfig {
  const provider =
    (process.env[`${prefix}_PROVIDER`] as OracleProvider) ||
    DEFAULT_LLM_CONFIG.provider;
  const model =
    process.env[`${prefix}_MODEL`] ||
    DEFAULT_MODELS[provider] ||
    DEFAULT_LLM_CONFIG.model;

  const apiKey = process.env[`${prefix}_API_KEY`] || getApiKeyFromEnv(provider);
  const baseUrl = process.env[`${prefix}_BASE_URL`] || DEFAULT_BASE_URLS[provider];
  const timeoutMs = parseInt(process.env[`${prefix}_TIMEOUT_MS`] || "", 10) || DEFAULT_LLM_CONFIG.timeoutMs;
  const maxTokens = parseInt(process.env[`${prefix}_MAX_TOKENS`] || "", 10) || DEFAULT_LLM_CONFIG.maxTokens;

  const maxOracleTurns = parseInt(process.env[`${prefix}_MAX_ORACLE_TURNS`] || "", 10) || DEFAULT_RUNTIME_CONFIG.maxOracleTurns;
  const maxEvalSteps = parseInt(process.env[`${prefix}_MAX_EVAL_STEPS`] || "", 10) || DEFAULT_RUNTIME_CONFIG.maxEvalSteps;
  const maxToolCalls = parseInt(process.env[`${prefix}_MAX_TOOL_CALLS`] || "", 10) || DEFAULT_RUNTIME_CONFIG.maxToolCalls;
  const maxNestedDepth = parseInt(process.env[`${prefix}_MAX_NESTED_DEPTH`] || "", 10) || DEFAULT_RUNTIME_CONFIG.maxNestedDepth;
  const strategy = process.env[`${prefix}_STRATEGY`] || DEFAULT_RUNTIME_CONFIG.strategy;

  const security = parseMiddlewareList(process.env[`${prefix}_RUNTIME_SECURITY`]);
  const middleware = parseMiddlewareList(process.env[`${prefix}_RUNTIME_MIDDLEWARE`]);

  return {
    llm: {
      provider,
      model,
      apiKey,
      baseUrl,
      timeoutMs,
      maxTokens,
    },
    runtime: {
      maxOracleTurns,
      maxEvalSteps,
      maxToolCalls,
      maxNestedDepth,
      strategy,
      security,
      middleware,
    },
    packages: {},
  };
}

function configOverridesFromEnv(prefix = "OMEGA"): Partial<OmegaConfig> {
  const llm: Partial<LLMConfig> = {};
  const runtime: Partial<RuntimeConfig> = {};

  if (process.env[`${prefix}_PROVIDER`]) {
    llm.provider = process.env[`${prefix}_PROVIDER`] as OracleProvider;
  }
  if (process.env[`${prefix}_MODEL`]) {
    llm.model = process.env[`${prefix}_MODEL`];
  }
  if (process.env[`${prefix}_API_KEY`]) {
    llm.apiKey = process.env[`${prefix}_API_KEY`];
  }
  if (process.env[`${prefix}_BASE_URL`]) {
    llm.baseUrl = process.env[`${prefix}_BASE_URL`];
  }
  if (process.env[`${prefix}_TIMEOUT_MS`]) {
    const value = parseInt(process.env[`${prefix}_TIMEOUT_MS`] || "", 10);
    if (Number.isFinite(value) && value > 0) {
      llm.timeoutMs = value;
    }
  }
  if (process.env[`${prefix}_MAX_TOKENS`]) {
    const value = parseInt(process.env[`${prefix}_MAX_TOKENS`] || "", 10);
    if (Number.isFinite(value) && value > 0) {
      llm.maxTokens = value;
    }
  }

  if (process.env[`${prefix}_MAX_ORACLE_TURNS`]) {
    const value = parseInt(process.env[`${prefix}_MAX_ORACLE_TURNS`] || "", 10);
    if (Number.isFinite(value) && value > 0) {
      runtime.maxOracleTurns = value;
    }
  }
  if (process.env[`${prefix}_MAX_EVAL_STEPS`]) {
    const value = parseInt(process.env[`${prefix}_MAX_EVAL_STEPS`] || "", 10);
    if (Number.isFinite(value) && value > 0) {
      runtime.maxEvalSteps = value;
    }
  }
  if (process.env[`${prefix}_MAX_TOOL_CALLS`]) {
    const value = parseInt(process.env[`${prefix}_MAX_TOOL_CALLS`] || "", 10);
    if (Number.isFinite(value) && value > 0) {
      runtime.maxToolCalls = value;
    }
  }
  if (process.env[`${prefix}_MAX_NESTED_DEPTH`]) {
    const value = parseInt(process.env[`${prefix}_MAX_NESTED_DEPTH`] || "", 10);
    if (Number.isFinite(value) && value > 0) {
      runtime.maxNestedDepth = value;
    }
  }
  if (process.env[`${prefix}_STRATEGY`]) {
    runtime.strategy = process.env[`${prefix}_STRATEGY`];
  }
  if (process.env[`${prefix}_RUNTIME_SECURITY`] !== undefined) {
    runtime.security = parseMiddlewareList(process.env[`${prefix}_RUNTIME_SECURITY`]);
  }
  if (process.env[`${prefix}_RUNTIME_MIDDLEWARE`] !== undefined) {
    runtime.middleware = parseMiddlewareList(process.env[`${prefix}_RUNTIME_MIDDLEWARE`]);
  }

  const hasLlmOverrides = Object.keys(llm).length > 0;
  const hasRuntimeOverrides = Object.keys(runtime).length > 0;

  return {
    ...(hasLlmOverrides ? { llm: llm as LLMConfig } : {}),
    ...(hasRuntimeOverrides ? { runtime: runtime as RuntimeConfig } : {}),
  };
}

export function configFromFile(filePath: string): OmegaConfig {
  if (!fs.existsSync(filePath)) {
    throw new Error(`Config file not found: ${filePath}`);
  }

  const content = fs.readFileSync(filePath, "utf8");
  const ext = path.extname(filePath).toLowerCase();

  let data: Record<string, unknown>;

  if (ext === ".json") {
    data = JSON.parse(content);
  } else if (ext === ".yaml" || ext === ".yml") {
    data = parseSimpleYaml(content);
  } else {
    throw new Error(`Unsupported config file format: ${ext}`);
  }

  return configFromObject(data);
}

export function configFromObject(data: Record<string, unknown>): OmegaConfig {
  const llmData = (data.llm as Record<string, unknown>) || {};
  const runtimeData = (data.runtime as Record<string, unknown>) || {};
  const packagesData = parsePackageConfigMap(data.packages);

  const provider = (llmData.provider as OracleProvider) || DEFAULT_LLM_CONFIG.provider;

  const strategy =
    (runtimeData.strategy as string) ||
    (runtimeData.defaultStrategy as string) ||
    (runtimeData.default_strategy as string) ||
    DEFAULT_RUNTIME_CONFIG.strategy;

  const security = parseMiddlewareList(
    runtimeData.security ?? runtimeData.runtime_security ?? runtimeData.runtimeSecurity,
  );
  const middleware = parseMiddlewareList(
    runtimeData.middleware ?? runtimeData.runtime_middleware ?? runtimeData.runtimeMiddleware,
  );

  return {
    llm: {
      provider,
      model: (llmData.model as string) || DEFAULT_MODELS[provider] || DEFAULT_LLM_CONFIG.model,
      apiKey: (llmData.apiKey as string) || (llmData.api_key as string),
      baseUrl: (llmData.baseUrl as string) || (llmData.base_url as string) || DEFAULT_BASE_URLS[provider],
      timeoutMs: (llmData.timeoutMs as number) || (llmData.timeout_ms as number) || DEFAULT_LLM_CONFIG.timeoutMs,
      maxTokens: (llmData.maxTokens as number) || (llmData.max_tokens as number) || DEFAULT_LLM_CONFIG.maxTokens,
      systemPrompt: (llmData.systemPrompt as string) || (llmData.system_prompt as string),
    },
    runtime: {
      maxOracleTurns:
        (runtimeData.maxOracleTurns as number) ||
        (runtimeData.max_oracle_turns as number) ||
        DEFAULT_RUNTIME_CONFIG.maxOracleTurns,
      maxEvalSteps:
        (runtimeData.maxEvalSteps as number) ||
        (runtimeData.max_eval_steps as number) ||
        DEFAULT_RUNTIME_CONFIG.maxEvalSteps,
      maxToolCalls:
        (runtimeData.maxToolCalls as number) ||
        (runtimeData.max_tool_calls as number) ||
        DEFAULT_RUNTIME_CONFIG.maxToolCalls,
      maxNestedDepth:
        (runtimeData.maxNestedDepth as number) ||
        (runtimeData.max_nested_depth as number) ||
        DEFAULT_RUNTIME_CONFIG.maxNestedDepth,
      strategy,
      security,
      middleware,
    },
    packages: packagesData,
  };
}

export function mergeConfigs(...configs: Partial<OmegaConfig>[]): OmegaConfig {
  const result: OmegaConfig = {
    llm: { ...DEFAULT_CONFIG.llm },
    runtime: {
      ...DEFAULT_CONFIG.runtime,
      security: cloneRuntimeRefs(DEFAULT_CONFIG.runtime.security),
      middleware: cloneRuntimeRefs(DEFAULT_CONFIG.runtime.middleware),
    },
    packages: clonePackageConfigMap(DEFAULT_CONFIG.packages),
  };

  for (const cfg of configs) {
    if (cfg.llm) {
      result.llm = { ...result.llm, ...cfg.llm };
    }

    if (cfg.runtime) {
      result.runtime = {
        ...result.runtime,
        ...cfg.runtime,
      };

      if (cfg.runtime.security !== undefined) {
        result.runtime.security = cloneRuntimeRefs(cfg.runtime.security);
      }

      if (cfg.runtime.middleware !== undefined) {
        result.runtime.middleware = cloneRuntimeRefs(cfg.runtime.middleware);
      }
    }

    if (cfg.packages) {
      for (const [pkgName, pkgConfig] of Object.entries(cfg.packages)) {
        result.packages[pkgName] = {
          ...(result.packages[pkgName] ?? {}),
          ...(pkgConfig ?? {}),
        };
      }
    }
  }

  return result;
}

export function loadConfig(options?: {
  configFile?: string;
  overrides?: Partial<OmegaConfig>;
}): OmegaConfig {
  let config: OmegaConfig = mergeConfigs(DEFAULT_CONFIG);

  if (options?.configFile) {
    const fileConfig = configFromFile(options.configFile);
    config = mergeConfigs(config, fileConfig);
  } else {
    const defaultPaths = ["omega.config.json", "omega.config.yaml", "omega.config.yml", "config.json", "config.yaml"];
    for (const configPath of defaultPaths) {
      if (!fs.existsSync(configPath)) {
        continue;
      }
      const fileConfig = configFromFile(configPath);
      config = mergeConfigs(config, fileConfig);
      break;
    }
  }

  // Environment variables override defaults and file values, but only when set.
  config = mergeConfigs(config, configOverridesFromEnv());

  if (options?.overrides) {
    config = mergeConfigs(config, options.overrides);
  }

  if (!config.llm.apiKey) {
    config.llm.apiKey = getApiKeyFromEnv(config.llm.provider);
  }

  return config;
}

function parseSimpleYaml(content: string): Record<string, unknown> {
  const result: Record<string, unknown> = {};
  const stack: Array<{ obj: Record<string, unknown>; indent: number }> = [{ obj: result, indent: -1 }];

  const lines = content.split("\n");

  for (const rawLine of lines) {
    const trimmed = rawLine.trim();
    if (!trimmed || trimmed.startsWith("#")) {
      continue;
    }

    const indent = rawLine.search(/\S/);
    if (indent < 0) {
      continue;
    }

    while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
      stack.pop();
    }

    const parent = stack[stack.length - 1].obj;

    const colonIdx = trimmed.indexOf(":");
    if (colonIdx < 0) {
      continue;
    }

    const key = trimmed.slice(0, colonIdx).trim();
    const rawValue = trimmed.slice(colonIdx + 1).trim();

    if (rawValue === "") {
      const nested: Record<string, unknown> = {};
      parent[key] = nested;
      stack.push({ obj: nested, indent });
      continue;
    }

    if (rawValue === "true") {
      parent[key] = true;
    } else if (rawValue === "false") {
      parent[key] = false;
    } else if (rawValue === "null") {
      parent[key] = null;
    } else if (/^-?\d+$/.test(rawValue)) {
      parent[key] = parseInt(rawValue, 10);
    } else if (/^-?\d+\.\d+$/.test(rawValue)) {
      parent[key] = parseFloat(rawValue);
    } else if (
      (rawValue.startsWith('"') && rawValue.endsWith('"')) ||
      (rawValue.startsWith("'") && rawValue.endsWith("'"))
    ) {
      parent[key] = rawValue.slice(1, -1);
    } else {
      parent[key] = rawValue;
    }
  }

  return result;
}

export type ConfigValidation = {
  valid: boolean;
  errors: string[];
  warnings: string[];
};

export function validateConfig(config: OmegaConfig): ConfigValidation {
  const errors: string[] = [];
  const warnings: string[] = [];

  if (!config.llm.apiKey && config.llm.provider) {
    warnings.push(
      `No API key for provider: ${config.llm.provider}. Set ${config.llm.provider.toUpperCase().replace(/-/g, "_")}_API_KEY or OMEGA_API_KEY if required.`,
    );
  }

  if (config.runtime.maxOracleTurns < 1) {
    errors.push("maxOracleTurns must be at least 1");
  }

  if (config.runtime.maxEvalSteps < 100) {
    warnings.push("maxEvalSteps is very low, may cause premature termination");
  }

  if (config.runtime.maxToolCalls < 1) {
    errors.push("maxToolCalls must be at least 1");
  }

  if (config.runtime.maxNestedDepth < 1) {
    errors.push("maxNestedDepth must be at least 1");
  }

  for (const [idx, item] of config.runtime.security.entries()) {
    if (!item.name || item.name.trim().length === 0) {
      errors.push(`runtime.security[${idx}] must define a middleware name`);
    }
  }

  for (const [idx, item] of config.runtime.middleware.entries()) {
    if (!item.name || item.name.trim().length === 0) {
      errors.push(`runtime.middleware[${idx}] must define a middleware name`);
    }
  }

  for (const [pkg, pkgConfig] of Object.entries(config.packages)) {
    if (!pkg || pkg.trim().length === 0) {
      errors.push("packages contains an empty package name key");
      continue;
    }

    if (!pkgConfig || typeof pkgConfig !== "object" || Array.isArray(pkgConfig)) {
      errors.push(`packages.${pkg} must be an object`);
    }
  }

  return {
    valid: errors.length === 0,
    errors,
    warnings,
  };
}
