import dotenv from "dotenv";
import fs from "node:fs";
import path from "node:path";
import webpush from "web-push";
import { z } from "zod";

dotenv.config();

const isMissingOrPlaceholder = (value: string | undefined) =>
  !value || value.trim().length === 0 || value.startsWith("replace_");

const isValidVapidConfig = (publicKey: string | undefined, privateKey: string | undefined, subject: string | undefined) => {
  if (isMissingOrPlaceholder(publicKey) || isMissingOrPlaceholder(privateKey)) {
    return false;
  }

  const resolvedSubject = isMissingOrPlaceholder(subject) ? "mailto:admin@example.com" : subject!;

  try {
    webpush.setVapidDetails(resolvedSubject, publicKey!, privateKey!);
    return true;
  } catch {
    return false;
  }
};

const parseEnvFile = (content: string) => {
  const entries = new Map<string, string>();
  for (const line of content.split(/\r?\n/)) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith("#")) continue;
    const separatorIndex = trimmed.indexOf("=");
    if (separatorIndex === -1) continue;
    const key = trimmed.slice(0, separatorIndex).trim();
    const value = trimmed.slice(separatorIndex + 1).trim();
    if (key) entries.set(key, value);
  }
  return entries;
};

const ensureVapidConfig = () => {
  const stateDir = process.env.VAPID_STATE_DIR?.trim() || path.join(process.cwd(), ".runtime");
  const stateFile = path.join(stateDir, "vapid.env");

  const currentPublic = process.env.VAPID_PUBLIC_KEY;
  const currentPrivate = process.env.VAPID_PRIVATE_KEY;
  const currentSubject = process.env.VAPID_SUBJECT;

  const readState = () => {
    try {
      if (!fs.existsSync(stateFile)) return null;
      return parseEnvFile(fs.readFileSync(stateFile, "utf8"));
    } catch {
      return null;
    }
  };

  const saveState = (publicKey: string, privateKey: string, subject: string) => {
    try {
      fs.mkdirSync(stateDir, { recursive: true });
      const payload = [
        `VAPID_PUBLIC_KEY=${publicKey}`,
        `VAPID_PRIVATE_KEY=${privateKey}`,
        `VAPID_SUBJECT=${subject}`
      ].join("\n");
      fs.writeFileSync(stateFile, `${payload}\n`, "utf8");
    } catch {
      // Ignore write errors and continue with in-memory env values.
    }
  };

  if (isValidVapidConfig(currentPublic, currentPrivate, currentSubject)) {
    const subject = isMissingOrPlaceholder(currentSubject) ? "mailto:admin@example.com" : currentSubject!;
    if (subject !== currentSubject) {
      process.env.VAPID_SUBJECT = subject;
    }
    saveState(currentPublic!, currentPrivate!, subject);
    return;
  }

  const state = readState();
  const statePublic = state?.get("VAPID_PUBLIC_KEY");
  const statePrivate = state?.get("VAPID_PRIVATE_KEY");
  const stateSubject = state?.get("VAPID_SUBJECT");

  if (isValidVapidConfig(statePublic, statePrivate, stateSubject || currentSubject)) {
    process.env.VAPID_PUBLIC_KEY = statePublic;
    process.env.VAPID_PRIVATE_KEY = statePrivate;
    process.env.VAPID_SUBJECT =
      !isMissingOrPlaceholder(currentSubject) ? currentSubject : stateSubject || "mailto:admin@example.com";
    return;
  }

  const generated = webpush.generateVAPIDKeys();
  const subject =
    !isMissingOrPlaceholder(currentSubject) && currentSubject ? currentSubject : "mailto:admin@example.com";

  process.env.VAPID_PUBLIC_KEY = generated.publicKey;
  process.env.VAPID_PRIVATE_KEY = generated.privateKey;
  process.env.VAPID_SUBJECT = subject;

  saveState(generated.publicKey, generated.privateKey, subject);
  console.warn(`Generated new VAPID keys and saved them to ${stateFile}`);
};

ensureVapidConfig();

const stringWithDefault = (fallback: string) =>
  z.preprocess((value) => {
    if (typeof value !== "string") return value;
    const trimmed = value.trim();
    return trimmed.length === 0 ? undefined : trimmed;
  }, z.string().default(fallback));

