import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { FIXED_DIGITAL_HUMAN_CONFIG, type AppConfig } from "@qjclaw/shared-types";

export interface ProjectModelRuntimeSecrets {
  copywritingApiKey?: string;
  imageApiKey?: string;
  videoApiKey?: string;
  digitalHumanVolcAccessKey?: string;
  digitalHumanVolcSecretKey?: string;
  digitalHumanQiniuAccessKey?: string;
  digitalHumanQiniuSecretKey?: string;
  videoAnalyzerApiKey?: string;
  replicationBriefApiKey?: string;
  vectCutApiKey?: string;
  xhsFeishuAppId?: string;
  xhsFeishuAppSecret?: string;
  xhsFeishuAppToken?: string;
  xhsFeishuTableId?: string;
  feishuMobileAppId?: string;
  feishuMobileAppSecret?: string;
}

export interface ProjectModelRuntimePreparation {
  env: Record<string, string>;
  envFileContent: string;
  summary: {
    envKeys: string[];
    copywritingBaseUrl?: string;
    copywritingModelId?: string;
    imageBaseUrl?: string;
    imageModelId?: string;
  };
}

export interface ProjectModelRuntimeValidationResult {
  ok: boolean;
  message?: string;
  missingFields: string[];
}

const XHS_PROJECT_IDS = new Set([
  "xhs",
  "xiaohongshu",
  "xiaohongshu-writer"
]);

const DOUYIN_PROJECT_IDS = new Set([
  "douyin"
]);

const CLIENT_SETTINGS_HINT = "Please complete the required settings in the client and try again.";

function normalizeValue(raw?: string): string {
  return raw?.trim() ?? "";
}

function withoutTrailingSlash(raw?: string): string {
  return normalizeValue(raw).replace(/\/+$/, "");
}

function normalizeChatCompletionsBaseUrl(raw?: string): string {
  const baseUrl = normalizeOpenAiCompatibleBaseUrl(raw);
  if (!baseUrl) {
    return "";
  }

  return baseUrl.endsWith("/chat/completions")
    ? baseUrl
    : `${baseUrl}/chat/completions`;
}

function normalizeOpenAiCompatibleBaseUrl(raw?: string): string {
  const baseUrl = withoutTrailingSlash(raw);
  if (!baseUrl) {
    return "";
  }

  return baseUrl.endsWith("/chat/completions")
    ? baseUrl.slice(0, -"/chat/completions".length)
    : baseUrl;
}

function normalizeArkBaseUrl(raw?: string): string {
  const baseUrl = withoutTrailingSlash(raw);
  if (!baseUrl) {
    return "";
  }

  return baseUrl.endsWith("/images/generations")
    ? baseUrl.slice(0, -"/images/generations".length)
    : baseUrl;
}

function encodeEnvValue(value: string): string {
  return JSON.stringify(value);
}

function formatMissingSection(label: string, missing: string[]): string | null {
  if (missing.length === 0) {
    return null;
  }

  return `${label} missing ${missing.join(", ")}`;
}

