import fs from "node:fs/promises";
import { constants as fsConstants } from "node:fs";
import path from "node:path";
import { createRequire } from "node:module";

type OpenClawLikeConfig = Record<string, unknown>;

type PluginLogger = {
  info: (message: string) => void;
  warn: (message: string) => void;
  error: (message: string) => void;
};

type OpenClawPluginHttpRouteParams = {
  path: string;
  auth: "gateway" | "plugin";
  match?: "exact" | "prefix";
  replaceExisting?: boolean;
  handler: (
    req: import("node:http").IncomingMessage,
    res: import("node:http").ServerResponse,
  ) => Promise<boolean | void> | boolean | void;
};

type RawBrandingConfig = {
  enabled?: boolean;
  appName?: string;
  browserTitle?: string;
  sidebarTitle?: string;
  sidebarEyebrow?: string;
  logoUrl?: string;
  faviconUrl?: string;
  topbarVideoUrl?: string;
  startupVideoUrl?: string;
  accentColor?: string;
  accentHover?: string;
  accentGlow?: string;
  fontBody?: string;
  fontDisplay?: string;
  customCss?: string;
  replaceWordmark?: boolean;
};

export type BrandingConfig = Required<RawBrandingConfig>;

export type BrandingContext = {
  logger: PluginLogger;
  pluginRoot: string;
  config: OpenClawLikeConfig;
  runtimeConfig: BrandingConfig;
};

export type BrandingServiceContext = BrandingContext & {
  stateDir?: string;
};

const BRAND_INJECT_START = "<!-- ui-whatsynaptor-branding:start -->";
const BRAND_INJECT_END = "<!-- ui-whatsynaptor-branding:end -->";
const LEGACY_RUNTIME_START = "<!-- ui-whatsynaptor-runtime:start -->";
const LEGACY_RUNTIME_END = "<!-- ui-whatsynaptor-runtime:end -->";
const LEGACY_RUNTIME_CONFIG_FILE = "ui-whatsynaptor-runtime-config.js";
const LEGACY_RUNTIME_SCRIPT_FILE = "ui-whatsynaptor-runtime.js";

export function resolveBrandingConfig(raw: unknown): BrandingConfig {
  const cfg = raw && typeof raw === "object" ? (raw as RawBrandingConfig) : {};
  return {
    enabled: cfg.enabled ?? true,
    appName: cfg.appName?.trim() || "OpenClaw Custom",
    browserTitle: cfg.browserTitle?.trim() || "OpenClaw Custom Control",
    sidebarTitle: cfg.sidebarTitle?.trim() || cfg.appName?.trim() || "OpenClaw Custom",
    sidebarEyebrow: cfg.sidebarEyebrow?.trim() || "Control",
    logoUrl: cfg.logoUrl?.trim() || "",
    faviconUrl: cfg.faviconUrl?.trim() || "",
    topbarVideoUrl: cfg.topbarVideoUrl?.trim() || "",
    startupVideoUrl: cfg.startupVideoUrl?.trim() || "",
    accentColor: cfg.accentColor?.trim() || "#3d8f6e",
    accentHover: cfg.accentHover?.trim() || "#4aaa84",
    accentGlow: cfg.accentGlow?.trim() || "rgba(61, 143, 110, 0.28)",
    fontBody:
      cfg.fontBody?.trim() ||
      "\"IBM Plex Sans\", \"Segoe UI\", -apple-system, BlinkMacSystemFont, sans-serif",
    fontDisplay:
      cfg.fontDisplay?.trim() || "\"Space Grotesk\", \"IBM Plex Sans\", sans-serif",
    customCss: cfg.customCss ?? "",
    replaceWordmark: cfg.replaceWordmark ?? false,
  };
}

