import type { IMatrixContext } from '../engine/core/IMatrixContext';
import { RequestReply } from '../engine/remoting/RequestReply';

export type DeepPartial<T> =
  T extends readonly (infer TItem)[] ? ReadonlyArray<DeepPartial<TItem>>
  : T extends (infer TItem)[] ? Array<DeepPartial<TItem>>
  : T extends object ? { [K in keyof T]?: DeepPartial<T[K]> }
  : T;

export interface IPackageConfigProviderDeclaration {
  readonly kind: 'env' | 'file' | 'service';
  readonly prefix?: string;
  readonly env?: string;
  readonly path?: string;
  readonly service?: string;
  readonly namespace?: string;
  readonly optional?: boolean;
}

export interface IPackageConfigContract {
  readonly schema?: string;
  readonly defaults?: string;
  readonly envPrefix?: string;
  readonly providers?: ReadonlyArray<IPackageConfigProviderDeclaration>;
}

export interface IPackageConfigResolutionOptions<TConfig extends object> {
  readonly env?: Record<string, string | undefined>;
  readonly context?: IMatrixContext;
  readonly packageName?: string;
  readonly configServiceMount?: string;
  readonly realm?: string;
  readonly timeoutMs?: number;
  readonly loadFileConfig?: (path: string) => Promise<unknown>;
  readonly loadServiceConfig?: (
    provider: IPackageConfigProviderDeclaration,
    locator?: string,
  ) => Promise<unknown>;
}

export interface IResolveConfiguredPackageConfigParams<TConfig extends object> {
  readonly defaults: TConfig;
  readonly profile?: DeepPartial<TConfig>;
  readonly contract?: IPackageConfigContract;
  readonly overrides?: DeepPartial<TConfig>;
  readonly packageName?: string;
  readonly options?: IPackageConfigResolutionOptions<TConfig>;
}

interface IConfigSchemaEntry {
  readonly key?: string;
}

interface IConfigListForMountResult {
  readonly config?: ReadonlyArray<IConfigSchemaEntry>;
}

interface IConfigGetEffectiveResult {
  readonly value?: unknown;
  readonly source?: string;
}

const DEFAULT_CONFIG_TIMEOUT_MS = 3000;

export function deepMergeConfig<T extends object>(base: T, patch: DeepPartial<T> | undefined): T {
  if (!patch) return structuredClone(base);
  const output = structuredClone(base) as Record<string, unknown>;
  for (const [key, value] of Object.entries(patch as Record<string, unknown>)) {
    if (isRecord(value) && isRecord(output[key])) {
      output[key] = deepMergeConfig(output[key] as Record<string, unknown>, value as Record<string, unknown>);
      continue;
    }
    output[key] = value;
  }
  return output as T;
}

function mergeConfigOverlay<TConfig extends object>(
  base: DeepPartial<TConfig>,
  patch: DeepPartial<TConfig>,
): DeepPartial<TConfig> {
  return deepMergeConfig(
    base as Record<string, unknown>,
    patch as Record<string, unknown>,
  ) as unknown as DeepPartial<TConfig>;
}

export async function resolveConfiguredPackageConfig<TConfig extends object>(
  params: IResolveConfiguredPackageConfigParams<TConfig>,
): Promise<TConfig> {
  const options = params.options ?? {};
  let resolved = deepMergeConfig(params.defaults, params.profile);

  for (const provider of getPackageConfigProviders(params.contract)) {
    const overlay = await resolvePackageConfigProviderOverlay(provider, {
      ...options,
      packageName: options.packageName ?? params.packageName,
    });
    if (overlay) {
      resolved = deepMergeConfig(resolved, overlay as DeepPartial<TConfig>);
    }
  }

  return deepMergeConfig(resolved, params.overrides);
}

function getPackageConfigProviders(
  contract?: IPackageConfigContract,
): ReadonlyArray<IPackageConfigProviderDeclaration> {
  if (Array.isArray(contract?.providers) && contract.providers.length > 0) {
    return contract.providers;
  }
  if (contract?.envPrefix) {
    return [{ kind: 'env', prefix: contract.envPrefix }];
  }
  return [];
}

