import { env } from "../config/env.js";
import { pool } from "../db/client.js";

export type ExternalServiceId =
  | "yandex_cloud"
  | "yandex_metrica"
  | "yandex_search_serp"
  | "yandex_webmaster"
  | "yandex_wordstat";

type ExternalServiceStatus = "configured" | "disabled" | "missing_required" | "not_implemented";
type ExternalServiceFieldType = "number" | "password" | "text" | "url";

type ExternalServiceField = {
  id: string;
  label: string;
  type: ExternalServiceFieldType;
  required: boolean;
  placeholder: string;
  help: string;
};

type ExternalServiceDefinition = {
  id: ExternalServiceId;
  label: string;
  description: string;
  implementationStatus: "active" | "planned";
  defaultMode: string;
  modes: Array<{
    id: string;
    label: string;
  }>;
  docs: Array<{
    label: string;
    url: string;
  }>;
  publicFields: ExternalServiceField[];
  secretFields: ExternalServiceField[];
};

type ExternalServiceSettingsRow = {
  service_id: ExternalServiceId;
  enabled: boolean;
  mode: string;
  public_config: Record<string, unknown>;
  secret_config: Record<string, unknown>;
  created_at: Date;
  updated_at: Date;
};

export type ExternalServiceSettings = {
  schemaVersion: "external-service-settings.v1";
  services: Array<{
    id: ExternalServiceId;
    label: string;
    description: string;
    implementationStatus: ExternalServiceDefinition["implementationStatus"];
    status: ExternalServiceStatus;
    enabled: boolean;
    mode: string;
    modes: ExternalServiceDefinition["modes"];
    dependsOn: ExternalServiceId[];
    docs: ExternalServiceDefinition["docs"];
    publicFields: ExternalServiceField[];
    secretFields: ExternalServiceField[];
    publicConfig: Record<string, string>;
    secrets: Record<
      string,
      {
        configured: boolean;
      }
    >;
    missingFields: string[];
    updatedAt: string | null;
  }>;
};

export type UpdateExternalServiceSettingsInput = {
  enabled: boolean;
  mode: string;
  publicConfig?: Record<string, unknown>;
  secrets?: Record<string, unknown>;
};

export type WordstatRuntimeConfig =
  | {
      provider: "disabled";
      mode: "disabled";
    }
  | {
      provider: "mcp_kv";
      mode: "mcp_kv";
      mcpEndpoint: string;
    }
  | {
      provider: "yandex_api";
      mode: "yandex_search_api";
      apiBaseUrl: string;
      apiToken: string;
      authType: "api_key" | "iam_token";
      folderId: string;
      regionIds: string[];
      devices: string[];
      numPhrases: number;
    };

const WORDSTAT_BASE_URL = "https://searchapi.api.cloud.yandex.net/v2/wordstat";
const WEB_SEARCH_BASE_URL = "https://searchapi.api.cloud.yandex.net/v2/web/search";
const WEBMASTER_BASE_URL = "https://api.webmaster.yandex.net/v4";
const METRICA_BASE_URL = "https://api-metrika.yandex.net";

