import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { generateEmail, type PipelineInput } from '@/modules/briefing/lib/pipeline';
import { getWorkspaceBySlug } from '@/lib/db/repositories';
import { readBrandKit } from '@/lib/db/defaults';
import { getSectionTemplate } from '@/lib/db/section-template-repo';
import { docToHtml, type TipTapDoc } from '@/modules/briefing/lib/sections-to-tiptap';
import type { ResolvedSection, SectionBodyMode, SectionPosition } from '@/modules/editor/lib/inject-sections';
import { sanitizeSectionHtml, extractMjml } from '@/modules/editor/lib/sanitize-section-html';
import { getPublicBaseUrl } from '@/modules/sending/lib/public-base-url';
import { withAuth } from '@/core/auth';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

const RequestSchema = z.object({
  mode: z.enum(['preserve', 'rewrite']).default('rewrite'),
  language: z.enum(['auto', 'de', 'en']).default('auto'),
  campaignName: z.string().min(1),
  // Im preserve-Mode optional: LLM extrahiert Subject/Preheader/Hero-Headline aus briefHtml.
  // Im rewrite-Mode Pflicht: Cross-Field-Validation unten erzwingt das.
  offer: z.string().optional(),
  painPoint: z.string().optional(),
  ctaPrimaryText: z.string().min(1),
  // CTA-URL optional: leer-String oder fehlend = kein Link, der Renderer setzt href="#" als Platzhalter.
  // User kann die URL später nachtragen und via Re-Render-Endpoint die Mail aktualisieren.
  ctaPrimaryUrl: z.string().url().optional().or(z.literal('')),
  ctaSecondaryText: z.string().optional(),
  ctaSecondaryUrl: z.string().url().optional().or(z.literal('')),
  workspaceSlug: z.string().min(1),
  briefHtml: z.string().optional(),
  heroEnabled: z.boolean(),
  heroBriefHint: z.string().optional(),
  /** Optional: max. Hero-Bild-Höhe in px für Crop-Rendering im finalen MJML. */
  heroHeight: z.number().int().min(80).max(800).optional(),
  /** Optional: Crop-Position für object-position. Default 'center' wenn nicht gesetzt. */
  heroCropPosition: z.enum(['top', 'center', 'bottom']).optional(),
  /** Optional: User-übernommene Hero-URL (aus Scrape/Upload) — überspringt fal.ai-Generierung. */
  heroImageUrl: z.string().url().optional(),
  /**
   * Modulare Zusatz-Sections (Kontakt, App-Download, Testimonial, FAQ, …) die der User
   * im Briefing aktiviert hat. Jede Instance referenziert ein section_templates.id und
   * trägt den editierten TipTap-Inhalt — die Pipeline resolved den Block deterministisch
   * post-LLM. Disabled-Sections werden hier gar nicht erst durchgereicht (UI strippt sie).
   */
  extraSections: z
    .array(
      z.object({
        instanceId: z.string().min(1),
        templateId: z.string().min(1),
        kind: z.string().min(1),
        position: z.enum(['before_cta', 'after_cta', 'before_signature', 'after_signature']),
        contentJson: z.object({ type: z.literal('doc'), content: z.array(z.record(z.string(), z.unknown())) }),
        /**
         * Optionaler Instance-Override des Body-Modus. Wenn 'mjml_raw' oder 'html_raw',
         * MUSS customBody gesetzt sein — sonst skippen wir die Section (mit Warning).
         */
        bodyMode: z.enum(['tiptap', 'mjml_raw', 'html_raw']).optional(),
        customBody: z.string().optional(),
      }),
    )
    .optional(),
});

// Workspaces kommen jetzt aus der DB via getWorkspaceBySlug() — siehe Phase 1.5

