import { spawn } from "child_process";
import { promises as fs } from "fs";
import path from "path";
import { getVideoConfig } from "@/lib/generation/video-config";
import { getLocalMediaPath, resolveLocalMediaPath } from "@/lib/storage/media-files";
import { isFfmpegAvailable } from "@/lib/generation/ffmpeg-video";
import { rasterizeSvgToPng } from "@/lib/generation/image-rasterize";
import type { LogoPlacementId } from "@/lib/generation/scene-planner";

/** Data-URL size budget (base64 expands ~33% vs binary). */
const MAX_LOGO_BYTES = 2_500_000;
/** Fit logo inside this box — aspect ratio preserved (never stretch). */
const LOGO_FIT_W = 280;
const LOGO_FIT_H = 96;
const ZONE_W = 320;
const ZONE_H = 112;
const MARGIN_Y = 22;
const MARGIN_X = 26;

export interface LogoZoneAnalysis {
  placement: LogoPlacementId;
  x: number;
  y: number;
  /** 0–255 average luminance of the logo zone after generation. */
  luminance: number;
  /** dark | light — drives contrast plate behind the exact logo. */
  surface: "dark" | "light";
  /** Soft plate color for readability (never a harsh sticker). */
  plateColor: string;
  plateOpacity: number;
}

function zoneOrigin(placement: LogoPlacementId): { x: number; y: number } {
  const y = MARGIN_Y;
  if (placement === "top_center") return { x: Math.round((1080 - ZONE_W) / 2), y };
  if (placement === "top_right") return { x: 1080 - ZONE_W - MARGIN_X, y };
  return { x: MARGIN_X, y };
}

