import { fal } from '@fal-ai/client';
import { createHash } from 'node:crypto';
import { existsSync, mkdirSync, writeFileSync, statSync } from 'node:fs';
import { join } from 'node:path';
import { getSystemPromptImage } from '@/lib/db/repositories';
import { DEFAULT_SYSTEM_PROMPT_IMAGE } from '@/lib/db/defaults';
import { getFalKey } from '@/core/settings';

const DEFAULT_MODEL = 'fal-ai/flux/schnell';
const ASSETS_DIR = join(process.cwd(), 'public', 'assets', 'hero');
const PUBLIC_PREFIX = '/assets/hero';

export interface HeroImageInput {
  imageBrief: string;
  brandPrimaryColor: string;
  brandName?: string;
  aspectRatio?: '16:9' | '3:2' | '4:3' | '1:1';
  forceRegenerate?: boolean;
}

export interface HeroImageResult {
  url: string;          // Public URL to serve via Next.js
  absoluteUrl: string;  // Full http://host/url for email rendering
  localPath: string;    // Filesystem path
  promptHash: string;
  bytes: number;
  cached: boolean;
  durationMs: number;
  prompt: string;
}

const NEGATIVE_PROMPT = [
  'no text overlay',
  'no logos',
  'no watermarks',
  'no extra fingers',
  'no distorted faces',
  'no fake AI artifacts',
  'no low quality',
  'no oversaturated colors',
].join(', ');

export async function generateHeroImage(input: HeroImageInput, baseUrl?: string): Promise<HeroImageResult> {
  const start = Date.now();
  ensureDir();

  const finalPrompt = buildPrompt(input);
  const promptHash = hashPrompt(finalPrompt + (input.aspectRatio ?? '3:2'));
  const filename = `${promptHash}.png`;
  const localPath = join(ASSETS_DIR, filename);
  const url = `${PUBLIC_PREFIX}/${filename}`;
  const fallbackBase = (process.env.MAILFORGE_BASE_URL?.trim() || 'http://localhost:3000').replace(/\/$/, '');
  const absoluteUrl = `${baseUrl ?? fallbackBase}${url}`;

  // Cache hit
  if (!input.forceRegenerate && existsSync(localPath)) {
    const bytes = statSync(localPath).size;
    return { url, absoluteUrl, localPath, promptHash, bytes, cached: true, durationMs: Date.now() - start, prompt: finalPrompt };
  }

  const falKey = getFalKey();
  if (!falKey) {
    // Stub fallback — placeholder image via picsum
    const placeholderUrl = `https://picsum.photos/seed/${promptHash}/1200/800`;
    return { url: placeholderUrl, absoluteUrl: placeholderUrl, localPath: '', promptHash, bytes: 0, cached: false, durationMs: Date.now() - start, prompt: '[stub] ' + finalPrompt };
  }

  fal.config({ credentials: falKey });
  const model = process.env.FAL_MODEL ?? DEFAULT_MODEL;

  const result = await fal.subscribe(model, {
    input: {
      prompt: finalPrompt,
      image_size: aspectToFalSize(input.aspectRatio ?? '3:2'),
      num_images: 1,
      negative_prompt: NEGATIVE_PROMPT,
    },
    logs: false,
  });

  // Try to find image url in standard response shape
  const r = result as unknown as { data?: { images?: Array<{ url: string }> } };
  const imgUrl = r.data?.images?.[0]?.url;
  if (!imgUrl) {
    throw new Error(`fal.ai response had no image url. Model=${model}, raw keys=${Object.keys((result as object) ?? {}).join(',')}`);
  }

  // Download to local FS
  const res = await fetch(imgUrl);
  if (!res.ok) throw new Error(`Failed to download fal.ai image: ${res.status}`);
  const buf = Buffer.from(await res.arrayBuffer());
  writeFileSync(localPath, buf);

  return {
    url,
    absoluteUrl,
    localPath,
    promptHash,
    bytes: buf.length,
    cached: false,
    durationMs: Date.now() - start,
    prompt: finalPrompt,
  };
}

/**
 * Baut den finalen Prompt für fal.ai.
 *
 * Architektur:
 *  - User liefert `imageBrief` (frei, was im Bild zu sehen sein soll)
 *  - System-Prompt liefert Style-Direktiven (editierbar in Settings → KI-Prompts → Image-Prompt)
 *  - Beide werden zu einem comma-separated-Prompt verkettet
 *
 * Style-Prompt-Format: pro Zeile ein Style-Fragment. Zeilen mit '#' am Anfang sind Kommentare
 * und werden ignoriert. Leere Zeilen ebenfalls. Verfügbare Placeholders:
 *  - `{BRAND_PRIMARY_COLOR}` → Hex-Farbcode der aktiven Workspace
 *  - `{BRAND_NAME}` → Name der Workspace (oder leer falls nicht gesetzt)
 *
 * Fallback: Wenn das Setting leer ist (frische DB ohne Init), nutzt die DEFAULT-Konstante.
 */
function buildPrompt(i: HeroImageInput): string {
  const stylePromptRaw = getSystemPromptImage() || DEFAULT_SYSTEM_PROMPT_IMAGE;
  const styleFragments = stylePromptRaw
    .split('\n')
    .map((line) => line.trim())
    .filter((line) => line.length > 0 && !line.startsWith('#')) // Skip Kommentare + leere Zeilen
    .map((line) =>
      line
        .replace(/\{BRAND_PRIMARY_COLOR\}/g, i.brandPrimaryColor)
        .replace(/\{BRAND_NAME\}/g, i.brandName ?? ''),
    );
  return [i.imageBrief.trim(), ...styleFragments].join(', ');
}

function aspectToFalSize(aspect: string): string {
  // fal.ai gpt-image-1 size enum
  switch (aspect) {
    case '16:9': return 'landscape_16_9';
    case '3:2': return 'landscape_4_3';
    case '4:3': return 'landscape_4_3';
    case '1:1': return 'square_hd';
    default: return 'landscape_4_3';
  }
}

function hashPrompt(s: string): string {
  return createHash('sha256').update(s).digest('hex').slice(0, 16);
}

function ensureDir() {
  if (!existsSync(ASSETS_DIR)) mkdirSync(ASSETS_DIR, { recursive: true });
}
