import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { createCampaignWithRecipients } from '@/lib/db/campaign-repo';
import { getWorkspaceBySlug, resolveM365Sender, getActiveM365Senders } from '@/lib/db/repositories';
import { db } from '@/lib/db/client';
import { users } from '@/lib/db/schema';
import { distributeJobs, hasDistributionLimits } from '@/modules/scheduler/lib/distribute-jobs';
import { withAuth } from '@/core/auth';

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

const RecipientSchema = z.object({
  email: z.string().email(),
  firstName: z.string().optional(),
  lastName: z.string().optional(),
  fullName: z.string().optional(),
  company: z.string().optional(),
  cumasId: z.union([z.string(), z.number()]).optional(),
  /**
   * Pool-Discriminator für Login-Direktlinks (Kompass-Magic-Links).
   *  - 'private'  → Shop-Backend looked in `address_pools` (Default)
   *  - 'business' → Shop-Backend looked in `business_places`
   *
   * Frontend-Mapping:
   *  - AudienceRecipient.source === 'business' → 'business'
   *  - AudienceRecipient.source ∈ {'orders', 'evb'} → 'private'
   *  - CSV-Upload → aus CsvSourcePanel-Toggle (default 'private')
   */
  poolType: z.enum(['private', 'business']).optional().default('private'),
  /**
   * Vollständiger CRM-Record (alle Spalten der Datenquelle). Wird in
   * `recipients.customDataJson` persistiert und vom Worker für generische
   * `{{column_name}}`-Substitution genutzt.
   */
  customData: z.record(z.string(), z.unknown()).optional(),
});

/** Eine Variante einer Campaign — bei mehreren = A/B-Test. */
const VariantSchema = z.object({
  label: z.string().min(1),
  subject: z.string().min(1),
  preheader: z.string(),
  emailHtml: z.string().min(50),
  emailText: z.string(),
  mjml: z.string(),
  strategy: z.record(z.string(), z.unknown()),
  spamReport: z.record(z.string(), z.unknown()),
});

const UtmSchema = z.object({
  source: z.string().optional(),
  medium: z.string().optional(),
  campaign: z.string().optional(),
  content: z.string().optional(),
  term: z.string().optional(),
}).optional();

const ChampionTestSchema = z.object({
  testPercent: z.number().int().min(5).max(50),
  winnerHours: z.number().int().min(1).max(24),
}).optional();

/**
 * Pre-Schedule-Settings für die Job-Verteilung. Liegt im `briefingJson.schedule`,
 * wird hier nochmal explizit validiert weil briefingJson sonst typenlos durchgereicht wird.
 */
const ScheduleSchema = z.object({
  mode: z.enum(['now', 'scheduled', 'recurring']).optional(),
  scheduledAt: z.string().optional(),
  throttlePerHour: z.number().int().min(1).max(9500).optional(),
  maxPerDay: z.number().int().min(1).max(9500).optional(),
  sendWindowStart: z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/).optional(),
  sendWindowEnd: z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/).optional(),
  allowedWeekdays: z.array(z.number().int().min(0).max(6)).optional(),
}).refine(
  (s) => (s.sendWindowStart === undefined) === (s.sendWindowEnd === undefined),
  { message: 'sendWindowStart und sendWindowEnd müssen gemeinsam gesetzt werden' },
).refine(
  (s) => !s.sendWindowStart || !s.sendWindowEnd || s.sendWindowStart < s.sendWindowEnd,
  { message: 'sendWindowEnd muss nach sendWindowStart liegen' },
);

const RequestSchema = z.object({
  workspaceSlug: z.string(),
  campaignName: z.string(),
  briefingJson: z.record(z.string(), z.unknown()),
  tiptapJson: z.record(z.string(), z.unknown()).optional(),
  /** Variants-Array: 1 Element = normaler Send, 2+ Elemente = A/B-Test. */
  variants: z.array(VariantSchema).min(1, 'Mindestens 1 Variante erforderlich'),
  /** UTM-Auto-Append-Defaults — falls aus Briefing-Toggle aktiviert. */
  utmDefaults: UtmSchema,
  /** Champion-Test-Config — falls aus Briefing-Toggle aktiviert UND ≥ 2 Varianten. */
  championTest: ChampionTestSchema,
  senderEmail: z.string().email().optional(),
  recipients: z.array(RecipientSchema).min(1, 'Mindestens 1 Empfänger erforderlich'),
});

/**
 * Versand starten: persistiert Campaign + Recipients + Queue.
 * Der Worker pollt anschließend die Queue ab und schickt via M365 Graph.
 */