const trimmedString = () =>
  z.preprocess((value) => {
    if (typeof value !== "string") return value;
    return value.trim();
  }, z.string());

const buildCorsOrigins = (rawCorsOrigin: string, appPublicUrl: string) => {
  const defaults = [
    appPublicUrl,
    "https://antigol.ru",
    "http://localhost:3000",
    "http://localhost:5173",
    "http://localhost",
    "https://localhost"
  ];

  const configured = rawCorsOrigin
    .split(",")
    .map((origin) => origin.trim())
    .filter(Boolean);

  return Array.from(new Set([...configured, ...defaults]));
};

const parseBotNameAliasesObject = (parsed: unknown) => {
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
    return new Map<string, string>();
  }

  return new Map(
    Object.entries(parsed)
      .filter((entry): entry is [string, string] => {
        const [key, value] = entry;
        return key.trim().length > 0 && typeof value === "string" && value.trim().length > 0;
      })
      .map(([key, value]) => [key.trim(), value.trim()])
  );
};

const loadBotNameAliases = (aliasesFilePath: string) => {
  try {
    const content = fs.readFileSync(aliasesFilePath, "utf8");
    return parseBotNameAliasesObject(JSON.parse(content) as unknown);
  } catch (error) {
    const nodeError = error as NodeJS.ErrnoException;
    if (nodeError.code !== "ENOENT") {
      console.warn(`Failed to load bot aliases from ${aliasesFilePath}: ${nodeError.message}`);
    }
    return new Map<string, string>();
  }
};

const envSchema = z.object({
  PORT: z.coerce.number().default(4000),
  REDIS_URL: stringWithDefault("redis://127.0.0.1:6379"),
  DATABASE_URL: trimmedString().pipe(z.string().min(1)),
  JWT_SECRET: trimmedString().pipe(z.string().min(8)),
  JWT_EXPIRES_IN: stringWithDefault("7d"),
  CORS_ORIGIN: stringWithDefault(""),
  APP_PUBLIC_URL: stringWithDefault("https://antigol.ru").pipe(z.string().url()),
  SMTP_HOST: stringWithDefault(""),
  SMTP_PORT: z.coerce.number().default(587),
  SMTP_SECURE: z
    .union([z.boolean(), z.string()])
    .transform((value) => {
      if (typeof value === "boolean") return value;
      const normalized = value.trim().toLowerCase();
      return normalized === "true" || normalized === "1" || normalized === "yes";
    })
    .default(false),
  SMTP_USER: stringWithDefault(""),
  SMTP_PASSWORD: stringWithDefault(""),
  SMTP_FROM_EMAIL: stringWithDefault(""),
  SMTP_FROM_NAME: stringWithDefault("Alpinbet"),
  PASSWORD_RESET_TTL_MINUTES: z.coerce.number().default(60),
  APP_LATEST_VERSION: stringWithDefault(""),
  APP_MIN_SUPPORTED_VERSION: stringWithDefault(""),
  APP_UPDATE_URL: stringWithDefault(""),
  BOT_NAME_ALIASES_FILE: stringWithDefault("./config/bot-name-aliases.json"),
  APP_UPDATE_MESSAGE: z.string().default("Доступна новая версия приложения"),
  VAPID_PUBLIC_KEY: stringWithDefault(""),
  VAPID_PRIVATE_KEY: stringWithDefault(""),
  VAPID_SUBJECT: stringWithDefault("mailto:admin@example.com"),
  FIREBASE_PROJECT_ID: stringWithDefault(""),
  FIREBASE_CLIENT_EMAIL: stringWithDefault(""),
  FIREBASE_PRIVATE_KEY: stringWithDefault(""),
  FIREBASE_SERVICE_ACCOUNT_JSON: stringWithDefault(""),
  SETTLEMENT_INTERVAL_MS: z.coerce.number().default(60000),
  SIGNALS_WORKER_CONCURRENCY: z.coerce.number().default(1),
  PUSH_WORKER_CONCURRENCY: z.coerce.number().default(1),
  PARSER_INTERNAL_SECRET: trimmedString().pipe(z.string().min(16))
});

export const env = envSchema.parse(process.env);
export const corsOrigins = buildCorsOrigins(env.CORS_ORIGIN, env.APP_PUBLIC_URL);
export const botNameAliases = loadBotNameAliases(path.resolve(process.cwd(), env.BOT_NAME_ALIASES_FILE));
