import {
  DEFAULT_MODEL_CONFIG_KEYS,
  type DEFAULT_MODEL_CONFIG_KEYS_LEGACY,
  INSIGHT_MODEL_CONFIG_KEYS,
  PLANNING_MODEL_CONFIG_KEYS,
} from './constants';
import {
  type IModelConfig,
  MIDSCENE_MODEL_FAMILY,
  MIDSCENE_OPENAI_HTTP_PROXY,
  MIDSCENE_OPENAI_INIT_CONFIG_JSON,
  MIDSCENE_OPENAI_SOCKS_PROXY,
  MIDSCENE_USE_DOUBAO_VISION,
  MIDSCENE_USE_GEMINI,
  MIDSCENE_USE_QWEN3_VL,
  MIDSCENE_USE_QWEN_VL,
  MIDSCENE_USE_VLM_UI_TARS,
  MODEL_FAMILY_VALUES,
  OPENAI_API_KEY,
  OPENAI_BASE_URL,
  type TIntent,
  type TModelFamily,
  UITarsModelVersion,
} from './types';

import { getDebug } from '../logger';

import { assert } from '../utils';
import { maskConfig, parseJson } from './helper';
import { initDebugConfig } from './init-debug';

declare const __VERSION__: string | undefined;

const MODEL_CONFIG_DOC_URL = 'https://midscenejs.com/model-common-config.html';

const getCurrentVersion = (): string => {
  if (typeof __VERSION__ !== 'undefined' && __VERSION__) {
    return __VERSION__;
  }

  return 'unknown';
};

const getInvalidModelFamilyMessage = (modelFamily: TModelFamily): string =>
  `Invalid MIDSCENE_MODEL_FAMILY value: ${modelFamily}. Current version v${getCurrentVersion()} accepts the following model families: ${MODEL_FAMILY_VALUES.join(', ')}. You can also visit ${MODEL_CONFIG_DOC_URL} for the latest configuration information.`;

type TModelConfigKeys =
  | typeof INSIGHT_MODEL_CONFIG_KEYS
  | typeof PLANNING_MODEL_CONFIG_KEYS
  | typeof DEFAULT_MODEL_CONFIG_KEYS
  | typeof DEFAULT_MODEL_CONFIG_KEYS_LEGACY;

const KEYS_MAP: Record<TIntent, TModelConfigKeys> = {
  insight: INSIGHT_MODEL_CONFIG_KEYS,
  planning: PLANNING_MODEL_CONFIG_KEYS,
  default: DEFAULT_MODEL_CONFIG_KEYS,
} as const;

/**
 * Get UI-TARS model version from model family
 * @param modelFamily - The model family value
 * @returns UITarsModelVersion if the model family is a UI-TARS variant, undefined otherwise
 */
export const getUITarsModelVersion = (
  modelFamily?: TModelFamily,
): UITarsModelVersion | undefined => {
  // UI-TARS variants with version handling
  if (modelFamily === 'vlm-ui-tars') {
    return UITarsModelVersion.V1_0;
  }

  if (
    modelFamily === 'vlm-ui-tars-doubao' ||
    modelFamily === 'vlm-ui-tars-doubao-1.5'
  ) {
    return UITarsModelVersion.DOUBAO_1_5_20B;
  }

  return undefined;
};

/**
 * Validate model family value
 * @param modelFamily - The model family value to validate
 * @throws Error if the model family is invalid
 */
export const validateModelFamily = (modelFamily?: TModelFamily): void => {
  if (modelFamily && !MODEL_FAMILY_VALUES.includes(modelFamily as any)) {
    throw new Error(getInvalidModelFamilyMessage(modelFamily));
  }
};

/**
 * Convert legacy environment variables to model family
 * @param provider - Environment variable provider (e.g., process.env)
 * @returns The corresponding model family value, or undefined if no legacy config is found
 */
