/**
 * CLIProxy Variant Settings File Manager
 *
 * Handles creation of settings.json files for CLIProxy variants.
 * These files contain environment variables for Claude CLI integration.
 */

import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { CLIProxyProfileName } from '../../auth/profile-detector';
import { getCcsDir } from '../../utils/config-manager';
import { expandPath } from '../../utils/helpers';
import { getClaudeEnvVars, CLIPROXY_DEFAULT_PORT } from '../config-generator';
import { CLIProxyProvider } from '../types';
import { CompositeTierConfig } from '../../config/unified-config-types';
import { ensureWebSearchMcpOrThrow } from '../../utils/websearch-manager';
import { ensureImageAnalysisMcpOrThrow } from '../../utils/image-analysis';
import {
  ensureProfileHooks as ensureImageAnalyzerHooks,
  removeImageAnalysisProfileHook,
} from '../../utils/hooks/image-analyzer-profile-hook-injector';
import { prepareImageAnalysisFallbackHook } from '../../utils/hooks';
import { getEffectiveApiKey } from '../auth-token-manager';
import { warn } from '../../utils/ui';
import { normalizeModelIdForProvider } from '../model-id-normalizer';

/** Environment settings structure */
interface SettingsEnv {
  [key: string]: string;
  ANTHROPIC_BASE_URL: string;
  ANTHROPIC_AUTH_TOKEN: string;
  ANTHROPIC_MODEL: string;
  ANTHROPIC_DEFAULT_OPUS_MODEL: string;
  ANTHROPIC_DEFAULT_SONNET_MODEL: string;
  ANTHROPIC_DEFAULT_HAIKU_MODEL: string;
}

interface SettingsFile {
  env: Record<string, string>;
  [key: string]: unknown;
}

function canonicalizeModelForProvider(
  provider: CLIProxyProfileName | undefined,
  model: string
): string {
  if (!provider) return model;
  return normalizeModelIdForProvider(model, provider);
}

/**
 * Build settings env object for a variant
 */
function buildSettingsEnv(
  provider: CLIProxyProfileName,
  model: string,
  port: number = CLIPROXY_DEFAULT_PORT
): SettingsEnv {
  const baseEnv = getClaudeEnvVars(provider as CLIProxyProvider, port);
  const normalizedModel = canonicalizeModelForProvider(provider, model);
  const defaultModel = normalizedModel;
  const opusModel = normalizedModel;
  const sonnetModel = normalizedModel;
  const haikuModel = canonicalizeModelForProvider(
    provider,
    baseEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL || model
  );

  return {
    ANTHROPIC_BASE_URL: baseEnv.ANTHROPIC_BASE_URL || '',
    ANTHROPIC_AUTH_TOKEN: baseEnv.ANTHROPIC_AUTH_TOKEN || '',
    ANTHROPIC_MODEL: defaultModel,
    ANTHROPIC_DEFAULT_OPUS_MODEL: opusModel,
    ANTHROPIC_DEFAULT_SONNET_MODEL: sonnetModel,
    ANTHROPIC_DEFAULT_HAIKU_MODEL: haikuModel,
  };
}

/**
 * Ensure directory exists
 */
function ensureDir(dir: string): void {
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
  }
}

/**
 * Write settings file atomically using temp file + rename.
 * The renameSync is atomic on POSIX systems, preventing partial writes on crash.
 */