export function createBrandingRoutes(ctx: BrandingContext): OpenClawPluginHttpRouteParams[] {
  const { config, logger, pluginRoot, runtimeConfig } = ctx;
  const basePaths = getBasePathVariants(config);
  const logicalAssets = [
    { logicalPath: "/plugins/ui-whatsynaptor/assets/brand.css", filePath: path.join(pluginRoot, "assets/brand.css") },
    { logicalPath: "/plugins/ui-whatsynaptor/assets/brand.js", filePath: path.join(pluginRoot, "assets/brand.js") },
    { logicalPath: "/plugins/ui-whatsynaptor/assets/logo.svg", filePath: path.join(pluginRoot, "assets/logo.svg") },
    {
      logicalPath: "/plugins/ui-whatsynaptor/assets/pulse-minimal.svg",
      filePath: path.join(pluginRoot, "assets/Pulse Minimal.svg"),
    },
    {
      logicalPath: "/plugins/ui-whatsynaptor/assets/brand-logo-uBqNKeuY.gif",
      filePath: path.join(pluginRoot, "assets/brand-logo-uBqNKeuY.gif"),
    },
    {
      logicalPath: "/plugins/ui-whatsynaptor/assets/topbar-stream-bg.mp4",
      filePath: path.join(pluginRoot, "assets/topbar-stream-bg.mp4"),
    },
    {
      logicalPath: "/plugins/ui-whatsynaptor/assets/startup.mp4",
      filePath: path.join(pluginRoot, "assets/startup.mp4"),
    },
    {
      logicalPath: "/media/assets/startup.mp4",
      filePath: path.join(pluginRoot, "assets/startup.mp4"),
    },
  ];
  const routes: OpenClawPluginHttpRouteParams[] = [];

  for (const asset of logicalAssets) {
    for (const basePath of basePaths) {
      const routePath = joinUrlPath(basePath, asset.logicalPath);
      routes.push({
        path: routePath,
        auth: "plugin",
        match: "exact",
        handler: async (_req, res) => {
          try {
            const body = await fs.readFile(
              asset.filePath,
              asset.filePath.endsWith(".gif") || asset.filePath.endsWith(".mp4") ? undefined : "utf8",
            );
            if (asset.logicalPath.endsWith("/brand.css")) {
              let cssBody = String(body);
              cssBody += `\n:root {\n  --ui-brand-accent: ${runtimeConfig.accentColor};\n  --ui-brand-accent-hover: ${runtimeConfig.accentHover};\n  --ui-brand-accent-glow: ${runtimeConfig.accentGlow};\n  --ui-brand-font-body: ${runtimeConfig.fontBody};\n  --ui-brand-font-display: ${runtimeConfig.fontDisplay};\n}\n`;
              if (runtimeConfig.customCss.trim()) {
                cssBody += `\n/* ui-patcher customCss */\n${runtimeConfig.customCss}\n`;
              }
              res.statusCode = 200;
              res.setHeader("Cache-Control", "no-cache");
              res.setHeader("Content-Type", guessContentType(asset.filePath));
              res.end(cssBody);
              return true;
            }
            res.statusCode = 200;
            res.setHeader("Cache-Control", "no-cache");
            res.setHeader("Content-Type", guessContentType(asset.filePath));
            res.end(body);
            return true;
          } catch (error) {
            logger.error(`[ui-whatsynaptor] Failed to serve ${routePath}: ${String(error)}`);
            res.statusCode = 500;
            res.end("ui-whatsynaptor asset error");
            return true;
          }
        },
      });
    }
  }

  return routes;
}

