import * as fs from 'node:fs';
import * as path from 'node:path';

type JsonRecord = Record<string, unknown>;

export const DEFAULT_PACKAGE_REGISTRY = 'https://registry.npmjs.org/';

export interface IMatrixCliConfig {
  readonly registry?: string;
}

export interface IConfigStoreOptions {
  readonly cwd?: string;
  readonly configPath?: string;
}

export interface IResolvedRegistryConfig {
  readonly registry: string;
  readonly source: 'explicit' | 'env' | 'config' | 'default';
  readonly configPath: string;
}

export interface IConfigCommandResult {
  readonly configPath: string;
  readonly values: IMatrixCliConfig;
}

const CONFIG_KEYS = new Set(['registry']);

function asRecord(value: unknown): JsonRecord | null {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return null;
  }
  return value as JsonRecord;
}

function readOptionalString(value: unknown): string | undefined {
  if (typeof value !== 'string') {
    return undefined;
  }
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : undefined;
}

function normalizeRegistryUrl(value: string): string {
  const trimmed = value.trim();
  if (!trimmed) {
    throw new Error('MX_CONFIG_INVALID: registry must be a non-empty HTTP(S) URL');
  }
  let parsed: URL;
  try {
    parsed = new URL(trimmed);
  } catch {
    throw new Error(`MX_CONFIG_INVALID: registry must be an HTTP(S) URL (${value})`);
  }
  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
    throw new Error(`MX_CONFIG_INVALID: registry must be an HTTP(S) URL (${value})`);
  }
  if (!parsed.pathname.endsWith('/')) {
    parsed.pathname = `${parsed.pathname}/`;
  }
  return parsed.toString();
}

function matrixHome(cwd: string): string {
  return path.resolve(
    cwd,
    process.env.MATRIX_HOME
      ?? path.join(cwd, '.matrix'),
  );
}

export function resolveMatrixCliConfigPath(options: IConfigStoreOptions = {}): string {
  const cwd = path.resolve(options.cwd ?? process.cwd());
  return path.resolve(
    cwd,
    options.configPath
      ?? process.env.MATRIX_CLI_CONFIG
      ?? path.join(matrixHome(cwd), 'config.json'),
  );
}

export function readMatrixCliConfig(options: IConfigStoreOptions = {}): IConfigCommandResult {
  const configPath = resolveMatrixCliConfigPath(options);
  if (!fs.existsSync(configPath)) {
    return { configPath, values: {} };
  }
  const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8')) as unknown;
  const record = asRecord(parsed);
  if (!record) {
    throw new Error(`MX_CONFIG_INVALID: ${configPath} must contain a JSON object`);
  }
  const registry = readOptionalString(record.registry);
  return {
    configPath,
    values: {
      ...(registry ? { registry: normalizeRegistryUrl(registry) } : {}),
    },
  };
}

export function writeMatrixCliConfig(
  values: IMatrixCliConfig,
  options: IConfigStoreOptions = {},
): IConfigCommandResult {
  const configPath = resolveMatrixCliConfigPath(options);
  fs.mkdirSync(path.dirname(configPath), { recursive: true });
  fs.writeFileSync(configPath, `${JSON.stringify(values, null, 2)}\n`, 'utf8');
  return { configPath, values };
}

export function setMatrixCliConfigValue(
  key: string,
  value: string,
  options: IConfigStoreOptions = {},
): IConfigCommandResult {
  const normalizedKey = key.trim();
  if (!CONFIG_KEYS.has(normalizedKey)) {
    throw new Error(`MX_CONFIG_INVALID: unsupported config key "${key}"`);
  }
  const current = readMatrixCliConfig(options);
  const next: IMatrixCliConfig = {
    ...current.values,
    registry: normalizeRegistryUrl(value),
  };
  return writeMatrixCliConfig(next, options);
}

export function resetMatrixCliConfig(options: IConfigStoreOptions = {}): IConfigCommandResult {
  const configPath = resolveMatrixCliConfigPath(options);
  if (fs.existsSync(configPath)) {
    fs.rmSync(configPath, { force: true });
  }
  return { configPath, values: {} };
}

export function resolvePackageRegistry(
  explicitRegistry: string | undefined,
  options: IConfigStoreOptions = {},
): IResolvedRegistryConfig {
  const configPath = resolveMatrixCliConfigPath(options);
  if (explicitRegistry && explicitRegistry.trim().length > 0) {
    return {
      registry: normalizeRegistryUrl(explicitRegistry),
      source: 'explicit',
      configPath,
    };
  }

  const envRegistry = process.env.MATRIX_PACKAGE_REGISTRY
    ?? process.env.MATRIX_REGISTRY_URL;
  if (envRegistry && envRegistry.trim().length > 0) {
    return {
      registry: normalizeRegistryUrl(envRegistry),
      source: 'env',
      configPath,
    };
  }

  const stored = readMatrixCliConfig(options).values.registry;
  if (stored) {
    return {
      registry: stored,
      source: 'config',
      configPath,
    };
  }

  return {
    registry: DEFAULT_PACKAGE_REGISTRY,
    source: 'default',
    configPath,
  };
}
