import { existsSync, readFileSync, statSync } from 'node:fs';
import { homedir } from 'node:os';
import { join, resolve } from 'node:path';

import { CredentialError } from './types.js';
import type { ProviderRegistry } from './types.js';

export const DEFAULT_CREDENTIALS_DIR = resolve(join(homedir(), '.config', 'mosaic', 'credentials'));
export const OC_CONFIG_PATH = join(homedir(), '.openclaw', 'openclaw.json');
export const REDACTED_MARKER = '__OPENCLAW_REDACTED__';

export const PROVIDER_REGISTRY: ProviderRegistry = {
  anthropic: {
    credential_file: 'anthropic.env',
    env_var: 'ANTHROPIC_API_KEY',
    oc_env_key: 'ANTHROPIC_API_KEY',
    oc_provider_path: 'anthropic',
  },
  openai: {
    credential_file: 'openai.env',
    env_var: 'OPENAI_API_KEY',
    oc_env_key: 'OPENAI_API_KEY',
    oc_provider_path: 'openai',
  },
  zai: {
    credential_file: 'zai.env',
    env_var: 'ZAI_API_KEY',
    oc_env_key: 'ZAI_API_KEY',
    oc_provider_path: 'zai',
  },
};

export function extractProvider(modelRef: string): string {
  const provider = String(modelRef).trim().split('/')[0]?.trim().toLowerCase() ?? '';
  if (!provider) {
    throw new CredentialError(`Unable to resolve provider from model reference: '${modelRef}'`);
  }
  if (!(provider in PROVIDER_REGISTRY)) {
    throw new CredentialError(`Unsupported credential provider: ${provider}`);
  }
  return provider;
}

export function parseDotenv(content: string): Record<string, string> {
  const parsed: Record<string, string> = {};
  for (const rawLine of content.split('\n')) {
    const line = rawLine.trim();
    if (!line || line.startsWith('#')) continue;
    if (!line.includes('=')) continue;
    const eqIdx = line.indexOf('=');
    const key = line.slice(0, eqIdx).trim();
    if (!key) continue;
    let value = line.slice(eqIdx + 1).trim();
    if (
      value.length >= 2 &&
      value[0] === value[value.length - 1] &&
      (value[0] === '"' || value[0] === "'")
    ) {
      value = value.slice(1, -1);
    }
    parsed[key] = value;
  }
  return parsed;
}

function loadCredentialFile(path: string): Record<string, string> {
  if (!existsSync(path)) return {};
  return parseDotenv(readFileSync(path, 'utf-8'));
}

export function stripJSON5Extensions(content: string): string {
  const strings: string[] = [];
  const MARKER = '\x00OCSTR';

  // 1. Remove full-line comments
  content = content.replace(/^\s*\/\/[^\n]*$/gm, '');

  // 2. Protect single-quoted strings
  content = content.replace(/'([^']*)'/g, (_m, g1: string) => {
    const idx = strings.length;
    strings.push(g1);
    return `${MARKER}${idx}\x00`;
  });

  // 3. Protect double-quoted strings
  content = content.replace(/"([^"]*)"/g, (_m, g1: string) => {
    const idx = strings.length;
    strings.push(g1);
    return `${MARKER}${idx}\x00`;
  });

  // 4. Structural transforms — safe because strings are now placeholders
  content = content.replace(/,\s*([}\]])/g, '$1');
  content = content.replace(/\b(\w[\w-]*)\b(?=\s*:)/g, '"$1"');

  // 5. Restore string values with proper JSON escaping
  for (let i = 0; i < strings.length; i++) {
    content = content.replace(`${MARKER}${i}\x00`, JSON.stringify(strings[i]!));
  }

  return content;
}

export interface PermissionCheckOptions {
  ocConfigPath?: string;
}

export function checkOCConfigPermissions(path: string, opts?: { getuid?: () => number }): boolean {
  if (!existsSync(path)) return false;

  const stat = statSync(path);
  const mode = stat.mode & 0o777;
  if (mode & 0o077) {
    // world/group readable — log warning (matches Python behavior)
  }

  const getuid = opts?.getuid ?? process.getuid?.bind(process);
  if (getuid && stat.uid !== getuid()) {
    return false;
  }

  return true;
}