export const POST = withAuth(async (req: NextRequest, _ctx) => {
  try {
    const body = await req.json();
    const parsed = RequestSchema.safeParse(body);
    if (!parsed.success) {
      return NextResponse.json({
        error: 'Invalid input',
        issues: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`),
      }, { status: 400 });
    }

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

    // Sender-Resolution: Request > Workspace companyId > Workspace sender_email (Pool-Match) > einziger Pool-Sender
    const sender = resolveM365Sender({
      explicitEmail: parsed.data.senderEmail,
      companyId: workspace.companyId,
      workspaceDefaultEmail: workspace.senderEmail,
    });
    if (!sender) {
      const poolCount = getActiveM365Senders().length;
      const hint =
        poolCount === 0
          ? ' Lege unter Einstellungen → „M365 / Versand“ mindestens einen aktiven Absender an.'
          : poolCount === 1
            ? ''
            : ` Es sind ${poolCount} aktive Absender im Pool — bitte unter „Workspaces“ bei „${workspace.name}“ die Pool-Zuordnung (Zahl = Spalte „Zuordnung“ am Pool-Sender) setzen, die Workspace-Sender-Email auf eine Pool-Mailbox legen, oder im Tab „Versand“ einen Absender wählen.`;
      return NextResponse.json({
        error: parsed.data.senderEmail
          ? `Sender "${parsed.data.senderEmail}" nicht im aktiven Pool. Bitte in den Settings ergänzen oder aktivieren.`
          : `Workspace "${workspace.name}" hat keine Pool-Zuordnung / kein passender Standard-Absender — kein Sender-Mapping möglich.${hint}`,
      }, { status: 400 });
    }

    // Default-User ermitteln (oder Stub) — Multi-User-Auth kommt später
    let userId = db.select().from(users).limit(1).get()?.id;
    if (!userId) {
      userId = crypto.randomUUID();
      db.insert(users).values({ id: userId, email: process.env.M365_AUDIT_BCC ?? 'admin@mailforge.local', role: 'admin' }).run();
    }

    // Aggregierter Spam-Verdict = pessimistischste Variante (reject > fix > ship).
    // Wenn mind. 1 Variante "reject" sagt, blockt die ganze Campaign.
    const verdictRank = { ship: 0, fix: 1, reject: 2 } as const;
    const aggregatedVerdict = parsed.data.variants.reduce<'ship' | 'fix' | 'reject'>((acc, v) => {
      const vv = ((v.spamReport as { verdict?: 'ship' | 'fix' | 'reject' }).verdict ?? 'ship');
      return verdictRank[vv] > verdictRank[acc] ? vv : acc;
    }, 'ship');

    // Schedule aus briefingJson extrahieren und validieren — nur scheduled/recurring + aktive Limits triggern Pre-Schedule.
    const rawSchedule = (parsed.data.briefingJson as { schedule?: unknown }).schedule;
    const scheduleParsed = ScheduleSchema.safeParse(rawSchedule ?? {});
    if (!scheduleParsed.success) {
      return NextResponse.json({
        error: 'Invalid schedule',
        issues: scheduleParsed.error.issues.map(i => `schedule.${i.path.join('.')}: ${i.message}`),
      }, { status: 400 });
    }
    const sched = scheduleParsed.data;
    let jobSchedule: Date[] | undefined;
    const isPreScheduleMode = sched.mode === 'scheduled' || sched.mode === 'recurring';
    if (isPreScheduleMode && sched.scheduledAt) {
      const distOpts = {
        count: parsed.data.recipients.length,
        startAt: new Date(sched.scheduledAt),
        throttlePerHour: sched.throttlePerHour,
        maxPerDay: sched.maxPerDay,
        windowStart: sched.sendWindowStart,
        windowEnd: sched.sendWindowEnd,
        allowedWeekdays: sched.allowedWeekdays,
      };
      if (hasDistributionLimits(distOpts)) {
        jobSchedule = distributeJobs(distOpts);
      }
    }
    // Champion-Test + Pre-Schedule schliessen sich aus — bei Konflikt Pre-Schedule droppen.
    if (jobSchedule && parsed.data.championTest && parsed.data.variants.length > 1) {
      jobSchedule = undefined;
    }

    const { campaignId, queuedCount, variantIds } = createCampaignWithRecipients({
      workspaceId: workspace.id,
      workspaceSlug: workspace.slug,
      campaignName: parsed.data.campaignName,
      briefingJson: parsed.data.briefingJson,
      tiptapJson: parsed.data.tiptapJson ?? {},
      variants: parsed.data.variants.map((v) => ({
        label: v.label,
        subject: v.subject,
        preheader: v.preheader,
        emailHtml: v.emailHtml,
        emailText: v.emailText,
        mjmlSource: v.mjml,
        strategyJson: v.strategy,
        spamReportJson: v.spamReport,
        spamScore: (v.spamReport as { score?: number }).score ?? 0,
      })),
      senderEmail: sender.email,
      senderName: sender.name,
      spamVerdict: aggregatedVerdict,
      utmDefaults: parsed.data.utmDefaults,
      championTest: parsed.data.championTest,
      recipients: parsed.data.recipients.map(r => ({
        email: r.email,
        firstName: r.firstName,
        lastName: r.lastName,
        fullName: r.fullName,
        company: r.company,
        crmId: r.cumasId !== undefined ? String(r.cumasId) : undefined,
        // poolType: zod hat den Default 'private' bereits angewendet; explizit weitergeben
        // damit der Recipient-Insert in campaign-repo den korrekten Wert sieht.
        poolType: r.poolType,
        customData: r.customData,
      })),
      createdById: userId,
      jobSchedule,
    });

    const variantCount = variantIds.length;
    const isChampion = !!parsed.data.championTest && variantCount > 1;
    const splitInfo = isChampion
      ? ` (Champion-Test: ${parsed.data.championTest!.testPercent}% sofort, Winner nach ${parsed.data.championTest!.winnerHours} h an Rest)`
      : variantCount > 1
        ? ` (A/B-Test mit ${variantCount} Varianten, even-split)`
        : '';

    const scheduleInfo = jobSchedule
      ? ` Verteilt von ${jobSchedule[0].toLocaleString('de-DE')} bis ${jobSchedule[jobSchedule.length - 1].toLocaleString('de-DE')}.`
      : '';

    return NextResponse.json({
      ok: true,
      campaignId,
      queuedCount,
      variantCount,
      message: `${queuedCount} Empfänger in Versand-Queue${splitInfo}.${scheduleInfo} Starte den Worker mit \`npm run worker\` damit die Mails rausgehen.`,
    });
  } catch (err) {
    console.error('[/api/campaign/send] error:', err);
    return NextResponse.json(
      { error: err instanceof Error ? err.message : 'Internal error' },
      { status: 500 },
    );
  }
});