const serviceDefinitions: Record<ExternalServiceId, ExternalServiceDefinition> = {
  yandex_cloud: {
    id: "yandex_cloud",
    label: "Yandex Cloud credentials",
    description: "Один общий credential для Search API слоёв: Wordstat сейчас, SERP позже.",
    implementationStatus: "active",
    defaultMode: "api_key",
    modes: [
      { id: "disabled", label: "Отключено" },
      { id: "api_key", label: "API key" },
      { id: "iam_token", label: "IAM token" }
    ],
    docs: [
      {
        label: "API keys",
        url: "https://yandex.cloud/en/docs/iam/concepts/authorization/api-key"
      },
      {
        label: "Folder ID",
        url: "https://yandex.cloud/en/docs/resource-manager/operations/folder/get-id"
      }
    ],
    publicFields: [
      {
        id: "folderId",
        label: "Yandex Cloud folderId",
        type: "text",
        required: true,
        placeholder: "b1g...",
        help: "ID каталога Yandex Cloud. Его используют Wordstat и SERP Search API."
      }
    ],
    secretFields: [
      {
        id: "apiKey",
        label: "API key",
        type: "password",
        required: false,
        placeholder: "Api-Key ...",
        help: "Yandex Cloud API key сервисного аккаунта с доступом к Search API."
      },
      {
        id: "iamToken",
        label: "IAM token",
        type: "password",
        required: false,
        placeholder: "Bearer token",
        help: "Альтернатива API key. Для prototype можно хранить локально, позже уйдёт в vault."
      }
    ]
  },
  yandex_wordstat: {
    id: "yandex_wordstat",
    label: "Yandex Wordstat",
    description: "Частотность, похожие запросы, динамика и региональная раскладка через Yandex Search API Wordstat.",
    implementationStatus: "active",
    defaultMode: "yandex_search_api",
    modes: [
      { id: "disabled", label: "Отключено" },
      { id: "yandex_search_api", label: "Yandex Search API Wordstat" },
      { id: "mcp_kv", label: "MCP/adapter endpoint" }
    ],
    docs: [
      {
        label: "Wordstat GetTop",
        url: "https://aistudio.yandex.ru/docs/en/search-api/api-ref/Wordstat/getTop"
      },
      {
        label: "Regions",
        url: "https://aistudio.yandex.ru/docs/en/search-api/api-ref/Wordstat/getRegionsDistribution"
      },
      {
        label: "Dynamics",
        url: "https://aistudio.yandex.ru/docs/ru/search-api/api-ref/Wordstat/getDynamics"
      }
    ],
    publicFields: [
      {
        id: "apiBaseUrl",
        label: "API base URL",
        type: "url",
        required: true,
        placeholder: WORDSTAT_BASE_URL,
        help: "Root endpoint Wordstat v2. Методы вызываются как /topRequests, /regions, /dynamics."
      },
      {
        id: "regionIds",
        label: "Region IDs",
        type: "text",
        required: false,
        placeholder: "225",
        help: "Через запятую. 225 — Россия; можно заменить на нужные регионы после RegionsTree."
      },
      {
        id: "devices",
        label: "Devices",
        type: "text",
        required: false,
        placeholder: "DEVICE_ALL",
        help: "Через запятую. Для первого слоя держим DEVICE_ALL."
      },
      {
        id: "numPhrases",
        label: "Phrases per seed",
        type: "number",
        required: false,
        placeholder: "50",
        help: "Сколько Wordstat rows запрашивать для одной seed-фразы."
      },
      {
        id: "mcpEndpoint",
        label: "MCP endpoint",
        type: "url",
        required: false,
        placeholder: "http://localhost:4120/wordstat/collect",
        help: "Используется только в режиме MCP/adapter endpoint."
      }
    ],
    secretFields: []
  },
  yandex_search_serp: {
    id: "yandex_search_serp",
    label: "Yandex SERP",
    description: "Поисковая выдача, конкуренты, сниппеты и типы страниц через Yandex Search API WebSearch.",
    implementationStatus: "planned",
    defaultMode: "yandex_search_api",
    modes: [
      { id: "disabled", label: "Отключено" },
      { id: "yandex_search_api", label: "Yandex Search API WebSearch" }
    ],
    docs: [
      {
        label: "WebSearch.Search",
        url: "https://aistudio.yandex.ru/docs/en/search-api/api-ref/WebSearch/search"
      }
    ],
    publicFields: [
      {
        id: "apiBaseUrl",
        label: "API endpoint",
        type: "url",
        required: true,
        placeholder: WEB_SEARCH_BASE_URL,
        help: "REST endpoint для WebSearch.Search."
      },
      {
        id: "searchType",
        label: "Search type",
        type: "text",
        required: false,
        placeholder: "SEARCH_TYPE_RU",
        help: "Для российского рынка держим SEARCH_TYPE_RU."
      }
    ],
    secretFields: []
  },
  yandex_webmaster: {
    id: "yandex_webmaster",
    label: "Yandex Webmaster",
    description: "Индексация, ошибки обхода, sitemap, canonical и статусы страниц после Apply.",
    implementationStatus: "planned",
    defaultMode: "oauth",
    modes: [
      { id: "disabled", label: "Отключено" },
      { id: "oauth", label: "OAuth token" }
    ],
    docs: [
      {
        label: "Webmaster API",
        url: "https://yandex.ru/dev/webmaster/doc/en/"
      }
    ],
    publicFields: [
      {
        id: "apiBaseUrl",
        label: "API base URL",
        type: "url",
        required: true,
        placeholder: WEBMASTER_BASE_URL,
        help: "Базовый URL Webmaster API."
      },
      {
        id: "hostId",
        label: "Host ID",
        type: "text",
        required: false,
        placeholder: "https:example.com:443",
        help: "ID сайта в Webmaster, можно заполнить позже после discovery."
      }
    ],
    secretFields: [
      {
        id: "oauthToken",
        label: "OAuth token",
        type: "password",
        required: true,
        placeholder: "OAuth ...",
        help: "Токен доступа Webmaster API."
      }
    ]
  },
  yandex_metrica: {
    id: "yandex_metrica",
    label: "Yandex Metrica",
    description: "Фактические показы, клики, CTR, цели и обратная связь после SEO-правок.",
    implementationStatus: "planned",
    defaultMode: "oauth",
    modes: [
      { id: "disabled", label: "Отключено" },
      { id: "oauth", label: "OAuth token" }
    ],
    docs: [
      {
        label: "Metrica API",
        url: "https://yandex.ru/dev/metrika/doc/api2/concept/about.html"
      }
    ],
    publicFields: [
      {
        id: "apiBaseUrl",
        label: "API base URL",
        type: "url",
        required: true,
        placeholder: METRICA_BASE_URL,
        help: "Базовый URL Metrica API."
      },
      {
        id: "counterId",
        label: "Counter ID",
        type: "text",
        required: true,
        placeholder: "12345678",
        help: "ID счётчика Метрики."
      }
    ],
    secretFields: [
      {
        id: "oauthToken",
        label: "OAuth token",
        type: "password",
        required: true,
        placeholder: "OAuth ...",
        help: "Токен Metrica API."
      }
    ]
  }
};