export function isValidCredential(value: string): boolean {
  const stripped = String(value).trim();
  return stripped.length > 0 && stripped !== REDACTED_MARKER;
}

function loadOCConfigCredentials(
  provider: string,
  envVar: string,
  ocConfigPath?: string,
): Record<string, string> {
  const configPath = ocConfigPath ?? OC_CONFIG_PATH;
  if (!existsSync(configPath)) return {};

  try {
    if (!checkOCConfigPermissions(configPath)) return {};
    const rawContent = readFileSync(configPath, 'utf-8');
    const config = JSON.parse(stripJSON5Extensions(rawContent)) as Record<string, unknown>;

    const providerMeta = PROVIDER_REGISTRY[provider];
    const ocEnvKey = providerMeta?.oc_env_key ?? envVar;
    const envBlock = config['env'];
    if (typeof envBlock === 'object' && envBlock !== null && !Array.isArray(envBlock)) {
      const envValue = (envBlock as Record<string, unknown>)[ocEnvKey];
      if (typeof envValue === 'string' && isValidCredential(envValue)) {
        return { [envVar]: envValue.trim() };
      }
    }

    const models = config['models'];
    const providers =
      typeof models === 'object' && models !== null && !Array.isArray(models)
        ? ((models as Record<string, unknown>)['providers'] as Record<string, unknown> | undefined)
        : undefined;
    const ocProviderPath = providerMeta?.oc_provider_path ?? provider;
    if (typeof providers === 'object' && providers !== null && !Array.isArray(providers)) {
      const providerConfig = providers[ocProviderPath];
      if (
        typeof providerConfig === 'object' &&
        providerConfig !== null &&
        !Array.isArray(providerConfig)
      ) {
        const apiKey = (providerConfig as Record<string, unknown>)['apiKey'];
        if (typeof apiKey === 'string' && isValidCredential(apiKey)) {
          return { [envVar]: apiKey.trim() };
        }
      }
    }
  } catch {
    return {};
  }

  return {};
}

function resolveTargetEnvVar(
  provider: string,
  taskConfig?: Record<string, unknown> | null,
): string {
  const providerMeta = PROVIDER_REGISTRY[provider]!;
  const rawCredentials =
    typeof taskConfig === 'object' && taskConfig !== null
      ? (taskConfig['credentials'] as Record<string, unknown> | undefined)
      : undefined;
  const credentials =
    typeof rawCredentials === 'object' && rawCredentials !== null ? rawCredentials : {};
  const envVar = String(credentials['provider_key_env'] || providerMeta.env_var).trim();
  if (!envVar) {
    throw new CredentialError(`Invalid credential env var override for provider: ${provider}`);
  }
  return envVar;
}

export interface ResolveCredentialsOptions {
  taskConfig?: Record<string, unknown> | null;
  credentialsDir?: string;
  ocConfigPath?: string;
}

export function resolveCredentials(
  modelRef: string,
  opts?: ResolveCredentialsOptions,
): Record<string, string> {
  const provider = extractProvider(modelRef);
  const providerMeta = PROVIDER_REGISTRY[provider]!;
  const envVar = resolveTargetEnvVar(provider, opts?.taskConfig);
  const credentialRoot = resolve(opts?.credentialsDir ?? DEFAULT_CREDENTIALS_DIR);
  const credentialFile = join(credentialRoot, providerMeta.credential_file);

  // 1. Mosaic credential file
  const fileValues = loadCredentialFile(credentialFile);
  const fileValue = (fileValues[envVar] ?? '').trim();
  if (fileValue) {
    return { [envVar]: fileValue };
  }

  // 2. OpenClaw config
  const ocValues = loadOCConfigCredentials(provider, envVar, opts?.ocConfigPath);
  if (Object.keys(ocValues).length > 0) {
    return ocValues;
  }

  // 3. Ambient environment
  const ambientValue = String(process.env[envVar] ?? '').trim();
  if (ambientValue) {
    return { [envVar]: ambientValue };
  }

  throw new CredentialError(
    `Missing required credential ${envVar} for provider ${provider} ` +
      `(checked ${credentialFile}, OC config, then ambient environment)`,
  );
}