async function resolvePackageConfigProviderOverlay<TConfig extends object>(
  provider: IPackageConfigProviderDeclaration,
  options: IPackageConfigResolutionOptions<TConfig>,
): Promise<DeepPartial<TConfig> | null> {
  switch (provider.kind) {
    case 'env':
      return resolveEnvOverlay<TConfig>(provider, options.env ?? getAmbientConfigEnv());
    case 'file':
      return resolveFileOverlay<TConfig>(provider, options);
    case 'service':
      return resolveServiceOverlay<TConfig>(provider, options);
    default:
      return null;
  }
}

function resolveEnvOverlay<TConfig extends object>(
  provider: IPackageConfigProviderDeclaration,
  env: Record<string, string | undefined>,
): DeepPartial<TConfig> | null {
  const merged: Record<string, unknown> = {};

  if (provider.prefix) {
    for (const [key, rawValue] of Object.entries(env)) {
      if (!rawValue || !key.startsWith(provider.prefix)) continue;
      const suffix = key.slice(provider.prefix.length);
      if (!suffix) continue;
      const path = suffix
        .split('__')
        .filter((segment) => segment.length > 0)
        .map(envSegmentToPathSegment);
      if (path.length === 0) continue;
      setDeepValue(merged, path, parseEnvValue(rawValue));
    }
  }

  if (provider.env) {
    const rawValue = env[provider.env];
    if (rawValue && rawValue.trim().length > 0) {
      const parsed = parseProviderObject<TConfig>(rawValue, `env:${provider.env}`);
      return Object.keys(merged).length > 0
        ? mergeConfigOverlay<TConfig>(merged as DeepPartial<TConfig>, parsed)
        : parsed;
    }
  }

  return Object.keys(merged).length > 0 ? merged as DeepPartial<TConfig> : null;
}

async function resolveFileOverlay<TConfig extends object>(
  provider: IPackageConfigProviderDeclaration,
  options: IPackageConfigResolutionOptions<TConfig>,
): Promise<DeepPartial<TConfig> | null> {
  const env = options.env ?? getAmbientConfigEnv();
  const candidatePath = provider.env ? env[provider.env] : provider.path;
  if (!candidatePath || candidatePath.trim().length === 0) {
    return null;
  }
  if (!options.loadFileConfig) {
    if (provider.optional) return null;
    throw new Error(`Package config file provider requires loadFileConfig callback for ${candidatePath}`);
  }
  const loaded = await options.loadFileConfig(candidatePath);
  return normalizeConfigOverlay<TConfig>(loaded, `file:${candidatePath}`, provider.optional);
}

async function resolveServiceOverlay<TConfig extends object>(
  provider: IPackageConfigProviderDeclaration,
  options: IPackageConfigResolutionOptions<TConfig>,
): Promise<DeepPartial<TConfig> | null> {
  const env = options.env ?? getAmbientConfigEnv();
  const locator = provider.env ? env[provider.env] : undefined;
  if (provider.env && (!locator || locator.trim().length === 0)) {
    return null;
  }

  if (options.loadServiceConfig) {
    const loaded = await options.loadServiceConfig(provider, locator);
    return normalizeConfigOverlay<TConfig>(loaded, `service:${provider.service ?? provider.namespace ?? 'config'}`, provider.optional);
  }

  const loaded = await resolveSystemConfigOverlay<TConfig>(provider, {
    ...options,
    configServiceMount: locator?.trim() || options.configServiceMount || provider.service,
  });
  return normalizeConfigOverlay<TConfig>(loaded, `service:${provider.service ?? provider.namespace ?? 'config'}`, provider.optional);
}