function toStringRecord(value: Record<string, unknown> | null | undefined) {
  const output: Record<string, string> = {};

  for (const [key, item] of Object.entries(value ?? {})) {
    if (item === null || item === undefined) {
      continue;
    }

    output[key] = String(item);
  }

  return output;
}

function getFieldDefault(serviceId: ExternalServiceId, fieldId: string) {
  if (serviceId === "yandex_cloud") {
    return "";
  }

  if (serviceId === "yandex_wordstat") {
    const defaults: Record<string, string> = {
      apiBaseUrl: WORDSTAT_BASE_URL,
      devices: "DEVICE_ALL",
      numPhrases: "50",
      regionIds: "225"
    };

    return defaults[fieldId] ?? "";
  }

  if (serviceId === "yandex_search_serp") {
    const defaults: Record<string, string> = {
      apiBaseUrl: WEB_SEARCH_BASE_URL,
      searchType: "SEARCH_TYPE_RU"
    };

    return defaults[fieldId] ?? "";
  }

  if (serviceId === "yandex_webmaster") {
    return fieldId === "apiBaseUrl" ? WEBMASTER_BASE_URL : "";
  }

  if (serviceId === "yandex_metrica") {
    return fieldId === "apiBaseUrl" ? METRICA_BASE_URL : "";
  }

  return "";
}

function getDependsOn(serviceId: ExternalServiceId, mode: string): ExternalServiceId[] {
  if ((serviceId === "yandex_wordstat" || serviceId === "yandex_search_serp") && mode === "yandex_search_api") {
    return ["yandex_cloud"];
  }

  return [];
}