export async function applyBrandingInjection(ctx: BrandingServiceContext): Promise<void> {
  const { logger, pluginRoot, config, runtimeConfig } = ctx;
  const repoRoot = await detectOpenClawRoot(pluginRoot);
  if (!repoRoot) {
    logger.warn("[ui-whatsynaptor] Could not detect OpenClaw install root with dist/control-ui.");
    return;
  }

  const indexPath = path.join(repoRoot, "dist/control-ui/index.html");
  if (!(await pathExists(indexPath))) {
    logger.warn(`[ui-whatsynaptor] Control UI index not found: ${indexPath}`);
    return;
  }

  const originalHtml = await fs.readFile(indexPath, "utf8");
  let html = originalHtml;
  html = stripBrandingBlock(html);
  html = stripLegacyRuntimeBlock(html);

  if (!runtimeConfig.enabled) {
    if (html === originalHtml) {
      logger.info("[ui-whatsynaptor] Branding disabled; no injector block present.");
      return;
    }
    if (!(await canWriteFile(indexPath))) {
      warnControlUiReadOnly(logger, indexPath, "remove the branding injector block");
      return;
    }
    await fs.writeFile(indexPath, html, "utf8");
    logger.info("[ui-whatsynaptor] Branding disabled; removed branding injector block.");
    return;
  }

  const preferredBasePath = normalizeBasePath(readControlUiBasePath(config));
  const cssUrl = joinUrlPath(preferredBasePath, "/plugins/ui-whatsynaptor/assets/brand.css");
  const jsUrl = joinUrlPath(preferredBasePath, "/plugins/ui-whatsynaptor/assets/brand.js");
  const defaultLogoUrl = joinUrlPath(preferredBasePath, "/plugins/ui-whatsynaptor/assets/logo.svg");
  const logoUrl = runtimeConfig.logoUrl || defaultLogoUrl;
  const faviconUrl = runtimeConfig.faviconUrl || logoUrl;
  const configPayload = {
    appName: runtimeConfig.appName,
    browserTitle: runtimeConfig.browserTitle,
    sidebarTitle: runtimeConfig.sidebarTitle,
    sidebarEyebrow: runtimeConfig.sidebarEyebrow,
    logoUrl,
    faviconUrl,
    topbarVideoUrl: runtimeConfig.topbarVideoUrl,
    startupVideoUrl: runtimeConfig.startupVideoUrl,
    replaceWordmark: runtimeConfig.replaceWordmark,
  };
  const injectBlock = [
    BRAND_INJECT_START,
    `    <script>window.__UI_PATCHER_BRAND__ = ${JSON.stringify(configPayload)};</script>`,
    `    <link rel="stylesheet" href="${escapeHtmlAttribute(cssUrl)}" />`,
    `    <script type="module" src="${escapeHtmlAttribute(jsUrl)}"></script>`,
    BRAND_INJECT_END,
  ].join("\n");

  if (html.includes("</head>")) {
    html = html.replace("</head>", `${injectBlock}\n  </head>`);
  } else {
    html += `\n${injectBlock}\n`;
  }

  if (html === originalHtml) {
    logger.info("[ui-whatsynaptor] Branding injector block already current.");
    return;
  }
  if (!(await canWriteFile(indexPath))) {
    warnControlUiReadOnly(logger, indexPath, "inject branding assets");
    return;
  }

  await fs.writeFile(indexPath, html, "utf8");
  await removeLegacyRuntimeFiles(path.dirname(indexPath), logger);
  logger.info("[ui-whatsynaptor] Injected non-destructive Control UI branding assets.");
}

export async function removeBrandingInjection(ctx: BrandingContext): Promise<void> {
  const { logger, pluginRoot } = ctx;
  const repoRoot = await detectOpenClawRoot(pluginRoot);
  if (!repoRoot) {
    return;
  }

  const indexPath = path.join(repoRoot, "dist/control-ui/index.html");
  if (!(await pathExists(indexPath))) {
    return;
  }

  let html = await fs.readFile(indexPath, "utf8");
  const cleaned = stripLegacyRuntimeBlock(stripBrandingBlock(html));
  if (cleaned !== html) {
    if (!(await canWriteFile(indexPath))) {
      warnControlUiReadOnly(logger, indexPath, "remove the branding injector block");
      return;
    }
    await fs.writeFile(indexPath, cleaned, "utf8");
    logger.info("[ui-whatsynaptor] Removed branding injector block from Control UI index.");
  }
  await removeLegacyRuntimeFiles(path.dirname(indexPath), logger);
}