export const legacyConfigToModelFamily = (
  provider: Record<string, string | undefined>,
): TModelFamily | undefined => {
  const isDoubao = provider[MIDSCENE_USE_DOUBAO_VISION];
  const isQwen = provider[MIDSCENE_USE_QWEN_VL];
  const isQwen3 = provider[MIDSCENE_USE_QWEN3_VL];
  const isUiTars = provider[MIDSCENE_USE_VLM_UI_TARS];
  const isGemini = provider[MIDSCENE_USE_GEMINI];

  const enabledModes = [
    isDoubao && MIDSCENE_USE_DOUBAO_VISION,
    isQwen && MIDSCENE_USE_QWEN_VL,
    isQwen3 && MIDSCENE_USE_QWEN3_VL,
    isUiTars && MIDSCENE_USE_VLM_UI_TARS,
    isGemini && MIDSCENE_USE_GEMINI,
  ].filter(Boolean);

  if (enabledModes.length > 1) {
    throw new Error(
      `Only one vision mode can be enabled at a time. Currently enabled modes: ${enabledModes.join(', ')}. Please disable all but one mode.`,
    );
  }

  // Simple modes that directly map to model family
  if (isQwen3) return 'qwen3-vl';
  if (isQwen) return 'qwen2.5-vl';
  if (isDoubao) return 'doubao-vision';
  if (isGemini) return 'gemini';

  // UI-TARS with version detection
  if (isUiTars) {
    if (isUiTars === '1') {
      return 'vlm-ui-tars';
    } else if (isUiTars === 'DOUBAO' || isUiTars === 'DOUBAO-1.5') {
      return 'vlm-ui-tars-doubao-1.5';
    } else {
      // Handle other UI-TARS versions
      return 'vlm-ui-tars-doubao';
    }
  }

  return undefined;
};

const getModelDescription = (
  modelFamily: TModelFamily | undefined,
  uiTarsModelVersion: UITarsModelVersion | undefined,
) => {
  if (uiTarsModelVersion) {
    return `UI-TARS=${uiTarsModelVersion}`;
  }
  if (modelFamily) {
    return `${modelFamily} mode`;
  }
  return '';
};

const normalizeOpenaiExtraConfig = (
  config: unknown,
): Record<string, unknown> | undefined => {
  if (!config || typeof config !== 'object' || Array.isArray(config)) {
    return undefined;
  }

  const { defaultHeaders, extra_headers, extraHeaders, ...rest } =
    config as Record<string, unknown>;

  // Priority: defaultHeaders > extra_headers > extraHeaders
  const headers = defaultHeaders ?? extra_headers ?? extraHeaders;

  if (headers !== undefined) {
    return { ...rest, defaultHeaders: headers };
  }

  return rest;
};

const parseTemperature = (rawValue: string | undefined): number | undefined => {
  if (rawValue === undefined || rawValue === '') {
    return undefined;
  }

  const temperature = Number(rawValue);
  return Number.isFinite(temperature) ? temperature : undefined;
};

/**
 * Parse OpenAI SDK config
 */