function getPublicConfig(definition: ExternalServiceDefinition, row: ExternalServiceSettingsRow | null) {
  const config = toStringRecord(row?.public_config);

  for (const field of definition.publicFields) {
    if (!config[field.id]) {
      config[field.id] = getFieldDefault(definition.id, field.id);
    }
  }

  return config;
}

function getMissingFields(
  definition: ExternalServiceDefinition,
  enabled: boolean,
  mode: string,
  publicConfig: Record<string, string>,
  secretConfig: Record<string, unknown>,
  dependencyStatuses: Map<ExternalServiceId, ExternalServiceStatus> = new Map()
) {
  if (!enabled || mode === "disabled") {
    return [];
  }

  const missing: string[] = [];

  for (const field of definition.publicFields) {
    if (field.id === "mcpEndpoint" && mode !== "mcp_kv") {
      continue;
    }

    if (field.id !== "mcpEndpoint" && mode === "mcp_kv") {
      continue;
    }

    if (field.required && !publicConfig[field.id]?.trim()) {
      missing.push(field.id);
    }
  }

  for (const dependencyId of getDependsOn(definition.id, mode)) {
    if (dependencyStatuses.get(dependencyId) !== "configured") {
      missing.push(dependencyId);
    }
  }

  if (definition.id === "yandex_cloud" && (mode === "api_key" || mode === "iam_token")) {
    const hasApiKey = typeof secretConfig.apiKey === "string" && secretConfig.apiKey.trim().length > 0;
    const hasIamToken = typeof secretConfig.iamToken === "string" && secretConfig.iamToken.trim().length > 0;

    if (mode === "api_key" && !hasApiKey) {
      missing.push("apiKey");
    }

    if (mode === "iam_token" && !hasIamToken) {
      missing.push("iamToken");
    }
  } else {
    for (const field of definition.secretFields) {
      const value = secretConfig[field.id];

      if (field.required && !(typeof value === "string" && value.trim().length > 0)) {
        missing.push(field.id);
      }
    }
  }

  return missing;
}

function getStatus(
  definition: ExternalServiceDefinition,
  enabled: boolean,
  mode: string,
  missingFields: string[]
): ExternalServiceStatus {
  if (!enabled || mode === "disabled") {
    return "disabled";
  }

  if (definition.implementationStatus === "planned") {
    return "not_implemented";
  }

  return missingFields.length === 0 ? "configured" : "missing_required";
}

function mapRow(
  definition: ExternalServiceDefinition,
  row: ExternalServiceSettingsRow | null,
  dependencyStatuses: Map<ExternalServiceId, ExternalServiceStatus> = new Map()
) {
  const enabled = row?.enabled ?? false;
  const mode = row?.mode ?? "disabled";
  const publicConfig = getPublicConfig(definition, row);
  const secretConfig = row?.secret_config ?? {};
  const secrets = Object.fromEntries(
    definition.secretFields.map((field) => [
      field.id,
      {
        configured: (() => {
          const value = secretConfig[field.id];

          return typeof value === "string" && value.trim().length > 0;
        })()
      }
    ])
  );
  const missingFields = getMissingFields(definition, enabled, mode, publicConfig, secretConfig, dependencyStatuses);

  return {
    id: definition.id,
    label: definition.label,
    description: definition.description,
    implementationStatus: definition.implementationStatus,
    status: getStatus(definition, enabled, mode, missingFields),
    enabled,
    mode,
    modes: definition.modes,
    dependsOn: getDependsOn(definition.id, mode),
    docs: definition.docs,
    publicFields: definition.publicFields,
    secretFields: definition.secretFields,
    publicConfig,
    secrets,
    missingFields,
    updatedAt: row?.updated_at.toISOString() ?? null
  };
}