function toFfmpegColor(hexOrCss: string | null | undefined, fallback = "0x0B1F3A"): string {
  const raw = (hexOrCss ?? "").trim();
  const hex = raw.match(/^#?([0-9a-fA-F]{6})$/);
  if (hex?.[1]) return `0x${hex[1].toUpperCase()}`;
  const short = raw.match(/^#?([0-9a-fA-F]{3})$/);
  if (short?.[1]) {
    const s = short[1];
    return `0x${s[0]}${s[0]}${s[1]}${s[1]}${s[2]}${s[2]}`.toUpperCase();
  }
  return fallback;
}

/**
 * After the banner is generated, sample the logo zone and decide plate/contrast.
 * We already have the exact uploaded logo — analysis only adjusts visibility.
 */
async function analyzeLogoZone(
  imagePath: string,
  placement: LogoPlacementId,
): Promise<LogoZoneAnalysis> {
  const cfg = getVideoConfig();
  const { x, y } = zoneOrigin(placement);

  // Sample a small grid inside the stamp zone (not a single edge pixel).
  const points = [
    [x + 20, y + 20],
    [x + ZONE_W / 2, y + ZONE_H / 2],
    [x + ZONE_W - 20, y + 20],
    [x + 20, y + ZONE_H - 20],
    [x + ZONE_W - 20, y + ZONE_H - 20],
  ] as const;

  const samples: Array<{ r: number; g: number; b: number }> = [];

  for (const [sx, sy] of points) {
    const rgb = await samplePixel(cfg.ffmpegPath, imagePath, Math.round(sx), Math.round(sy));
    if (rgb) samples.push(rgb);
  }

  let luminance = 40;
  if (samples.length) {
    const avg =
      samples.reduce((acc, p) => acc + (0.299 * p.r + 0.587 * p.g + 0.114 * p.b), 0) /
      samples.length;
    luminance = Math.round(avg);
  }

  // Dark zone → soft light plate so logo reads; light zone → soft dark plate or minimal.
  const surface: "dark" | "light" = luminance < 140 ? "dark" : "light";
  const plateColor = surface === "dark" ? "white@0.92" : "black@0.18";
  const plateOpacity = surface === "dark" ? 0.92 : 0.18;

  return {
    placement,
    x,
    y,
    luminance,
    surface,
    plateColor,
    plateOpacity,
  };
}

function samplePixel(
  ffmpegPath: string,
  imagePath: string,
  sx: number,
  sy: number,
): Promise<{ r: number; g: number; b: number } | null> {
  const x = Math.max(0, Math.min(1079, sx));
  const y = Math.max(0, Math.min(1079, sy));
  return new Promise((resolve) => {
    const args = [
      "-v",
      "error",
      "-i",
      imagePath,
      "-vf",
      `crop=1:1:${x}:${y}`,
      "-f",
      "rawvideo",
      "-pix_fmt",
      "rgb24",
      "pipe:1",
    ];
    const proc = spawn(ffmpegPath, args, { stdio: ["ignore", "pipe", "pipe"] });
    const chunks: Buffer[] = [];
    proc.stdout.on("data", (c: Buffer) => chunks.push(c));
    proc.on("error", () => resolve(null));
    proc.on("close", (code) => {
      const buf = Buffer.concat(chunks);
      if (code !== 0 || buf.length < 3) {
        resolve(null);
        return;
      }
      resolve({ r: buf[0]!, g: buf[1]!, b: buf[2]! });
    });
  });
}

async function writeLogoTempFile(logoDataUrl: string, postId: string): Promise<string | null> {
  const match = logoDataUrl.match(/^data:([^;]+);base64,(.+)$/i);
  if (!match?.[2]) return null;

  const mime = (match[1] ?? "image/png").toLowerCase();
  const isSvg = mime.includes("svg");
  const ext = isSvg
    ? "svg"
    : mime.includes("png")
      ? "png"
      : mime.includes("jpeg") || mime.includes("jpg")
        ? "jpg"
        : mime.includes("webp")
          ? "webp"
          : "png";

  try {
    const rawPath = getLocalMediaPath(`${postId}-logo-src.${ext}`);
    await fs.writeFile(rawPath, Buffer.from(match[2], "base64"));

    if (!isSvg) {
      return rawPath;
    }

    const pngPath = getLocalMediaPath(`${postId}-logo.png`);
    const ok = await rasterizeSvgToPng(rawPath, pngPath, { width: 640, height: 240 });
    await fs.unlink(rawPath).catch(() => undefined);
    if (!ok) {
      console.warn("[PostSync] SVG logo could not be rasterized — re-upload as PNG");
      return null;
    }
    return pngPath;
  } catch (err) {
    console.warn("[PostSync] Failed to write logo temp file:", err);
    return null;
  }
}

/**
 * 1) Analyze generated banner logo zone (luminance).
 * 2) Soft contrast plate only when needed for readability.
 * 3) Stamp exact uploaded logo (never AI-drawn).
 */
function runFfmpegLogoCompose(input: {
  imagePath: string;
  logoPath: string;
  outputPath: string;
  analysis: LogoZoneAnalysis;
}): Promise<{ ok: true } | { ok: false; error: string }> {
  const cfg = getVideoConfig();
  const { x, y } = input.analysis;
  const plate = input.analysis.plateColor;

  // Soft rounded plate via drawbox (FFmpeg lacks true rounded rect — soft opacity instead of harsh sticker).
  const filter = [
    `[0:v]drawbox=x=${x}:y=${y}:w=${ZONE_W}:h=${ZONE_H}:color=${plate}:t=fill[plated];`,
    `[1:v]scale=${LOGO_FIT_W}:${LOGO_FIT_H}:force_original_aspect_ratio=decrease,`,
    `format=rgba,`,
    `pad=${ZONE_W}:${ZONE_H}:(ow-iw)/2:(oh-ih)/2:black@0[lg];`,
    `[plated][lg]overlay=${x}:${y}`,
  ].join("");

  return new Promise((resolve) => {
    const args = [
      "-y",
      "-i",
      input.imagePath,
      "-i",
      input.logoPath,
      "-filter_complex",
      filter,
      input.outputPath,
    ];

    const proc = spawn(cfg.ffmpegPath, args, { stdio: ["ignore", "pipe", "pipe"] });
    let stderr = "";

    proc.stderr.on("data", (chunk: Buffer) => {
      stderr += chunk.toString();
    });

    proc.on("error", (err) => {
      resolve({ ok: false, error: err.message });
    });

    proc.on("close", (code) => {
      if (code === 0) resolve({ ok: true });
      else resolve({ ok: false, error: stderr.slice(-400) || `FFmpeg exited ${code}` });
    });
  });
}

/** Composite the uploaded brand logo onto a raster post image (exact asset, no AI invent). */
export async function applyLogoOverlayToImageFile(input: {
  imagePath: string;
  logoDataUrl: string | null;
  postId: string;
  logoPlacement?: LogoPlacementId;
  wipeColor?: string | null;
}): Promise<{ ok: true; path: string } | { ok: false; skipped: boolean; error?: string }> {
  const { imagePath, logoDataUrl, postId, logoPlacement } = input;
  void input.wipeColor;

  if (!logoDataUrl?.trim()) {
    return { ok: false, skipped: true, error: "No logo data URL" };
  }
  if (logoDataUrl.length > MAX_LOGO_BYTES) {
    console.warn(
      `[PostSync] Logo data URL too large (${logoDataUrl.length} bytes) — max ${MAX_LOGO_BYTES}`,
    );
    return { ok: false, skipped: true, error: "Logo file too large" };
  }

  if (imagePath.toLowerCase().endsWith(".svg")) {
    return { ok: false, skipped: true, error: "Banner still SVG — rasterize first" };
  }

  const ffmpegOk = await isFfmpegAvailable();
  if (!ffmpegOk) {
    return { ok: false, skipped: true, error: "FFmpeg not available for logo overlay" };
  }

  const logoPath = await writeLogoTempFile(logoDataUrl, postId);
  if (!logoPath) {
    return { ok: false, skipped: true, error: "Invalid or unsupported logo (use PNG/JPG)" };
  }

  const ext = path.extname(imagePath).toLowerCase();
  const outExt = ext === ".png" ? ".png" : ".jpg";
  const tempOut = getLocalMediaPath(`${postId}-branded${outExt}`);
  const placement = logoPlacement ?? "top_right";

  try {
    const analysis = await analyzeLogoZone(imagePath, placement);
    console.info(
      `[PostSync] Logo zone analysis: surface=${analysis.surface} luminance=${analysis.luminance} plate=${analysis.plateColor}`,
    );

    const result = await runFfmpegLogoCompose({
      imagePath,
      logoPath,
      outputPath: tempOut,
      analysis,
    });

    if (!result.ok) {
      console.warn("[PostSync] Logo overlay FFmpeg failed:", result.error);
      return { ok: false, skipped: false, error: result.error };
    }

    await fs.copyFile(tempOut, imagePath);
    await fs.unlink(tempOut).catch(() => undefined);
    return { ok: true, path: imagePath };
  } finally {
    await fs.unlink(logoPath).catch(() => undefined);
  }
}

/** Apply logo to a persisted /api/media/... image URL. Returns same URL on success. */
export async function applyLogoToPersistedImage(input: {
  postId: string;
  imageApiUrl: string | null;
  logoDataUrl: string | null;
  logoPlacement?: LogoPlacementId;
  wipeColor?: string | null;
}): Promise<string | null> {
  if (!input.imageApiUrl) return null;

  const imagePath = resolveLocalMediaPath(input.imageApiUrl);
  if (!imagePath) {
    console.warn("[PostSync] Logo overlay skipped — could not resolve media path");
    return input.imageApiUrl;
  }

  const result = await applyLogoOverlayToImageFile({
    imagePath,
    logoDataUrl: input.logoDataUrl,
    postId: input.postId,
    logoPlacement: input.logoPlacement ?? "top_right",
    wipeColor: input.wipeColor,
  });

  if (!result.ok) {
    if (result.skipped) {
      console.warn("[PostSync] Logo overlay skipped:", result.error || "unknown");
    } else {
      console.warn("[PostSync] Logo overlay failed:", result.error);
    }
  } else {
    console.info("[PostSync] Exact brand logo stamped after zone analysis (top-right)");
  }
  return input.imageApiUrl;
}

/** Exported for tests / logging — convert brand hex if needed. */
export { toFfmpegColor };