export const POST = withAuth(async (req: NextRequest, _ctx) => {
  try {
    const body = await req.json();
    const parsed = RequestSchema.safeParse(body);
    if (!parsed.success) {
      const issues = parsed.error.issues.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`);
      console.warn('[/api/generate] validation failed:', issues);
      return NextResponse.json({
        error: 'Eingabe ungültig — bitte Felder prüfen',
        issues,
        details: parsed.error.flatten(),
      }, { status: 400 });
    }

    // Mode-aware Cross-Field-Validation: offer/painPoint sind im Preserve-Mode optional
    // (LLM extrahiert aus briefHtml), im Rewrite-Mode sind sie Pflicht.
    const { mode, offer, painPoint, briefHtml } = parsed.data;
    if (mode === 'rewrite' && (!offer?.trim() || !painPoint?.trim())) {
      return NextResponse.json({
        error: 'Im Modus "KI schreibt" sind Angebot und Schmerzpunkt Pflichtfelder.',
        issues: [
          ...(!offer?.trim() ? ['offer: required in rewrite mode'] : []),
          ...(!painPoint?.trim() ? ['painPoint: required in rewrite mode'] : []),
        ],
      }, { status: 400 });
    }
    if (mode === 'preserve' && !briefHtml?.trim()) {
      return NextResponse.json({
        error: 'Im Modus "Fertiger Text" muss ein Mailtext im Editor vorhanden sein.',
        issues: ['briefHtml: required in preserve mode'],
      }, { status: 400 });
    }

    const workspace = getWorkspaceBySlug(parsed.data.workspaceSlug);
    if (!workspace) {
      return NextResponse.json({ error: `Unknown workspace: ${parsed.data.workspaceSlug}` }, { status: 400 });
    }

    // Resolve Extra-Sections: Lookup section_templates per instance.templateId, splice
    // den editierten TipTap-Inhalt in den mjmlBlock. Templates aus fremden Workspaces oder
    // unbekannte IDs werden geskippt (mit Warning) statt zum 500 zu führen.
    //
    // Body-Modus-Auflösung:
    //  - Instance.bodyMode hat Vorrang vor Template.bodyMode.
    //  - Bei 'mjml_raw': customBody wird 1:1 als mjmlBlock verwendet, kein Template-Lookup.
    //  - Bei 'html_raw': customBody wird sanitized + in <mj-raw> gewrapped, kein Template-Lookup.
    //  - Bei 'tiptap'  : Template-mjmlBlock + docToHtml in {{TIPTAP_HTML}}-Slot (heutiges Verhalten).
    const resolvedSections: ResolvedSection[] = [];
    for (const inst of parsed.data.extraSections ?? []) {
      const tpl = getSectionTemplate(inst.templateId);
      if (!tpl) {
        console.warn(`[/api/generate] unknown section template: ${inst.templateId}, skipping`);
        continue;
      }
      if (tpl.workspaceId !== workspace.id) {
        console.warn(`[/api/generate] section template belongs to other workspace: ${inst.templateId}, skipping`);
        continue;
      }

      // Effective bodyMode: Instance-Override > Template-Default. Template-bodyMode kommt
      // aus der DB-Spalte body_mode (Default 'tiptap' für alle bisherigen Seed-Rows).
      const tplBodyMode = (tpl.bodyMode ?? 'tiptap') as SectionBodyMode;
      const effectiveBodyMode: SectionBodyMode = inst.bodyMode ?? tplBodyMode;

      let mjmlBlock: string;
      let contentHtml = '';
      let headStyles: string | undefined;

      if (effectiveBodyMode === 'mjml_raw') {
        const customBody = (inst.customBody ?? '').trim();
        if (!customBody) {
          // Instance hat raw-Modus signalisiert aber keinen Body — fallen wir auf das
          // Template-mjmlBlock zurück, sonst würde die Section komplett verschwinden.
          console.warn(
            `[/api/generate] section ${inst.instanceId} has bodyMode='mjml_raw' but empty customBody, falling back to template`,
          );
          mjmlBlock = tpl.mjmlBlock;
          contentHtml = docToHtml(inst.contentJson as unknown as TipTapDoc);
        } else {
          // Wenn der User ein vollständiges MJML-Dokument gepasted hat (`<mjml>...<mj-body>`),
          // extrahieren wir nur den Body-Inhalt. Sonst doppelter `<mjml>`-Wurzel-Tag → Compiler
          // verwirft die Section. `<mj-style>`-Blöcke aus dem User-Header werden separat
          // extrahiert und in den Mail-`<mj-head>` migriert (statt verworfen).
          const extracted = extractMjml(customBody);
          mjmlBlock = extracted.body;
          headStyles = extracted.styles || undefined;
        }
      } else if (effectiveBodyMode === 'html_raw') {
        const customBody = (inst.customBody ?? '').trim();
        if (!customBody) {
          console.warn(
            `[/api/generate] section ${inst.instanceId} has bodyMode='html_raw' but empty customBody, falling back to template`,
          );
          mjmlBlock = tpl.mjmlBlock;
          contentHtml = docToHtml(inst.contentJson as unknown as TipTapDoc);
        } else {
          // Sanitize + in mj-raw wrappen — Workspace-Token-Replacement passiert downstream
          // in injectExtraSections.resolvePlaceholders auf dem ganzen mjmlBlock-String.
          const { html } = sanitizeSectionHtml(customBody);
          mjmlBlock = `<mj-raw>${html}</mj-raw>`;
        }
      } else {
        // tiptap-Modus (Default + heutiges Verhalten).
        mjmlBlock = tpl.mjmlBlock;
        contentHtml = docToHtml(inst.contentJson as unknown as TipTapDoc);
      }

      resolvedSections.push({
        instanceId: inst.instanceId,
        kind: inst.kind,
        position: inst.position as SectionPosition,
        bodyMode: effectiveBodyMode,
        mjmlBlock,
        contentHtml,
        headStyles,
      });
    }

    const brandKit = readBrandKit(workspace.brandKitJson);
    const brandFooterMjml = brandKit.footer?.mode === 'mjml'
      ? brandKit.footer?.customMjml?.trim() || undefined
      : undefined;

    const input: PipelineInput = {
      ...parsed.data,
      ctaSecondaryUrl: parsed.data.ctaSecondaryUrl || undefined,
      brandName: workspace.name,
      brandPrimaryColor: workspace.primaryColor,
      brandSenderName: workspace.senderName,
      brandFooterText: workspace.footerText,
      brandFooterMjml,
      brandFooterLegalText: brandKit.footer?.legalFooterText,
      brandFooterLegalTextEn: brandKit.footer?.legalFooterTextEn,
      brandFooterConfidentialityNotice: brandKit.footer?.confidentialityNotice,
      brandFooterConfidentialityNoticeEn: brandKit.footer?.confidentialityNoticeEn,
      brandFooterShowUnsubscribe: brandKit.footer?.showUnsubscribe,
      brandLogoUrl: workspace.logoUrl ?? undefined,
      brandVoice: workspace.brandVoice,
      brandWordingStyle: workspace.wordingStyle ?? undefined,
      brandDesignStyle: workspace.designStyle ?? undefined,
      extraSections: resolvedSections.length > 0 ? resolvedSections : undefined,
    };

    const baseUrl = getPublicBaseUrl(req);
    const result = await generateEmail(input, { baseUrl });

    return NextResponse.json({
      ok: true,
      strategy: result.strategy,
      mjml: result.mjml,
      html: result.rendered.html,
      text: result.rendered.text,
      htmlBytes: result.rendered.bytes,
      mjmlErrors: result.rendered.errors,
      spam: result.spam,
      hero: result.hero ? {
        url: result.hero.url,
        absoluteUrl: result.hero.absoluteUrl,
        bytes: result.hero.bytes,
        cached: result.hero.cached,
        durationMs: result.hero.durationMs,
        prompt: result.hero.prompt,
      } : null,
      meta: {
        modelUsed: result.modelUsed,
        tokensIn: result.tokensIn,
        tokensOut: result.tokensOut,
        durationMs: result.durationMs,
      },
    });
  } catch (err) {
    console.error('[/api/generate] error:', err);
    return NextResponse.json(
      { error: err instanceof Error ? err.message : 'Internal error' },
      { status: 500 },
    );
  }
});