async function getSettingsRows() {
  const result = await pool.query<ExternalServiceSettingsRow>(
    `
      select service_id, enabled, mode, public_config, secret_config, created_at, updated_at
      from external_service_settings;
    `
  );

  return new Map(result.rows.map((row) => [row.service_id, row]));
}

function getDependencyStatuses(rows: Map<ExternalServiceId, ExternalServiceSettingsRow>) {
  const cloudDefinition = serviceDefinitions.yandex_cloud;
  const cloudRow = rows.get("yandex_cloud") ?? null;
  const cloudPublicConfig = getPublicConfig(cloudDefinition, cloudRow);
  const cloudSecretConfig = cloudRow?.secret_config ?? {};
  const cloudEnabled = cloudRow?.enabled ?? false;
  const cloudMode = cloudRow?.mode ?? "disabled";
  const cloudMissingFields = getMissingFields(
    cloudDefinition,
    cloudEnabled,
    cloudMode,
    cloudPublicConfig,
    cloudSecretConfig
  );

  return new Map<ExternalServiceId, ExternalServiceStatus>([
    ["yandex_cloud", getStatus(cloudDefinition, cloudEnabled, cloudMode, cloudMissingFields)]
  ]);
}

export async function getExternalServiceSettings(): Promise<ExternalServiceSettings> {
  const rows = await getSettingsRows();
  const dependencyStatuses = getDependencyStatuses(rows);

  return {
    schemaVersion: "external-service-settings.v1",
    services: (Object.keys(serviceDefinitions) as ExternalServiceId[]).map((serviceId) =>
      mapRow(serviceDefinitions[serviceId], rows.get(serviceId) ?? null, dependencyStatuses)
    )
  };
}

function cleanPublicConfig(definition: ExternalServiceDefinition, value: Record<string, unknown> | undefined) {
  const allowedFields = new Set(definition.publicFields.map((field) => field.id));
  const output: Record<string, string> = {};

  for (const [key, item] of Object.entries(value ?? {})) {
    if (!allowedFields.has(key) || item === null || item === undefined) {
      continue;
    }

    output[key] = String(item).trim();
  }

  return output;
}

function mergeSecrets(
  definition: ExternalServiceDefinition,
  currentSecrets: Record<string, unknown>,
  incomingSecrets: Record<string, unknown> | undefined
) {
  const allowedFields = new Set(definition.secretFields.map((field) => field.id));
  const output: Record<string, string> = {};

  for (const [key, item] of Object.entries(currentSecrets)) {
    if (allowedFields.has(key) && typeof item === "string" && item.trim()) {
      output[key] = item;
    }
  }

  for (const [key, item] of Object.entries(incomingSecrets ?? {})) {
    if (!allowedFields.has(key)) {
      continue;
    }

    const text = String(item ?? "").trim();

    if (text) {
      output[key] = text;
    }
  }

  return output;
}

export async function updateExternalServiceSettings(
  serviceId: ExternalServiceId,
  input: UpdateExternalServiceSettingsInput
) {
  const definition = serviceDefinitions[serviceId];
  const existing = (
    await pool.query<ExternalServiceSettingsRow>(
      `
        select service_id, enabled, mode, public_config, secret_config, created_at, updated_at
        from external_service_settings
        where service_id = $1;
      `,
      [serviceId]
    )
  ).rows[0];
  const mode = definition.modes.some((item) => item.id === input.mode) ? input.mode : definition.defaultMode;
  const publicConfig = cleanPublicConfig(definition, input.publicConfig);
  const secretConfig = input.enabled && mode !== "disabled" ? mergeSecrets(definition, existing?.secret_config ?? {}, input.secrets) : {};
  const result = await pool.query<ExternalServiceSettingsRow>(
    `
      insert into external_service_settings (service_id, enabled, mode, public_config, secret_config, updated_at)
      values ($1, $2, $3, $4, $5, now())
      on conflict (service_id) do update
      set enabled = excluded.enabled,
        mode = excluded.mode,
        public_config = excluded.public_config,
        secret_config = excluded.secret_config,
        updated_at = now()
      returning service_id, enabled, mode, public_config, secret_config, created_at, updated_at;
    `,
    [serviceId, input.enabled, mode, publicConfig, secretConfig]
  );
  const row = result.rows[0];

  if (!row) {
    throw new Error("Не удалось сохранить настройки внешнего сервиса.");
  }

  const rows = await getSettingsRows();
  const dependencyStatuses = getDependencyStatuses(rows);

  return mapRow(definition, row, dependencyStatuses);
}

