import {
  getGeminiImageConfig,
  getOpenAIImageConfig,
  resolveImageProviderChain,
  type ChainProviderId,
} from "./config";
import { requestGeminiImage } from "./providers/gemini-image";
import type { ImageGenerationResult } from "./types";
import {
  buildBrandedSocialCardSvg,
  type SocialImageContext,
} from "@/lib/generation/social-image";
import { buildHeroImagePrompt } from "@/lib/generation/hero-image-prompt";
import { buildFullBannerImagePrompt } from "@/lib/generation/full-banner-prompt";
import { estimateImageCostUsd } from "./cost-tracking";
import { promises as fs } from "fs";
import path from "path";
import { getLocalMediaPath } from "@/lib/storage/media-files";

/**
 * Provider order for raw AI-only path (no social context).
 */
function resolveChainForBrand(
  _chain: ChainProviderId[],
  socialContext?: SocialImageContext,
): ChainProviderId[] {
  if (!socialContext) return _chain;

  // Social post images are OpenAI-only. The placeholder is deterministic
  // local composition, not another AI provider, and is retained for outages.
  return ["openai", "placeholder"];
}

const OPENAI_IMAGE_MODEL_FALLBACKS = ["gpt-image-1", "dall-e-3", "dall-e-2"] as const;

function buildLegacyPlaceholderSvg(prompt: string): string {
  const label = prompt.slice(0, 48).replace(/[<>&"]/g, "");
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
  <defs>
    <linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" style="stop-color:#2563eb"/>
      <stop offset="100%" style="stop-color:#7dd3fc"/>
    </linearGradient>
  </defs>
  <rect width="1024" height="1024" fill="url(#g)"/>
  <rect x="64" y="64" width="896" height="896" rx="48" fill="rgba(255,255,255,0.12)"/>
  <text x="512" y="480" text-anchor="middle" fill="white" font-family="Arial,sans-serif" font-size="36" font-weight="bold">PostSync Pro</text>
  <text x="512" y="540" text-anchor="middle" fill="rgba(255,255,255,0.9)" font-family="Arial,sans-serif" font-size="22">Branded Template</text>
  <text x="512" y="600" text-anchor="middle" fill="rgba(255,255,255,0.75)" font-family="Arial,sans-serif" font-size="16">${label}</text>
</svg>`;
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
}

function brandedPlaceholder(
  socialContext: SocialImageContext | undefined,
  fallbackPrompt: string,
): string {
  return socialContext
    ? buildBrandedSocialCardSvg(socialContext)
    : buildLegacyPlaceholderSvg(fallbackPrompt);
}

async function requestOpenAIImage(
  apiKey: string,
  model: string,
  prompt: string,
  timeoutMs: number,
): Promise<string | null> {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const promptLimit = model.includes("gpt-image")
      ? 16_000
      : model.includes("dall-e-3")
        ? 4_000
        : 1_000;
    const body: Record<string, unknown> = {
      // GPT Image accepts richer art-direction briefs; legacy DALL·E models
      // receive compact prompts within their smaller practical limits.
      prompt: prompt.slice(0, promptLimit),
      n: 1,
      size: model.includes("dall-e-3") || model.includes("gpt-image") ? "1024x1024" : "1024x1024",
    };

    if (model.startsWith("dall-e") || model.startsWith("gpt-image")) {
      body.model = model;
    }

    const res = await fetch("https://api.openai.com/v1/images/generations", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
      signal: controller.signal,
    });

    const data = (await res.json()) as {
      data?: Array<{ url?: string; b64_json?: string }>;
      error?: { message?: string };
    };

    if (!res.ok) {
      console.warn(`[PostSync] OpenAI image (${model}) failed:`, data.error?.message);
      return null;
    }

    const item = data.data?.[0];
    if (item?.url) return item.url;
    if (item?.b64_json) return `data:image/png;base64,${item.b64_json}`;
    return null;
  } catch (err) {
    console.warn("[PostSync] OpenAI image request error:", err);
    return null;
  } finally {
    clearTimeout(timer);
  }
}

interface ChainAttempt {
  provider: ChainProviderId;
  ok: boolean;
  error?: string;
}

async function attemptGemini(imagePrompt: string): Promise<{ url: string | null; error?: string }> {
  const config = getGeminiImageConfig();
  if (!config.apiKey) {
    return { url: null, error: "GEMINI_API_KEY missing" };
  }

  const result = await requestGeminiImage(config, imagePrompt);
  if (result.url) return { url: result.url };
  return { url: null, error: result.error ?? "Gemini returned no image" };
}

async function attemptOpenAI(imagePrompt: string): Promise<{ url: string | null; error?: string }> {
  const config = getOpenAIImageConfig();
  if (!config.apiKey) {
    return { url: null, error: "OPENAI_API_KEY missing" };
  }

  const modelsToTry = [
    config.model,
    ...OPENAI_IMAGE_MODEL_FALLBACKS.filter((m) => m !== config.model),
  ];

  const errors: string[] = [];
  for (const model of modelsToTry) {
    const url = await requestOpenAIImage(
      config.apiKey,
      model,
      imagePrompt,
      config.timeoutMs,
    );
    if (url) return { url };
    errors.push(model);
  }

  return { url: null, error: `OpenAI models failed (${errors.join(", ")})` };
}

function buildFallbackWarning(
  attempts: ChainAttempt[],
  winner: ChainProviderId,
): string | null {
  const failed = attempts.filter((a) => !a.ok);
  if (failed.length === 0) return null;

  if (winner === "placeholder") {
    const summary = failed.map((a) => `${a.provider}: ${a.error ?? "failed"}`).join("; ");
    return `All AI image providers failed (${summary}) — using branded template fallback.`;
  }

  const failedNames = failed.map((a) => a.provider).join(", ");
  return `Image chain fallback: ${failedNames} unavailable — rendered with ${winner}.`;
}

async function fetchHttpImageAsDataUrl(url: string): Promise<string | null> {
  try {
    const res = await fetch(url);
    if (!res.ok) return null;
    const buffer = Buffer.from(await res.arrayBuffer());
    const mime = res.headers.get("content-type") || "image/png";
    return `data:${mime};base64,${buffer.toString("base64")}`;
  } catch {
    return null;
  }
}

/** Persist hero bytes to disk — FFmpeg cannot reliably rasterize SVG data-URL images. */
async function persistHeroToDisk(
  heroUrl: string,
  mediaAssetId?: string,
): Promise<{ filePath: string; dataUrl: string } | null> {
  let dataUrl = heroUrl;
  if (heroUrl.startsWith("http://") || heroUrl.startsWith("https://")) {
    const fetched = await fetchHttpImageAsDataUrl(heroUrl);
    if (!fetched) return null;
    dataUrl = fetched;
  }
  if (!dataUrl.startsWith("data:image")) return null;

  const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
  if (!match) return null;
  const [, mime, b64] = match;
  const ext = mime.includes("jpeg") || mime.includes("jpg") ? "jpg" : "png";
  const id = path.basename(mediaAssetId || `hero-${Date.now().toString(36)}`);
  const filename = `${id}-hero.${ext}`;
  const filePath = getLocalMediaPath(filename);
  await fs.mkdir(path.dirname(filePath), { recursive: true });
  await fs.writeFile(filePath, Buffer.from(b64, "base64"));
  return { filePath, dataUrl };
}

/**
 * Prefer a fully new Gemini/OpenAI 1080×1080 banner on regenerate.
 * Fall back to checklist SVG + hero overlay when full AI banner fails.
 */
async function runComposedMarketingBanner(
  socialContext: SocialImageContext,
  legacyPrompt: string,
): Promise<ImageGenerationResult> {
  const chain = resolveChainForBrand(resolveImageProviderChain(), socialContext);
  const attempts: ChainAttempt[] = [];
  const hasExactLogo = Boolean(socialContext.logoDataUrl?.trim());

  // When an uploaded brand logo exists, NEVER use full-canvas AI banners.
  // DALL·E/Gemini invent garbled wordmarks; Path B keeps typography + logo in code.
  // Path A (full AI) only when no logo is uploaded.
  let fullUrl: string | null = null;
  let winner: ChainProviderId | null = null;
  const fullPrompt = buildFullBannerImagePrompt(socialContext);

  if (!hasExactLogo) {
    for (const provider of chain) {
      if (provider === "placeholder") continue;
      if (provider === "gemini") {
        const result = await attemptGemini(fullPrompt);
        if (result.url) {
          fullUrl = result.url;
          winner = "gemini";
          attempts.push({ provider, ok: true });
          break;
        }
        attempts.push({ provider, ok: false, error: result.error });
        console.warn(`[PostSync] Full banner gemini failed — ${result.error}`);
        continue;
      }
      if (provider === "openai") {
        const result = await attemptOpenAI(fullPrompt);
        if (result.url) {
          fullUrl = result.url;
          winner = "openai";
          attempts.push({ provider, ok: true });
          break;
        }
        attempts.push({ provider, ok: false, error: result.error });
        console.warn(`[PostSync] Full banner openai failed — ${result.error}`);
      }
    }

    if (fullUrl && winner) {
      const pathTaken = [
        ...attempts.map((a) => (a.ok ? `${a.provider}✓` : `${a.provider}✗`)),
        "full-banner✓",
      ].join(" → ");
      const imageModel =
        winner === "openai" ? getOpenAIImageConfig().model : getGeminiImageConfig().model;
      return {
        imageUrl: fullUrl,
        imagePrompt: fullPrompt,
        provider: winner,
        providerChain: pathTaken,
        warning: null,
        imageModel: `${imageModel}+full-banner`,
        estimatedCostUsd: estimateImageCostUsd(winner, imageModel),
        brandTemplateComposed: false,
        heroImageFilePath: null,
      };
    }
  } else {
    console.info(
      "[PostSync] Uploaded logo present — skipping full-AI banner (exact logo + SVG typography path)",
    );
  }

  // --- Path B: checklist SVG (exact logo via FFmpeg) + AI hero WITHOUT text/logo ---
  const heroPrompt = buildHeroImagePrompt(socialContext);
  let heroUrl: string | null = null;
  winner = null;

  for (const provider of chain) {
    if (provider === "placeholder") continue;
    if (provider === "gemini") {
      const result = await attemptGemini(heroPrompt);
      if (result.url) {
        heroUrl = result.url;
        winner = "gemini";
        attempts.push({ provider, ok: true });
        break;
      }
      attempts.push({ provider, ok: false, error: result.error });
      console.warn(`[PostSync] Hero image gemini failed — ${result.error}`);
      continue;
    }
    if (provider === "openai") {
      const result = await attemptOpenAI(heroPrompt);
      if (result.url) {
        heroUrl = result.url;
        winner = "openai";
        attempts.push({ provider, ok: true });
        break;
      }
      attempts.push({ provider, ok: false, error: result.error });
      console.warn(`[PostSync] Hero image openai failed — ${result.error}`);
    }
  }

  const persisted = heroUrl
    ? await persistHeroToDisk(heroUrl, socialContext.mediaAssetId)
    : null;

  const composedCtx: SocialImageContext = {
    ...socialContext,
    heroImageDataUrl: null,
    heroImageFilePath: null,
    forceFreshVisuals: true,
  };
  const imageUrl = buildBrandedSocialCardSvg(composedCtx);

  if (!winner || !persisted) {
    winner = "placeholder";
    attempts.push({ provider: "placeholder", ok: true });
  }

  const pathTaken = [
    ...attempts.map((a) => (a.ok ? `${a.provider}✓` : `${a.provider}✗`)),
    "composite-fallback✓",
  ].join(" → ");

  const imageModel =
    winner === "openai"
      ? getOpenAIImageConfig().model
      : winner === "gemini"
        ? getGeminiImageConfig().model
        : "branded-template";

  return {
    imageUrl,
    imagePrompt: heroPrompt || fullPrompt || legacyPrompt,
    provider: winner,
    providerChain: pathTaken,
    warning: persisted
      ? hasExactLogo
        ? null
        : "Full AI banner unavailable — used checklist template + hero overlay."
      : buildFallbackWarning(attempts, "placeholder") ||
        "Hero AI unavailable — checklist banner rendered without new photo.",
    imageModel: persisted
      ? hasExactLogo
        ? `${imageModel}+logo-safe-composite`
        : `${imageModel}+composite`
      : "branded-template",
    estimatedCostUsd: estimateImageCostUsd(
      winner === "placeholder" ? "placeholder" : winner,
      imageModel,
    ),
    brandTemplateComposed: true,
    heroImageFilePath: persisted?.filePath ?? null,
  };
}

/**
 * With company context: always composite Gemini/OpenAI hero into branded 1080 banner.
 */
export async function runImageProviderChain(
  imagePrompt: string,
  socialContext?: SocialImageContext,
): Promise<ImageGenerationResult> {
  if (socialContext) {
    return runComposedMarketingBanner(socialContext, imagePrompt);
  }

  const chain = resolveImageProviderChain();
  const attempts: ChainAttempt[] = [];
  let winner: ChainProviderId | null = null;
  let imageUrl: string | null = null;

  for (const provider of chain) {
    if (provider === "placeholder") {
      winner = "placeholder";
      imageUrl = brandedPlaceholder(undefined, imagePrompt);
      attempts.push({ provider, ok: true });
      break;
    }

    if (provider === "gemini") {
      const result = await attemptGemini(imagePrompt);
      if (result.url) {
        winner = "gemini";
        imageUrl = result.url;
        attempts.push({ provider, ok: true });
        break;
      }
      attempts.push({ provider, ok: false, error: result.error });
      console.warn(`[PostSync] Image chain: gemini failed — ${result.error}`);
      continue;
    }

    if (provider === "openai") {
      const result = await attemptOpenAI(imagePrompt);
      if (result.url) {
        winner = "openai";
        imageUrl = result.url;
        attempts.push({ provider, ok: true });
        break;
      }
      attempts.push({ provider, ok: false, error: result.error });
      console.warn(`[PostSync] Image chain: openai failed — ${result.error}`);
    }
  }

  if (!winner || !imageUrl) {
    winner = "placeholder";
    imageUrl = brandedPlaceholder(undefined, imagePrompt);
  }

  const imageModel =
    winner === "openai"
      ? getOpenAIImageConfig().model
      : winner === "gemini"
        ? getGeminiImageConfig().model
        : "branded-template";

  const estimatedCostUsd = estimateImageCostUsd(winner, imageModel);
  const pathTaken = attempts.map((a) => (a.ok ? `${a.provider}✓` : `${a.provider}✗`)).join(" → ");

  return {
    imageUrl,
    imagePrompt,
    provider: winner,
    providerChain: pathTaken || winner,
    warning: buildFallbackWarning(attempts, winner),
    imageModel,
    estimatedCostUsd,
  };
}