export const parseOpenaiSdkConfig = ({
  keys,
  provider,
  useLegacyLogic = false,
}: {
  keys: TModelConfigKeys;
  provider: Record<string, string | undefined>;
  useLegacyLogic?: boolean;
}): IModelConfig => {
  initDebugConfig();
  const debugLog = getDebug('ai:config');

  debugLog('enter parseOpenaiSdkConfig with keys:', keys);

  const legacyAPIKey = useLegacyLogic ? provider[OPENAI_API_KEY] : undefined;
  const legacyBaseURL = useLegacyLogic ? provider[OPENAI_BASE_URL] : undefined;
  const legacySocksProxy = useLegacyLogic
    ? provider[MIDSCENE_OPENAI_SOCKS_PROXY]
    : undefined;
  const legacyHttpProxy = useLegacyLogic
    ? provider[MIDSCENE_OPENAI_HTTP_PROXY]
    : undefined;
  const legacyOpenaiExtraConfig = useLegacyLogic
    ? provider[MIDSCENE_OPENAI_INIT_CONFIG_JSON]
    : undefined;
  const legacyModelFamily = useLegacyLogic
    ? legacyConfigToModelFamily(provider)
    : undefined;

  const modelFamilyRaw = provider[keys.modelFamily] || legacyModelFamily;
  const openaiApiKey: string | undefined =
    provider[keys.openaiApiKey] || legacyAPIKey;
  const openaiBaseURL: string | undefined =
    provider[keys.openaiBaseURL] || legacyBaseURL;
  const socksProxy: string | undefined =
    provider[keys.socksProxy] || legacySocksProxy;
  const httpProxy: string | undefined =
    provider[keys.httpProxy] || legacyHttpProxy;
  const modelName: string | undefined = provider[keys.modelName];
  const openaiExtraConfigStr: string | undefined =
    provider[keys.openaiExtraConfig];
  const openaiExtraConfig = parseJson(
    keys.openaiExtraConfig,
    openaiExtraConfigStr || legacyOpenaiExtraConfig,
  );
  const extraBodyStr: string | undefined = provider[keys.extraBody];
  const extraBody = parseJson(keys.extraBody, extraBodyStr);
  const temperature = parseTemperature(provider[keys.temperature]);

  const modelFamily = modelFamilyRaw as unknown as TModelFamily;
  validateModelFamily(modelFamily);
  const uiTarsModelVersion = getUITarsModelVersion(modelFamily);

  const modelDescription = getModelDescription(modelFamily, uiTarsModelVersion);

  return {
    socksProxy,
    httpProxy,
    openaiBaseURL,
    openaiApiKey,
    openaiExtraConfig: normalizeOpenaiExtraConfig(openaiExtraConfig),
    extraBody,
    modelFamily,
    uiTarsModelVersion,
    modelName: modelName!,
    modelDescription,
    intent: '-' as any,
    slot: '-' as any,
    timeout: provider[keys.timeout]
      ? Number(provider[keys.timeout])
      : undefined,
    temperature,
    retryCount: (() => {
      if (!provider[keys.retryCount]) return 1;
      const val = Number(provider[keys.retryCount]);
      if (!Number.isFinite(val)) return 1;
      if (val < 0)
        throw new Error(`${keys.retryCount} must be non-negative, got ${val}`);
      return val;
    })(),
    retryInterval: (() => {
      if (!provider[keys.retryInterval]) return 2000;
      const val = Number(provider[keys.retryInterval]);
      if (!Number.isFinite(val)) return 2000;
      if (val < 0)
        throw new Error(
          `${keys.retryInterval} must be non-negative, got ${val}`,
        );
      return val;
    })(),
    reasoningEffort: provider[keys.reasoningEffort]?.trim() || undefined,
    reasoningEnabled: (() => {
      const val = provider[keys.reasoningEnabled]?.trim()?.toLowerCase();
      if (val === 'true' || val === '1') return true;
      if (val === 'false' || val === '0') return false;
      if (val === 'default') return 'default';
      return undefined;
    })(),
    reasoningBudget: (() => {
      const val = provider[keys.reasoningBudget]?.trim();
      if (!val) return undefined;
      const num = Number(val);
      return Number.isFinite(num) ? num : undefined;
    })(),
    responseFormat: (() => {
      const val = provider[keys.responseFormat]?.trim()?.toLowerCase();
      if (!val || val === 'auto') return 'auto';
      if (val === 'none') return 'none';
      throw new Error(
        `${keys.responseFormat} must be one of: none, auto. Got: ${val}`,
      );
    })(),
  };
};

export const decideModelConfigFromIntentConfig = (
  intent: TIntent,
  configMap: Record<string, string | undefined>,
): IModelConfig | undefined => {
  const debugLog = getDebug('ai:config');

  debugLog(
    'will decideModelConfig base on agent.modelConfig()',
    intent,
    maskConfig(configMap),
  );

  const keysForFn = KEYS_MAP[intent];
  const modelName = configMap[keysForFn.modelName];

  if (!modelName) {
    debugLog('no modelName found for intent', intent);
    return undefined;
  }

  const finalResult = parseOpenaiSdkConfig({
    keys: keysForFn,
    provider: configMap,
    useLegacyLogic: intent === 'default',
  });
  finalResult.intent = intent;
  finalResult.slot = intent;

  debugLog(
    'decideModelConfig result by agent.modelConfig() with intent',
    intent,
    maskConfig({ ...finalResult }),
  );

  assert(
    finalResult.openaiBaseURL,
    `failed to get base URL of model (intent=${intent}). See https://midscenejs.com/model-strategy`,
  );

  if (!finalResult.modelName) {
    console.warn(
      `modelName is not set for intent ${intent}, this may cause unexpected behavior. See https://midscenejs.com/model-strategy`,
    );
  }

  return finalResult;
};