function writeSettings(filePath: string, settings: SettingsFile): void {
  const tempPath = `${filePath}.tmp.${process.pid}`;
  fs.writeFileSync(tempPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
  fs.renameSync(tempPath, filePath);
}

function rollbackSettingsFile(
  filePath: string,
  previousContent: string | null,
  existedBefore: boolean
): void {
  if (existedBefore && previousContent !== null) {
    fs.writeFileSync(filePath, previousContent, 'utf8');
    return;
  }

  if (fs.existsSync(filePath)) {
    fs.unlinkSync(filePath);
  }
}

/**
 * Get settings file path for a variant
 */
export function getSettingsFilePath(provider: CLIProxyProfileName, name: string): string {
  const ccsDir = getCcsDir();
  return path.join(ccsDir, `${provider}-${name}.settings.json`);
}

/**
 * Get settings file name (without path)
 */
export function getSettingsFileName(provider: CLIProxyProfileName, name: string): string {
  return `${provider}-${name}.settings.json`;
}

/**
 * Get relative settings path with ~ prefix
 */
export function getRelativeSettingsPath(provider: CLIProxyProfileName, name: string): string {
  return `~/.ccs/${getSettingsFileName(provider, name)}`;
}

/**
 * Create settings.json file for CLIProxy variant (legacy mode)
 */
export function createSettingsFile(
  name: string,
  provider: CLIProxyProfileName,
  model: string,
  port: number = CLIPROXY_DEFAULT_PORT
): string {
  const ccsDir = getCcsDir();
  const settingsPath = getSettingsFilePath(provider, name);

  const settings: SettingsFile = {
    env: buildSettingsEnv(provider, model, port),
  };

  const settingsExisted = fs.existsSync(settingsPath);
  const previousSettingsContent = settingsExisted ? fs.readFileSync(settingsPath, 'utf8') : null;
  ensureDir(ccsDir);
  writeSettings(settingsPath, settings);

  try {
    ensureWebSearchMcpOrThrow();
    const imageAnalysisMcpReady = ensureImageAnalysisMcpOrThrow();
    if (imageAnalysisMcpReady) {
      removeImageAnalysisProfileHook(`${provider}-${name}`, settingsPath);
    } else {
      const imageAnalysisFallbackHookReady = prepareImageAnalysisFallbackHook();
      ensureImageAnalyzerHooks({
        profileName: `${provider}-${name}`,
        profileType: 'cliproxy',
        cliproxyProvider: provider,
        settingsPath,
        sharedHookInstalled: imageAnalysisFallbackHookReady,
      });
    }
  } catch (error) {
    rollbackSettingsFile(settingsPath, previousSettingsContent, settingsExisted);
    throw error;
  }

  return settingsPath;
}

/**
 * Create settings.json file for CLIProxy variant (unified mode)
 */
export function createSettingsFileUnified(
  name: string,
  provider: CLIProxyProfileName,
  model: string,
  port: number = CLIPROXY_DEFAULT_PORT
): string {
  const ccsDir = getCcsDir(); // Use centralized function for CCS_HOME support
  const settingsPath = path.join(ccsDir, getSettingsFileName(provider, name));

  const settings: SettingsFile = {
    env: buildSettingsEnv(provider, model, port),
  };

  const settingsExisted = fs.existsSync(settingsPath);
  const previousSettingsContent = settingsExisted ? fs.readFileSync(settingsPath, 'utf8') : null;
  ensureDir(ccsDir);
  writeSettings(settingsPath, settings);

  try {
    ensureWebSearchMcpOrThrow();
    const imageAnalysisMcpReady = ensureImageAnalysisMcpOrThrow();
    if (imageAnalysisMcpReady) {
      removeImageAnalysisProfileHook(`${provider}-${name}`, settingsPath);
    } else {
      const imageAnalysisFallbackHookReady = prepareImageAnalysisFallbackHook();
      ensureImageAnalyzerHooks({
        profileName: `${provider}-${name}`,
        profileType: 'cliproxy',
        cliproxyProvider: provider,
        settingsPath,
        sharedHookInstalled: imageAnalysisFallbackHookReady,
      });
    }
  } catch (error) {
    rollbackSettingsFile(settingsPath, previousSettingsContent, settingsExisted);
    throw error;
  }

  return settingsPath;
}

/**
 * Build settings env object for a composite variant.
 * Uses root URL (no /api/provider/ path) for model-based routing.
 */
function buildCompositeSettingsEnv(
  tiers: { opus: CompositeTierConfig; sonnet: CompositeTierConfig; haiku: CompositeTierConfig },
  defaultTier: 'opus' | 'sonnet' | 'haiku',
  port: number = CLIPROXY_DEFAULT_PORT
): SettingsEnv {
  const defaultModel = tiers[defaultTier].model;

  return {
    // Root URL — CLIProxyAPI routes based on model name, no provider prefix
    ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}`,
    ANTHROPIC_AUTH_TOKEN: getEffectiveApiKey(),
    ANTHROPIC_MODEL: defaultModel,
    ANTHROPIC_DEFAULT_OPUS_MODEL: tiers.opus.model,
    ANTHROPIC_DEFAULT_SONNET_MODEL: tiers.sonnet.model,
    ANTHROPIC_DEFAULT_HAIKU_MODEL: tiers.haiku.model,
  };
}

/**
 * Create settings.json file for a composite variant.
 */
export function createCompositeSettingsFile(
  name: string,
  tiers: { opus: CompositeTierConfig; sonnet: CompositeTierConfig; haiku: CompositeTierConfig },
  defaultTier: 'opus' | 'sonnet' | 'haiku',
  port: number = CLIPROXY_DEFAULT_PORT,
  settingsPathOverride?: string
): string {
  const ccsDir = getCcsDir();
  const defaultSettingsPath = path.join(ccsDir, `composite-${name}.settings.json`);
  const settingsPath = settingsPathOverride
    ? (() => {
        const expanded = expandPath(settingsPathOverride);
        return path.isAbsolute(expanded) ? expanded : path.join(ccsDir, expanded);
      })()
    : defaultSettingsPath;
  const settingsDir = path.dirname(settingsPath);

  const coreEnv = buildCompositeSettingsEnv(tiers, defaultTier, port);
  let settings: SettingsFile = { env: coreEnv };

  // Preserve non-core env vars and non-env fields (hooks/presets/etc.) when regenerating.
  if (fs.existsSync(settingsPath)) {
    try {
      const content = fs.readFileSync(settingsPath, 'utf-8');
      const parsed = JSON.parse(content) as SettingsFile;

      if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
        const existingEnv =
          parsed.env && typeof parsed.env === 'object' && !Array.isArray(parsed.env)
            ? (parsed.env as Record<string, string>)
            : {};
        const {
          ANTHROPIC_BASE_URL: _baseUrl,
          ANTHROPIC_AUTH_TOKEN: _authToken,
          ANTHROPIC_MODEL: _model,
          ANTHROPIC_DEFAULT_OPUS_MODEL: _opus,
          ANTHROPIC_DEFAULT_SONNET_MODEL: _sonnet,
          ANTHROPIC_DEFAULT_HAIKU_MODEL: _haiku,
          ...extraEnv
        } = existingEnv;

        settings = {
          ...parsed,
          env: {
            ...extraEnv,
            ...coreEnv,
          },
        };
      }
    } catch {
      // Invalid JSON — overwrite with a clean settings object.
    }
  }

  const settingsExisted = fs.existsSync(settingsPath);
  const previousSettingsContent = settingsExisted ? fs.readFileSync(settingsPath, 'utf8') : null;
  ensureDir(settingsDir);
  writeSettings(settingsPath, settings);

  if (path.resolve(settingsPath) === path.resolve(defaultSettingsPath)) {
    try {
      ensureWebSearchMcpOrThrow();
      const imageAnalysisMcpReady = ensureImageAnalysisMcpOrThrow();
      if (imageAnalysisMcpReady) {
        removeImageAnalysisProfileHook(`composite-${name}`, settingsPath);
      } else {
        const imageAnalysisFallbackHookReady = prepareImageAnalysisFallbackHook();
        ensureImageAnalyzerHooks({
          profileName: `composite-${name}`,
          profileType: 'cliproxy',
          cliproxyProvider: tiers[defaultTier].provider,
          isComposite: true,
          settingsPath,
          sharedHookInstalled: imageAnalysisFallbackHookReady,
        });
      }
    } catch (error) {
      rollbackSettingsFile(settingsPath, previousSettingsContent, settingsExisted);
      throw error;
    }
  }

  return settingsPath;
}

/**
 * Get relative settings path for a composite variant
 */
export function getCompositeRelativeSettingsPath(name: string): string {
  return `~/.ccs/composite-${name}.settings.json`;
}

/**
 * Delete settings file if it exists.
 * Uses expandPath() for cross-platform path handling.
 */
export function deleteSettingsFile(settingsPath: string): boolean {
  const resolvedPath = expandPath(settingsPath);
  if (fs.existsSync(resolvedPath)) {
    fs.unlinkSync(resolvedPath);
    return true;
  }
  return false;
}

/**
 * Update model in an existing settings file
 */
export function updateSettingsModel(
  settingsPath: string,
  model: string,
  provider?: CLIProxyProfileName,
  options?: {
    rewriteHaikuModel?: (model: string) => string;
  }
): void {
  const fileName = path.basename(settingsPath);
  if (fileName.startsWith('composite-')) {
    console.log(
      warn('Cannot update model for composite variant. Edit config.yaml tiers directly.')
    );
    return;
  }

  const resolvedPath = settingsPath.replace(/^~/, os.homedir());
  if (!fs.existsSync(resolvedPath)) {
    return;
  }

  try {
    const content = fs.readFileSync(resolvedPath, 'utf8');
    const settings = JSON.parse(content) as SettingsFile;

    if (model) {
      settings.env = settings.env || ({} as SettingsEnv);
      const normalizedModel = canonicalizeModelForProvider(provider, model);
      settings.env.ANTHROPIC_MODEL = normalizedModel;
      settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL = normalizedModel;
      settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL = normalizedModel;
      if (provider === 'codex' && settings.env.ANTHROPIC_DEFAULT_HAIKU_MODEL) {
        const normalizedHaikuModel = canonicalizeModelForProvider(
          provider,
          settings.env.ANTHROPIC_DEFAULT_HAIKU_MODEL
        );
        settings.env.ANTHROPIC_DEFAULT_HAIKU_MODEL = options?.rewriteHaikuModel
          ? options.rewriteHaikuModel(normalizedHaikuModel)
          : normalizedHaikuModel;
      }
    } else {
      // Clear model settings to use defaults
      delete (settings.env as unknown as Record<string, string>).ANTHROPIC_MODEL;
    }

    const tempPath = `${resolvedPath}.tmp.${process.pid}`;
    fs.writeFileSync(tempPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
    fs.renameSync(tempPath, resolvedPath);
  } catch {
    // Ignore errors - settings file may be invalid
  }
}

/**
 * Update provider + model core env vars in an existing single-provider settings file.
 * Preserves non-core env vars and top-level settings keys (hooks, presets, etc.).
 */
export function updateSettingsProviderAndModel(
  settingsPath: string,
  provider: CLIProxyProfileName,
  model: string,
  port: number = CLIPROXY_DEFAULT_PORT
): void {
  const resolvedPath = expandPath(settingsPath);
  const fileName = path.basename(resolvedPath);
  if (fileName.startsWith('composite-')) {
    console.log(
      warn('Cannot update provider/model for composite variant. Edit config.yaml tiers directly.')
    );
    return;
  }

  const coreEnv = buildSettingsEnv(provider, model, port);
  let settings: SettingsFile = { env: coreEnv };

  if (fs.existsSync(resolvedPath)) {
    try {
      const content = fs.readFileSync(resolvedPath, 'utf8');
      const parsed = JSON.parse(content) as SettingsFile;
      if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
        const existingEnv =
          parsed.env && typeof parsed.env === 'object' && !Array.isArray(parsed.env)
            ? (parsed.env as Record<string, string>)
            : {};
        const {
          ANTHROPIC_BASE_URL: _baseUrl,
          ANTHROPIC_AUTH_TOKEN: _authToken,
          ANTHROPIC_MODEL: _model,
          ANTHROPIC_DEFAULT_OPUS_MODEL: _opus,
          ANTHROPIC_DEFAULT_SONNET_MODEL: _sonnet,
          ANTHROPIC_DEFAULT_HAIKU_MODEL: _haiku,
          ...extraEnv
        } = existingEnv;

        settings = {
          ...parsed,
          env: {
            ...extraEnv,
            ...coreEnv,
          },
        };
      }
    } catch {
      // Keep default and overwrite malformed file.
    }
  }

  ensureDir(path.dirname(resolvedPath));
  writeSettings(resolvedPath, settings);
}