export function validateProjectModelRuntime(
  projectId: string,
  config: Pick<AppConfig, "expertModelConfig" | "douyinRuntimeConfig">,
  secrets: ProjectModelRuntimeSecrets
): ProjectModelRuntimeValidationResult {
  const normalizedProjectId = normalizeValue(projectId).toLowerCase();
  const copywritingBaseUrl = normalizeOpenAiCompatibleBaseUrl(config.expertModelConfig.copywriting.baseUrl);
  const copywritingModelId = normalizeValue(config.expertModelConfig.copywriting.modelId);
  const copywritingApiKey = normalizeValue(secrets.copywritingApiKey);
  const imageBaseUrl = normalizeArkBaseUrl(config.expertModelConfig.image.baseUrl);
  const imageModelId = normalizeValue(config.expertModelConfig.image.modelId);
  const imageApiKey = normalizeValue(secrets.imageApiKey);

  if (!XHS_PROJECT_IDS.has(normalizedProjectId) && !DOUYIN_PROJECT_IDS.has(normalizedProjectId)) {
    return {
      ok: true,
      missingFields: []
    };
  }

  const copywritingMissing: string[] = [];
  if (!copywritingBaseUrl) {
    copywritingMissing.push("baseUrl");
  }
  if (!copywritingApiKey) {
    copywritingMissing.push("apiKey");
  }
  if (!copywritingModelId) {
    copywritingMissing.push("modelId");
  }

  const imageMissing: string[] = [];
  if (!imageBaseUrl) {
    imageMissing.push("baseUrl");
  }
  if (!imageApiKey) {
    imageMissing.push("apiKey");
  }
  if (!imageModelId) {
    imageMissing.push("modelId");
  }

  const missingFields: string[] = [
    ...copywritingMissing.map((field) => `copywriting.${field}`),
    ...imageMissing.map((field) => `image.${field}`)
  ];
  const sections: string[] = [
    formatMissingSection("Copywriting model", copywritingMissing),
    formatMissingSection("Image model", imageMissing)
  ].filter((value): value is string => Boolean(value));

  if (DOUYIN_PROJECT_IDS.has(normalizedProjectId)) {
    const videoAnalyzerBaseUrl = normalizeOpenAiCompatibleBaseUrl(config.douyinRuntimeConfig.videoAnalyzer.baseUrl);
    const videoAnalyzerModelId = normalizeValue(config.douyinRuntimeConfig.videoAnalyzer.modelId);
    const videoAnalyzerApiKey = normalizeValue(secrets.videoAnalyzerApiKey);
    const replicationBriefBaseUrl = normalizeOpenAiCompatibleBaseUrl(config.douyinRuntimeConfig.replicationBrief.baseUrl);
    const replicationBriefModelId = normalizeValue(config.douyinRuntimeConfig.replicationBrief.modelId);
    const replicationBriefApiKey = normalizeValue(secrets.replicationBriefApiKey);

    const videoAnalyzerMissing: string[] = [];
    if (!videoAnalyzerBaseUrl) {
      videoAnalyzerMissing.push("baseUrl");
    }
    if (!videoAnalyzerApiKey) {
      videoAnalyzerMissing.push("apiKey");
    }
    if (!videoAnalyzerModelId) {
      videoAnalyzerMissing.push("modelId");
    }

    const replicationBriefMissing: string[] = [];
    if (!replicationBriefBaseUrl) {
      replicationBriefMissing.push("baseUrl");
    }
    if (!replicationBriefApiKey) {
      replicationBriefMissing.push("apiKey");
    }
    if (!replicationBriefModelId) {
      replicationBriefMissing.push("modelId");
    }

    missingFields.push(
      ...videoAnalyzerMissing.map((field) => `douyinRuntimeConfig.videoAnalyzer.${field}`),
      ...replicationBriefMissing.map((field) => `douyinRuntimeConfig.replicationBrief.${field}`)
    );
    sections.push(
      ...[
        formatMissingSection("Video Analyzer", videoAnalyzerMissing),
        formatMissingSection("Replication Brief", replicationBriefMissing)
      ].filter((value): value is string => Boolean(value))
    );
  }

  if (sections.length === 0) {
    return {
      ok: true,
      missingFields: []
    };
  }

  return {
    ok: false,
    message: `${XHS_PROJECT_IDS.has(normalizedProjectId) ? "XHS" : "Douyin"} project is missing client settings: ${sections.join("; ")}. ${CLIENT_SETTINGS_HINT}`,
    missingFields
  };
}