async function detectOpenClawRoot(pluginRoot: string): Promise<string | null> {
  const candidates = new Set<string>();

  candidates.add(path.resolve(pluginRoot, "../.."));
  candidates.add("/opt/openclaw/app");
  candidates.add("/usr/lib/node_modules/openclaw");
  candidates.add("/usr/local/lib/node_modules/openclaw");
  // Support custom N_PREFIX / nvm / home-based installs
  if (process.env.N_PREFIX) candidates.add(path.join(process.env.N_PREFIX, "lib/node_modules/openclaw"));
  const homeDir = process.env.HOME || process.env.USERPROFILE || "";
  if (homeDir) {
    candidates.add(path.join(homeDir, "n/lib/node_modules/openclaw"));
    candidates.add(path.join(homeDir, ".nvm/versions/node", process.version, "lib/node_modules/openclaw"));
  }

  try {
    const require = createRequire(import.meta.url);
    const pkgPath = require.resolve("openclaw/package.json");
    candidates.add(path.dirname(pkgPath));
  } catch {
    // no-op
  }

  for (const root of candidates) {
    if (await pathExists(path.join(root, "dist/control-ui/index.html"))) {
      return root;
    }
  }

  return null;
}

async function removeLegacyRuntimeFiles(targetDir: string, logger: PluginLogger): Promise<void> {
  for (const fileName of [LEGACY_RUNTIME_CONFIG_FILE, LEGACY_RUNTIME_SCRIPT_FILE]) {
    const filePath = path.join(targetDir, fileName);
    if (await pathExists(filePath)) {
      try {
        await fs.rm(filePath, { force: true });
        logger.info(`[ui-whatsynaptor] Removed legacy runtime file ${fileName}.`);
      } catch (error) {
        logger.warn(`[ui-whatsynaptor] Could not remove legacy runtime file ${filePath}: ${String(error)}`);
      }
    }
  }
}

function stripBrandingBlock(html: string): string {
  return html.replace(new RegExp(`${escapeRegex(BRAND_INJECT_START)}[\\s\\S]*?${escapeRegex(BRAND_INJECT_END)}\\n?`, "g"), "");
}

function stripLegacyRuntimeBlock(html: string): string {
  return html.replace(new RegExp(`${escapeRegex(LEGACY_RUNTIME_START)}[\\s\\S]*?${escapeRegex(LEGACY_RUNTIME_END)}\\n?`, "g"), "");
}

function escapeRegex(value: string): string {
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function readControlUiBasePath(config: OpenClawLikeConfig): string {
  const maybe = (config as Record<string, unknown>).gateway as Record<string, unknown> | undefined;
  const controlUi = maybe?.controlUi as Record<string, unknown> | undefined;
  return typeof controlUi?.basePath === "string" ? controlUi.basePath : "";
}

function getBasePathVariants(config: OpenClawLikeConfig): string[] {
  const normalized = normalizeBasePath(readControlUiBasePath(config));
  return normalized ? ["", normalized] : [""];
}

function normalizeBasePath(input: string): string {
  const trimmed = input.trim();
  if (!trimmed || trimmed === "/") {
    return "";
  }
  const withLeading = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
  return withLeading.replace(/\/+$/, "");
}

function joinUrlPath(basePath: string, routePath: string): string {
  const normalizedBase = normalizeBasePath(basePath);
  const normalizedRoute = routePath.startsWith("/") ? routePath : `/${routePath}`;
  return `${normalizedBase}${normalizedRoute}`;
}

function guessContentType(filePath: string): string {
  if (filePath.endsWith(".css")) return "text/css; charset=utf-8";
  if (filePath.endsWith(".js")) return "text/javascript; charset=utf-8";
  if (filePath.endsWith(".svg")) return "image/svg+xml";
  if (filePath.endsWith(".gif")) return "image/gif";
  if (filePath.endsWith(".mp4")) return "video/mp4";
  return "application/octet-stream";
}

function escapeHtmlAttribute(value: string): string {
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
}

async function pathExists(filePath: string): Promise<boolean> {
  try {
    await fs.access(filePath);
    return true;
  } catch {
    return false;
  }
}

async function canWriteFile(filePath: string): Promise<boolean> {
  try {
    await fs.access(filePath, fsConstants.W_OK);
    return true;
  } catch {
    return false;
  }
}

function warnControlUiReadOnly(logger: PluginLogger, indexPath: string, action: string): void {
  logger.warn(
    `[ui-whatsynaptor] Cannot ${action}; Control UI index is not writable by this process: ${indexPath}. ` +
      "The plugin will continue without mutating the global OpenClaw install.",
  );
}