function splitCsv(value: string | undefined, fallback: string[]) {
  const values = (value ?? "")
    .split(",")
    .map((item) => item.trim())
    .filter(Boolean);

  return values.length > 0 ? values : fallback;
}

function getNumber(value: string | undefined, fallback: number) {
  const parsed = Number(value);

  return Number.isFinite(parsed) && parsed > 0 ? Math.round(parsed) : fallback;
}

export async function getWordstatRuntimeConfig(): Promise<WordstatRuntimeConfig> {
  const rows = await getSettingsRows();
  const row = rows.get("yandex_wordstat");
  const cloudRow = rows.get("yandex_cloud");
  const definition = serviceDefinitions.yandex_wordstat;
  const dependencyStatuses = getDependencyStatuses(rows);
  const mapped = mapRow(definition, row ?? null, dependencyStatuses);

  if (row?.enabled && row.mode === "mcp_kv") {
    const endpoint = mapped.publicConfig.mcpEndpoint?.trim();

    if (endpoint) {
      return {
        provider: "mcp_kv",
        mode: "mcp_kv",
        mcpEndpoint: endpoint
      };
    }
  }

  if (row?.enabled && row.mode === "yandex_search_api" && mapped.status === "configured") {
    const cloudPublicConfig = getPublicConfig(serviceDefinitions.yandex_cloud, cloudRow ?? null);
    const cloudSecretConfig = cloudRow?.secret_config ?? {};
    const apiKey = typeof cloudSecretConfig.apiKey === "string" ? cloudSecretConfig.apiKey.trim() : "";
    const iamToken = typeof cloudSecretConfig.iamToken === "string" ? cloudSecretConfig.iamToken.trim() : "";

    return {
      provider: "yandex_api",
      mode: "yandex_search_api",
      apiBaseUrl: mapped.publicConfig.apiBaseUrl || WORDSTAT_BASE_URL,
      apiToken: apiKey || iamToken,
      authType: apiKey ? "api_key" : "iam_token",
      folderId: cloudPublicConfig.folderId,
      regionIds: splitCsv(mapped.publicConfig.regionIds, ["225"]),
      devices: splitCsv(mapped.publicConfig.devices, ["DEVICE_ALL"]),
      numPhrases: getNumber(mapped.publicConfig.numPhrases, 50)
    };
  }

  if (env.marketProviders.wordstat.provider === "mcp_kv" && env.marketProviders.wordstat.mcpEndpoint) {
    return {
      provider: "mcp_kv",
      mode: "mcp_kv",
      mcpEndpoint: env.marketProviders.wordstat.mcpEndpoint
    };
  }

  if (
    env.marketProviders.wordstat.provider === "yandex_api" &&
    env.marketProviders.wordstat.apiBaseUrl &&
    env.marketProviders.wordstat.apiToken &&
    env.marketProviders.wordstat.folderId
  ) {
    return {
      provider: "yandex_api",
      mode: "yandex_search_api",
      apiBaseUrl: env.marketProviders.wordstat.apiBaseUrl,
      apiToken: env.marketProviders.wordstat.apiToken,
      authType: env.marketProviders.wordstat.authType,
      folderId: env.marketProviders.wordstat.folderId,
      regionIds: env.marketProviders.wordstat.regionIds,
      devices: env.marketProviders.wordstat.devices,
      numPhrases: env.marketProviders.wordstat.numPhrases
    };
  }

  return {
    provider: "disabled",
    mode: "disabled"
  };
}