async function resolveSystemConfigOverlay<TConfig extends object>(
  provider: IPackageConfigProviderDeclaration,
  options: IPackageConfigResolutionOptions<TConfig>,
): Promise<DeepPartial<TConfig> | null> {
  const context = options.context;
  if (!context) {
    if (provider.optional) return null;
    throw new Error(`Package config service provider requires Matrix context for ${provider.service ?? provider.namespace ?? 'config'}`);
  }

  const namespace = provider.namespace?.trim() || options.packageName?.trim();
  if (!namespace) {
    throw new Error(`Package config service provider requires namespace or packageName for ${provider.service ?? 'config'}`);
  }

  const endpoint = await resolveConfigServiceMount(context, options.configServiceMount);
  const timeoutMs = options.timeoutMs ?? DEFAULT_CONFIG_TIMEOUT_MS;
  const listResult = await RequestReply.execute(
    context,
    endpoint,
    'config.listForMount',
    { mount: namespace },
    { timeoutMs },
  ) as IConfigListForMountResult;

  const overlay: Record<string, unknown> = {};
  for (const entry of listResult.config ?? []) {
    const key = typeof entry?.key === 'string' ? entry.key.trim() : '';
    if (!key) continue;
    const result = await RequestReply.execute(
      context,
      endpoint,
      'config.getEffective',
      {
        mount: namespace,
        key,
        ...(options.realm ? { realm: options.realm } : {}),
      },
      { timeoutMs },
    ) as IConfigGetEffectiveResult | null;
    if (!result || !Object.prototype.hasOwnProperty.call(result, 'value')) continue;
    setDeepValue(overlay, key.split('.'), result.value);
  }

  return Object.keys(overlay).length > 0 ? overlay as DeepPartial<TConfig> : null;
}

async function resolveConfigServiceMount(
  context: IMatrixContext,
  preferred?: string,
): Promise<string> {
  const configured = preferred?.trim();
  if (configured) return configured;

  const service = context.getService<string>('config-endpoint');
  if (service && service.trim().length > 0) {
    return service.trim();
  }

  const resolved = await context.resolveServiceAsync?.('config');
  if (resolved && resolved.trim().length > 0) {
    return resolved.trim();
  }

  return 'system.config';
}

function normalizeConfigOverlay<TConfig extends object>(
  value: unknown,
  source: string,
  optional = false,
): DeepPartial<TConfig> | null {
  if (value == null) {
    if (optional) return null;
    throw new Error(`Package config provider returned no value for ${source}`);
  }
  if (!isRecord(value)) {
    throw new Error(`Package config provider returned non-object for ${source}`);
  }
  return value as DeepPartial<TConfig>;
}

function parseProviderObject<TConfig extends object>(
  rawValue: string,
  source: string,
): DeepPartial<TConfig> {
  try {
    const parsed = JSON.parse(rawValue) as unknown;
    if (!isRecord(parsed)) {
      throw new Error(`Package config ${source} must be a JSON object`);
    }
    return parsed as DeepPartial<TConfig>;
  } catch (err) {
    throw new Error(`Failed to parse package config ${source}: ${err instanceof Error ? err.message : String(err)}`);
  }
}

function parseEnvValue(rawValue: string): unknown {
  const value = rawValue.trim();
  if (value === 'true') return true;
  if (value === 'false') return false;
  if (value === 'null') return null;
  if (/^-?\d+(\.\d+)?$/.test(value)) return Number(value);
  if ((value.startsWith('{') && value.endsWith('}')) || (value.startsWith('[') && value.endsWith(']'))) {
    try {
      return JSON.parse(value);
    } catch {
      return rawValue;
    }
  }
  return rawValue;
}

function envSegmentToPathSegment(segment: string): string {
  return segment
    .toLowerCase()
    .split('_')
    .filter((part) => part.length > 0)
    .map((part, index) => (index === 0 ? part : `${part[0].toUpperCase()}${part.slice(1)}`))
    .join('');
}

function setDeepValue(target: Record<string, unknown>, path: string[], value: unknown): void {
  let cursor = target;
  for (let index = 0; index < path.length - 1; index += 1) {
    const key = path[index];
    const next = cursor[key];
    if (!isRecord(next)) {
      cursor[key] = {};
    }
    cursor = cursor[key] as Record<string, unknown>;
  }
  cursor[path[path.length - 1]] = value;
}

function getAmbientConfigEnv(): Record<string, string | undefined> {
  if (typeof process !== 'undefined' && process?.env) {
    return process.env as Record<string, string | undefined>;
  }
  const env = (import.meta as { env?: Record<string, string | undefined> }).env;
  if (env) return env;
  return {};
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}
