import { NextRequest, NextResponse } from 'next/server';
import { eq } from 'drizzle-orm';
import { incrementVariantClick } from '@/lib/db/campaign-repo';
import { db } from '@/lib/db/client';
import { campaigns, workspaces } from '@/lib/db/schema';

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

/**
 * Click-Tracking-Endpoint mit 302-Redirect.
 *
 * URL-Form: `/api/track/click?to=ENCODED_URL&c=CID&v=VID&r=RID`
 *
 * Workflow:
 * 1. Click-Counter auf Variante atomar erhöhen (`tracking_events` mit linkUrl)
 * 2. mf_rcp/mf_cmp/mf_v an die Ziel-URL anhängen (für Shop-Side Attribution)
 * 3. 302-Redirect zur angereicherten `to`-URL
 *
 * Sicherheit:
 *  - `to` muss http(s)-URL sein, sonst 400 (Phishing-Vermeidung — kein offener Redirect)
 *  - Kein Open-Redirect auf javascript:/data:/file:
 *  - Bei missing params: einfache 400-Antwort (kein Redirect-Fallback)
 */
function isSafeUrl(raw: string): boolean {
  try {
    const u = new URL(raw);
    return u.protocol === 'http:' || u.protocol === 'https:';
  } catch {
    return false;
  }
}

/**
 * Hängt mf_rcp/mf_cmp/mf_v an die Ziel-URL an, damit der Shop den Klick einer
 * konkreten Email zuordnen kann (Cookie-Capture im Root-Layout des Shops).
 * Existierende Parameter werden NICHT überschrieben — wenn der User die Ziel-URL
 * im Template manuell mit `mf_rcp=X` setzt, gewinnt der manuelle Wert.
 */
function appendAttributionParams(
  to: string,
  recipientId: string | undefined,
  campaignId: string | null,
  variantId: string | null,
): string {
  try {
    const url = new URL(to);
    if (recipientId && !url.searchParams.has('mf_rcp')) url.searchParams.set('mf_rcp', recipientId);
    if (campaignId && !url.searchParams.has('mf_cmp')) url.searchParams.set('mf_cmp', campaignId);
    if (variantId && !url.searchParams.has('mf_v')) url.searchParams.set('mf_v', variantId);
    return url.toString();
  } catch {
    // isSafeUrl wurde vorher geprüft — sollte nie hier landen.
    return to;
  }
}

export async function GET(req: NextRequest) {
  const { searchParams } = req.nextUrl;
  const campaignId = searchParams.get('c');
  const variantId = searchParams.get('v');
  const recipientId = searchParams.get('r') ?? undefined;
  const toRaw = searchParams.get('to');

  if (!toRaw) {
    return NextResponse.json({ error: 'Missing "to" parameter' }, { status: 400 });
  }

  // searchParams gibt bereits decoded zurück, aber wir reparieren auch falsche Encodings:
  const to = toRaw;
  if (!isSafeUrl(to)) {
    return NextResponse.json({ error: 'Invalid redirect target' }, { status: 400 });
  }

  // Counter erhöhen (silent failure — Tracking ist best-effort, blockiert NIE den Redirect)
  if (campaignId && variantId) {
    try {
      incrementVariantClick(variantId, campaignId, recipientId, to);
    } catch (e) {
      console.error('[/api/track/click] tracking-failure:', e instanceof Error ? e.message : e);
    }
  }

  const finalTarget = appendAttributionParams(to, recipientId, campaignId, variantId);
  const res = NextResponse.redirect(finalTarget, 302);

  // mf_attr-Cookie für Onsite-Tracking-Snippet setzen (Phase 3+4).
  // Workspace-Shop-Domain wird zur Laufzeit aus campaigns.workspaceId → workspaces.shopDomain
  // aufgelöst. Cookie ist Cross-Domain (Domain-Scope auf Shop, nicht MailForge),
  // damit mf-track.js auf der Shop-Seite ihn lesen kann.
  //
  // Best-Effort: bei DB-Failures wird der Cookie einfach nicht gesetzt, der Redirect
  // selbst läuft trotzdem durch. Onsite-Tracking funktioniert dann nur über den
  // URL-Param-Fallback (?mf_rcp=...) statt Cookie.
  if (campaignId && recipientId) {
    try {
      const shopDomain = resolveShopDomain(campaignId);
      if (shopDomain) {
        const cookieValue = JSON.stringify({
          r: recipientId,
          c: campaignId,
          v: variantId ?? null,
          ts: Math.floor(Date.now() / 1000),
        });
        // Cookie-Domain ohne Protokoll/Slash und mit führendem Punkt für Subdomain-Match.
        // `kompass-versicherung.de` → `.kompass-versicherung.de` damit www. + shop. matchen.
        const cookieDomain = normalizeCookieDomain(shopDomain);
        res.headers.append(
          'Set-Cookie',
          `mf_attr=${encodeURIComponent(cookieValue)}; Domain=${cookieDomain}; ` +
          `Path=/; Max-Age=7776000; SameSite=Lax; Secure`,
        );
      }
    } catch (e) {
      console.error('[/api/track/click] cookie-failure:', e instanceof Error ? e.message : e);
    }
  }

  return res;
}

/**
 * Schlägt für eine Campaign die Workspace-Shop-Domain nach. Synchron via Drizzle,
 * Single-Query — kein Caching nötig weil die Campaign-Tabelle indexed ist.
 *
 * Returns null wenn Campaign nicht existiert oder Workspace keine shopDomain hat.
 */
function resolveShopDomain(campaignId: string): string | null {
  const row = db
    .select({ shopDomain: workspaces.shopDomain })
    .from(campaigns)
    .innerJoin(workspaces, eq(workspaces.id, campaigns.workspaceId))
    .where(eq(campaigns.id, campaignId))
    .get();
  return row?.shopDomain ?? null;
}

/**
 * Normalisiert eine Shop-Domain für die Verwendung als Cookie-Domain.
 *  - Protokoll entfernen ('https://www.example.de' → 'www.example.de')
 *  - Path/Query entfernen ('example.de/shop' → 'example.de')
 *  - 'www.'-Prefix entfernen ('www.example.de' → 'example.de')
 *  - Führenden Punkt vorne — alte Konvention für "subdomains erlaubt"
 */
function normalizeCookieDomain(raw: string): string {
  let d = raw.trim();
  d = d.replace(/^https?:\/\//, '');
  d = d.split('/')[0];
  d = d.replace(/^www\./, '');
  return '.' + d;
}