export function buildProjectModelRuntime(
  projectId: string,
  config: Pick<AppConfig, "expertModelConfig" | "douyinRuntimeConfig">,
  secrets: ProjectModelRuntimeSecrets
): ProjectModelRuntimePreparation {
  const normalizedProjectId = normalizeValue(projectId).toLowerCase();
  const validation = validateProjectModelRuntime(projectId, config, secrets);
  if (!validation.ok) {
    throw new Error(validation.message);
  }

  const copywritingBaseUrl = normalizeOpenAiCompatibleBaseUrl(config.expertModelConfig.copywriting.baseUrl);
  const copywritingModelId = normalizeValue(config.expertModelConfig.copywriting.modelId);
  const copywritingApiKey = normalizeValue(secrets.copywritingApiKey);
  const imageBaseUrl = normalizeArkBaseUrl(config.expertModelConfig.image.baseUrl);
  const imageModelId = normalizeValue(config.expertModelConfig.image.modelId);
  const imageApiKey = normalizeValue(secrets.imageApiKey);
  const videoBaseUrl = withoutTrailingSlash(config.expertModelConfig.video.baseUrl);
  const videoModelId = normalizeValue(config.expertModelConfig.video.modelId);
  const videoApiKey = normalizeValue(secrets.videoApiKey);
  const digitalHumanVolcAccessKey = normalizeValue(secrets.digitalHumanVolcAccessKey);
  const digitalHumanVolcSecretKey = normalizeValue(secrets.digitalHumanVolcSecretKey);
  const digitalHumanQiniuAccessKey = normalizeValue(secrets.digitalHumanQiniuAccessKey);
  const digitalHumanQiniuSecretKey = normalizeValue(secrets.digitalHumanQiniuSecretKey);
  const videoAnalyzerBaseUrl = normalizeOpenAiCompatibleBaseUrl(config.douyinRuntimeConfig.videoAnalyzer.baseUrl);
  const videoAnalyzerModelId = normalizeValue(config.douyinRuntimeConfig.videoAnalyzer.modelId);
  const videoAnalyzerApiKey = normalizeValue(secrets.videoAnalyzerApiKey);
  const replicationBriefBaseUrl = normalizeOpenAiCompatibleBaseUrl(config.douyinRuntimeConfig.replicationBrief.baseUrl);
  const replicationBriefModelId = normalizeValue(config.douyinRuntimeConfig.replicationBrief.modelId);
  const replicationBriefApiKey = normalizeValue(secrets.replicationBriefApiKey);
  const vectCutBaseUrl = withoutTrailingSlash(config.douyinRuntimeConfig.vectcut.baseUrl);
  const vectCutFileBaseUrl = withoutTrailingSlash(config.douyinRuntimeConfig.vectcut.fileBaseUrl);
  const vectCutApiKey = normalizeValue(secrets.vectCutApiKey);
  const xhsFeishuAppId = normalizeValue(secrets.xhsFeishuAppId);
  const xhsFeishuAppSecret = normalizeValue(secrets.xhsFeishuAppSecret);
  const xhsFeishuAppToken = normalizeValue(secrets.xhsFeishuAppToken);
  const xhsFeishuTableId = normalizeValue(secrets.xhsFeishuTableId);
  const feishuMobileAppId = normalizeValue(secrets.feishuMobileAppId);
  const feishuMobileAppSecret = normalizeValue(secrets.feishuMobileAppSecret);
  const env: Record<string, string> = {};

  if (XHS_PROJECT_IDS.has(normalizedProjectId)) {
    if (copywritingBaseUrl) {
      env.QWEN_BASE_URL = copywritingBaseUrl;
    }
    if (copywritingApiKey) {
      env.QWEN_API_KEY = copywritingApiKey;
    }
    if (copywritingModelId) {
      env.QWEN_MODEL = copywritingModelId;
      env.QWEN_VISION_MODEL = copywritingModelId;
    }
    if (imageBaseUrl || imageApiKey || imageModelId) {
      env.XHS_IMAGE_PROVIDER = "env:xhsImage";
    }
    if (imageBaseUrl) {
      env.XHS_IMAGE_BASE_URL = imageBaseUrl;
    }
    if (imageApiKey) {
      env.XHS_IMAGE_API_KEY = imageApiKey;
    }
    if (imageModelId) {
      env.XHS_IMAGE_MODEL = imageModelId;
    }
    if (xhsFeishuAppId) {
      env.FEISHU_APP_ID = xhsFeishuAppId;
    }
    if (xhsFeishuAppSecret) {
      env.FEISHU_APP_SECRET = xhsFeishuAppSecret;
    }
    if (xhsFeishuAppToken) {
      env.FEISHU_APP_TOKEN = xhsFeishuAppToken;
    }
    if (xhsFeishuTableId) {
      env.FEISHU_TABLE_ID = xhsFeishuTableId;
    }
  }

  if (feishuMobileAppId) {
    env.FEISHU_MOBILE_APP_ID = feishuMobileAppId;
  }
  if (feishuMobileAppSecret) {
    env.FEISHU_MOBILE_APP_SECRET = feishuMobileAppSecret;
  }

  if (DOUYIN_PROJECT_IDS.has(normalizedProjectId)) {
    const writerBaseUrl = normalizeChatCompletionsBaseUrl(copywritingBaseUrl);
    const seedreamBaseUrl = normalizeArkBaseUrl(imageBaseUrl);

    env.QJC_CLIENT_CONFIG_ACTIVE = "1";

    if (writerBaseUrl) {
      env.DOUYIN_WRITER_BASE_URL = writerBaseUrl;
      env.DOUYIN_WRITER_LLM_BASE_URL = writerBaseUrl;
    }
    if (copywritingApiKey) {
      env.DOUYIN_WRITER_API_KEY = copywritingApiKey;
      env.DASHSCOPE_API_KEY = copywritingApiKey;
      env.QWEN_API_KEY = copywritingApiKey;
    }
    if (copywritingModelId) {
      env.DOUYIN_WRITER_MODEL = copywritingModelId;
      env.DOUYIN_WRITER_LLM_MODEL = copywritingModelId;
    }

    if (seedreamBaseUrl) {
      env.SEEDREAM_BASE_URL = seedreamBaseUrl;
      env.SEEDREAM_ARK_BASE_URL = seedreamBaseUrl;
    }
    if (imageApiKey) {
      env.SEEDREAM_API_KEY = imageApiKey;
      env.SEEDREAM_ARK_API_KEY = imageApiKey;
    }
    if (imageModelId) {
      env.SEEDREAM_MODEL = imageModelId;
    }

    if (videoBaseUrl) {
      env.SEEDANCE_BASE_URL = videoBaseUrl;
      env.SEEDANCE_ARK_BASE_URL = videoBaseUrl;
    }
    if (videoApiKey) {
      env.SEEDANCE_API_KEY = videoApiKey;
      env.SEEDANCE_ARK_API_KEY = videoApiKey;
    }
    if (videoModelId) {
      env.SEEDANCE_MODEL = videoModelId;
    }

    if (videoAnalyzerBaseUrl) {
      env.VIDEO_LLM_ANALYZER_BASE_URL = videoAnalyzerBaseUrl;
      env.ARK_BASE_URL = videoAnalyzerBaseUrl;
    } else if (videoBaseUrl) {
      env.ARK_BASE_URL = videoBaseUrl;
    }
    if (videoAnalyzerApiKey) {
      env.VIDEO_LLM_ANALYZER_API_KEY = videoAnalyzerApiKey;
      env.ARK_API_KEY = videoAnalyzerApiKey;
    } else if (videoApiKey) {
      env.ARK_API_KEY = videoApiKey;
    }
    if (videoAnalyzerModelId) {
      env.VIDEO_LLM_ANALYZER_MODEL = videoAnalyzerModelId;
    }

    if (replicationBriefBaseUrl) {
      env.REPLICATION_BRIEF_BASE_URL = replicationBriefBaseUrl;
    }
    if (replicationBriefApiKey) {
      env.REPLICATION_BRIEF_API_KEY = replicationBriefApiKey;
    }
    if (replicationBriefModelId) {
      env.REPLICATION_BRIEF_MODEL = replicationBriefModelId;
    }

    if (vectCutBaseUrl) {
      env.VECTCUT_BASE_URL = vectCutBaseUrl;
    }
    if (vectCutFileBaseUrl) {
      env.VECTCUT_FILE_BASE_URL = vectCutFileBaseUrl;
    }
    if (vectCutApiKey) {
      env.VECTCUT_API_KEY = vectCutApiKey;
    }

    if (digitalHumanVolcAccessKey) {
      env.OMNIHUMAN_VOLC_ACCESS_KEY = digitalHumanVolcAccessKey;
      env.VOLC_ACCESS_KEY = digitalHumanVolcAccessKey;
    }
    if (digitalHumanVolcSecretKey) {
      env.OMNIHUMAN_VOLC_SECRET_KEY = digitalHumanVolcSecretKey;
      env.VOLC_SECRET_KEY = digitalHumanVolcSecretKey;
    }
    env.OMNIHUMAN_VOLC_REGION = FIXED_DIGITAL_HUMAN_CONFIG.volcRegion;
    env.OMNIHUMAN_VOLC_SERVICE = FIXED_DIGITAL_HUMAN_CONFIG.volcService;
    env.OMNIHUMAN_VOLC_HOST = FIXED_DIGITAL_HUMAN_CONFIG.volcHost;
    env.OMNIHUMAN_VOLC_SCHEME = FIXED_DIGITAL_HUMAN_CONFIG.volcScheme;
    env.OMNIHUMAN_TTS_VOICE = FIXED_DIGITAL_HUMAN_CONFIG.ttsVoice;
    env.VOLC_REGION = FIXED_DIGITAL_HUMAN_CONFIG.volcRegion;
    env.VOLC_SERVICE = FIXED_DIGITAL_HUMAN_CONFIG.volcService;
    env.VOLC_HOST = FIXED_DIGITAL_HUMAN_CONFIG.volcHost;
    env.VOLC_SCHEME = FIXED_DIGITAL_HUMAN_CONFIG.volcScheme;

    if (digitalHumanQiniuAccessKey) {
      env.OMNIHUMAN_QINIU_ACCESS_KEY = digitalHumanQiniuAccessKey;
      env.QINIU_ACCESS_KEY = digitalHumanQiniuAccessKey;
      env.SEEDANCE_QINIU_ACCESS_KEY = digitalHumanQiniuAccessKey;
    }
    if (digitalHumanQiniuSecretKey) {
      env.OMNIHUMAN_QINIU_SECRET_KEY = digitalHumanQiniuSecretKey;
      env.QINIU_SECRET_KEY = digitalHumanQiniuSecretKey;
      env.SEEDANCE_QINIU_SECRET_KEY = digitalHumanQiniuSecretKey;
    }
    env.OMNIHUMAN_QINIU_BUCKET = FIXED_DIGITAL_HUMAN_CONFIG.qiniuBucket;
    env.OMNIHUMAN_QINIU_DOMAIN = FIXED_DIGITAL_HUMAN_CONFIG.qiniuDomain;
    env.OMNIHUMAN_QINIU_KEY_PREFIX = FIXED_DIGITAL_HUMAN_CONFIG.qiniuKeyPrefix;
    env.QINIU_BUCKET = FIXED_DIGITAL_HUMAN_CONFIG.qiniuBucket;
    env.QINIU_DOMAIN = FIXED_DIGITAL_HUMAN_CONFIG.qiniuDomain;
    env.QINIU_KEY_PREFIX = FIXED_DIGITAL_HUMAN_CONFIG.qiniuKeyPrefix;
    env.SEEDANCE_QINIU_BUCKET = FIXED_DIGITAL_HUMAN_CONFIG.qiniuBucket;
    env.SEEDANCE_QINIU_DOMAIN = FIXED_DIGITAL_HUMAN_CONFIG.qiniuDomain;
    env.SEEDANCE_QINIU_KEY_PREFIX = FIXED_DIGITAL_HUMAN_CONFIG.qiniuKeyPrefix;
  }

  const envKeys = Object.keys(env).sort();
  const lines = [
    "# Generated by qjclaw desktop runtime model injection.",
    ...envKeys.map((key) => `${key}=${encodeEnvValue(env[key])}`)
  ];

  return {
    env,
    envFileContent: lines.join("\n") + "\n",
    summary: {
      envKeys,
      ...(copywritingBaseUrl ? { copywritingBaseUrl } : {}),
      ...(copywritingModelId ? { copywritingModelId } : {}),
      ...(imageBaseUrl ? { imageBaseUrl } : {}),
      ...(imageModelId ? { imageModelId } : {})
    }
  };
}

export async function materializeProjectModelRuntime(
  projectRoot: string,
  runtime: ProjectModelRuntimePreparation
): Promise<string> {
  const memoryRoot = path.join(projectRoot, "memory");
  const envFilePath = path.join(memoryRoot, "project.env");
  await mkdir(memoryRoot, { recursive: true });
  await writeFile(envFilePath, runtime.envFileContent, "utf8");
  return envFilePath;
}
