import { sql } from 'drizzle-orm';
import { sqliteTable, text, integer, index, uniqueIndex } from 'drizzle-orm/sqlite-core';

const id = () => text('id').primaryKey().$defaultFn(() => crypto.randomUUID());
const ts = (n: string) => integer(n, { mode: 'timestamp_ms' });
const now = sql`(unixepoch() * 1000)`;

export const users = sqliteTable('users', {
  id: id(),
  email: text('email').notNull().unique(),
  name: text('name'),
  /** scrypt hash (`saltHex:keyHex`) — set via `scripts/create-native-user.ts` */
  passwordHash: text('password_hash'),
  role: text('role', { enum: ['admin', 'editor'] }).notNull().default('editor'),
  createdAt: ts('created_at').notNull().default(now),
  lastLoginAt: ts('last_login_at'),
});

export const workspaces = sqliteTable('workspaces', {
  id: id(),
  slug: text('slug').notNull().unique(),
  name: text('name').notNull(),
  primaryColor: text('primary_color').notNull(),
  secondaryColor: text('secondary_color'),
  logoUrl: text('logo_url'),
  senderDomain: text('sender_domain').notNull(),
  senderName: text('sender_name').notNull(),
  senderEmail: text('sender_email').notNull(),
  replyToEmail: text('reply_to_email').notNull(),
  /**
   * Ziel-Shop-Domain für Login-Direktlinks (Kompass-Magic-Links).
   * Format: 'https://www.versicherungsspartarif.de' (mit oder ohne https://, mit oder ohne trailing-Slash).
   * Wird vom Worker beim Auflösen von `{{LOGIN_LINK}}` an `buildLoginLink()` (src/lib/crm/kompass-link.ts) übergeben.
   * Nullable — Workspaces ohne Shop produzieren leere Login-Links (Worker loggt warn).
   */
  shopDomain: text('shop_domain'),
  /**
   * HMAC-Secret für eingehende Tracking-Webhooks (Shop-/CRM-Push-Events wie add_to_cart,
   * quote_form_submit). Wird vom Webhook-Endpoint `/api/track/webhook/[workspaceSlug]`
   * verwendet — der Caller muss `X-MailForge-Signature: sha256=…` mitsenden.
   * Rotation: UI generiert einen neuen 256-bit Random-Hex-String und speichert hier;
   * keine Versionierung — bei Rotation wird der alte Wert sofort ungültig.
   */
  webhookSecret: text('webhook_secret'),
  /**
   * GA4 Property-ID für On-Demand-Session-Lookup pro Recipient
   * (z.B. '123456789' — die nackte Property-ID ohne 'properties/'-Prefix).
   * Wird vom RecipientTimelineDrawer benutzt um über die GA4 Data API die zugehörige
   * Session zu zeigen.
   */
  ga4PropertyId: text('ga4_property_id'),
  /**
   * GA4 Service-Account-Credentials pro Workspace (JSON vom Google-Cloud-Console-Download).
   * Enthält client_email, private_key, project_id, token_uri.
   * Wird über separaten PATCH-Endpoint /api/workspaces/[slug]/credentials gepflegt
   * (separat von /api/workspaces, um versehentliches Logging zu vermeiden).
   */
  ga4CredentialsJson: text('ga4_credentials_json', { mode: 'json' }),
  footerText: text('footer_text').notNull(),
  brandVoice: text('brand_voice').notNull(),
  wordingStyle: text('wording_style'),  // z.B. "B2B Sie-Form, sachlich, kurz"
  designStyle: text('design_style'),    // z.B. "Minimalist, viel Whitespace, keine Schatten"
  companyId: integer('company_id'),     // Winitux-Mailserver-Mapping (1=ITS, 2=KSM, 3=Spartarif)
  imprintUrl: text('imprint_url').notNull(),
  privacyUrl: text('privacy_url').notNull(),
  brandKitJson: text('brand_kit_json', { mode: 'json' }).notNull(),
  /**
   * Multi-Touch-Attribution-Modell pro Workspace:
   *  - 'last-click': 100% Credit auf letzten click/open (Default)
   *  - 'first-click': 100% Credit auf ersten click/open
   *  - 'linear': 1/N pro Touch
   *  - 'time-decay': exponentiell, Halbwertszeit 7 Tage
   */
  attributionModel: text('attribution_model', {
    enum: ['last-click', 'first-click', 'linear', 'time-decay'],
  }).notNull().default('last-click'),
  /**
   * Lookback-Window in Tagen — Conversions zählen nur, wenn der letzte Touch
   * innerhalb dieses Zeitraums liegt. B2C kürzer (7-14), B2B länger (30-90).
   */
  attributionLookbackDays: integer('attribution_lookback_days').notNull().default(30),
  /**
   * Cumas-Feldname (Dot-Path) für Recipient-ID-Match — pro Workspace. Wenn null,
   * fällt der Poller auf das globale `app_settings.conversion_polling.recipientIdField`
   * zurück; wenn auch das leer ist, läuft der Email-Match (Phase-1-Verhalten).
   */
  cumasRecipientField: text('cumas_recipient_field'),
  /**
   * Welche Conversion-Kinds für diese Workspace als gültige Conversion zählen.
   * Default: alle vier. UI zeigt 4 Checkbox-Pills.
   */
  activeConversionKinds: text('active_conversion_kinds', { mode: 'json' })
    .$type<Array<'order' | 'lead' | 'magic_login' | 'form_submit'>>()
    .notNull()
    .default(['order', 'lead', 'magic_login', 'form_submit']),
  createdAt: ts('created_at').notNull().default(now),
  updatedAt: ts('updated_at').notNull().default(now),
});

/**
 * Generischer Key-Value-Store für anwendungs-globale Konfiguration:
 * - 'system_prompt_generate' → string (Master-Prompt für Generate-Pipeline)
 * - 'system_prompt_suggest' → string (Master-Prompt für Briefing-Suggest)
 * - 'system_prompt_image' → string (Master-Prompt für Image-Refinement)
 * - 'm365_senders' → Array<M365Sender> (User-konfigurierter Sender-Pool)
 */
export const appSettings = sqliteTable('app_settings', {
  key: text('key').primaryKey(),
  value: text('value', { mode: 'json' }).notNull(),
  updatedAt: ts('updated_at').notNull().default(now),
});

export const templates = sqliteTable('templates', {
  id: id(),
  workspaceId: text('workspace_id').notNull().references(() => workspaces.id),
  name: text('name').notNull(),
  description: text('description'),
  tiptapJson: text('tiptap_json', { mode: 'json' }).notNull(),
  briefingJson: text('briefing_json', { mode: 'json' }),
  createdAt: ts('created_at').notNull().default(now),
  updatedAt: ts('updated_at').notNull().default(now),
});

/**
 * Section-Templates sind WIEDERVERWENDBARE Bausteine pro Workspace (z.B. Kontakt-Service-Block,
 * App-Download-Badges, Testimonial-Quote, FAQ-Block). Sie unterscheiden sich von `templates`:
 * `templates` = ganze Mail (briefing+tiptap), `sectionTemplates` = ein einzelner MJML-Block.
 *
 * Im Briefing-Panel kann der User mehrere Section-Instanzen anlegen — die Instanz hält
 * editierten Inhalt (TipTap-Doc), das Template stellt das visuelle MJML-Skelett.
 *
 * `kind`: enum für UI-Icon und Default-System-Prompt-Block. 'custom' = User-eigene Section.
 * `mjmlBlock`: enthält die Platzhalter `{{TIPTAP_HTML}}`, `{{BRAND_PRIMARY_COLOR}}`, `{{BRAND_NAME}}`.
 * `defaultPosition`: hinweis wo die Section im finalen MJML eingespliced wird.
 * `isSystem`: vom Seed-Script angelegte Standard-Templates (nicht löschbar im UI).
 */
export const sectionTemplates = sqliteTable('section_templates', {
  id: id(),
  workspaceId: text('workspace_id').notNull().references(() => workspaces.id, { onDelete: 'cascade' }),
  kind: text('kind', { enum: ['contact_service', 'app_download', 'testimonial', 'faq', 'custom'] }).notNull(),
  name: text('name').notNull(),
  description: text('description'),
  defaultContentJson: text('default_content_json', { mode: 'json' }).notNull(),
  mjmlBlock: text('mjml_block').notNull(),
  aiPromptHint: text('ai_prompt_hint'),
  defaultPosition: text('default_position', {
    enum: ['before_cta', 'after_cta', 'before_signature', 'after_signature'],
  }).notNull().default('before_signature'),
  /**
   * Body-Modus des Templates:
   *  - 'tiptap'   → mjmlBlock enthält {{TIPTAP_HTML}}-Slot, Inhalt kommt aus defaultContentJson + UI-Editor.
   *  - 'mjml_raw' → mjmlBlock ist vom User importiertes MJML, kein Slot, KI-Vorschlag deaktiviert.
   *  - 'html_raw' → mjmlBlock ist sanitized HTML in <mj-raw> gewrapped, kein Slot, KI deaktiviert.
   * Bestehende Seed-Templates bleiben auf 'tiptap' (Default), Verhalten unverändert.
   */
  bodyMode: text('body_mode', { enum: ['tiptap', 'mjml_raw', 'html_raw'] })
    .notNull().default('tiptap'),
  isSystem: integer('is_system', { mode: 'boolean' }).notNull().default(false),
  createdAt: ts('created_at').notNull().default(now),
  updatedAt: ts('updated_at').notNull().default(now),
}, (t) => ({
  workspaceKindIdx: index('section_templates_workspace_kind').on(t.workspaceId, t.kind),
}));

export const campaigns = sqliteTable('campaigns', {
  id: id(),
  workspaceId: text('workspace_id').notNull().references(() => workspaces.id),
  templateId: text('template_id').references(() => templates.id),
  createdById: text('created_by_id').notNull().references(() => users.id),
  name: text('name').notNull(),
  briefingJson: text('briefing_json', { mode: 'json' }).notNull(),
  tiptapJson: text('tiptap_json', { mode: 'json' }).notNull(),
  outputDir: text('output_dir'),
  strategyJson: text('strategy_json', { mode: 'json' }),
  emailHtmlPath: text('email_html_path'),
  emailTxtPath: text('email_txt_path'),
  heroAssetId: text('hero_asset_id'),
  spamReportJson: text('spam_report_json', { mode: 'json' }),
  spamScore: integer('spam_score'),
  spamVerdict: text('spam_verdict', { enum: ['ship', 'fix', 'reject'] }),
  abEnabled: integer('ab_enabled', { mode: 'boolean' }).notNull().default(false),
  abWinnerHours: integer('ab_winner_hours').notNull().default(4),
  abWinnerVariantId: text('ab_winner_variant_id'),
  /**
   * UTM-Defaults der Campaign (Auto-Append an alle Links beim Versand).
   * Shape: { source?: string; medium?: string; campaign?: string; content?: string; term?: string }
   * Wenn null → kein UTM-Append. utm_content wird per A/B-Variante zur Send-Zeit überschrieben (A/B/C).
   */
  utmDefaultsJson: text('utm_defaults_json', { mode: 'json' }),
  status: text('status', {
    enum: ['draft', 'building', 'review', 'scheduled', 'sending', 'sent', 'failed', 'cancelled'],
  }).notNull().default('draft'),
  /**
   * Discriminator zwischen klassischer Marketing-Campaign und versteckter
   * Container-Campaign für Trigger-Templates (Transactional-Mailer).
   *  - 'marketing'         → normaler Bulk-Send mit Empfängerliste, im UI sichtbar
   *  - 'trigger_container' → langlebiger Versand-Bucket pro Trigger-Template,
   *                          status bleibt dauerhaft 'sending', wird vom UI ausgeblendet,
   *                          finalizeCampaignIfDone() ignoriert sie.
   */
  kind: text('kind', { enum: ['marketing', 'trigger_container'] }).notNull().default('marketing'),
  scheduledFor: ts('scheduled_for'),
  startedAt: ts('started_at'),
  completedAt: ts('completed_at'),
  createdAt: ts('created_at').notNull().default(now),
  updatedAt: ts('updated_at').notNull().default(now),
});

/**
 * Versions-Snapshots eines Drafts/Campaigns. Komplette Snapshots statt Diffs:
 *  - JSON-Felder sind klein, kein Merge-Risiko, Restore = simples Copy.
 *  - source='auto'      → vom System erzeugt (z.B. nach 5min Aktivität)
 *  - source='milestone' → bei wichtigen Übergängen (generate, render, send)
 *  - source='manual'    → User-Klick mit optionalem Label
 */
export const campaignVersions = sqliteTable('campaign_versions', {
  id: id(),
  campaignId: text('campaign_id').notNull().references(() => campaigns.id, { onDelete: 'cascade' }),
  source: text('source', { enum: ['auto', 'manual', 'milestone'] }).notNull(),
  label: text('label'),
  milestone: text('milestone', { enum: ['generate', 'render', 'send', 'pre_restore'] }),
  name: text('name').notNull(),
  briefingJson: text('briefing_json', { mode: 'json' }).notNull(),
  tiptapJson: text('tiptap_json', { mode: 'json' }).notNull(),
  strategyJson: text('strategy_json', { mode: 'json' }),
  emailHtml: text('email_html'),
  emailText: text('email_text'),
  spamReportJson: text('spam_report_json', { mode: 'json' }),
  heroAssetId: text('hero_asset_id'),
  createdAt: ts('created_at').notNull().default(now),
}, (t) => ({
  campaignCreatedIdx: index('versions_campaign_created').on(t.campaignId, t.createdAt),
}));

/**
 * Eine Variante einer Campaign — für A/B-Tests mit Pattern C (Full-Template).
 * Jede Variante trägt eigenen MJML-Source, gerendertes HTML, Plain-Text und Strategy-Snapshot.
 * Wenn eine Campaign nur 1 Variante hat → kein A/B-Test, einfach normaler Send.
 * Bei N > 1 Varianten → Recipients werden modulo-N evenly über die Varianten gesplittet.
 */
export const campaignVariants = sqliteTable('campaign_variants', {
  id: id(),
  campaignId: text('campaign_id').notNull().references(() => campaigns.id, { onDelete: 'cascade' }),
  /** Anzeigelabel "A" / "B" / "C" — wird auch als utm_content beim Send angehängt. */
  label: text('label').notNull(),
  subject: text('subject').notNull(),
  preheader: text('preheader').notNull(),
  /** Pro-Variante MJML-Source (für Re-Render im UI). */
  mjml: text('mjml'),
  /** Pro-Variante gerendertes HTML — der Worker zieht das. */
  html: text('html'),
  /** Pro-Variante Plain-Text-Fallback. */
  htmlText: text('html_text'),
  /** Strategy-Snapshot dieser Variante (subject_variants, hero_headline, intro, …). */
  strategyJson: text('strategy_json', { mode: 'json' }),
  /** Spam-Report-Snapshot dieser Variante. */
  spamReportJson: text('spam_report_json', { mode: 'json' }),
  spamScore: integer('spam_score'),
  isWinner: integer('is_winner', { mode: 'boolean' }).notNull().default(false),
  sentCount: integer('sent_count').notNull().default(0),
  openCount: integer('open_count').notNull().default(0),
  clickCount: integer('click_count').notNull().default(0),
  createdAt: ts('created_at').notNull().default(now),
}, (t) => ({
  campaignIdx: index('variants_campaign').on(t.campaignId),
}));

export const recipients = sqliteTable('recipients', {
  id: id(),
  campaignId: text('campaign_id').notNull().references(() => campaigns.id),
  variantId: text('variant_id').references(() => campaignVariants.id),
  crmId: text('crm_id'),
  email: text('email').notNull(),
  firstName: text('first_name'),
  lastName: text('last_name'),
  /**
   * Pool-Discriminator für Login-Direktlinks (Kompass-Magic-Links).
   *  - 'private'  → Shop-Backend looked in `address_pools` (default, ohne &type-Param)
   *  - 'business' → Shop-Backend looked in `business_places` (&type=business im Link)
   *
   * Quelle: AudienceRecipient.source ('business' → 'business', 'orders'/'evb' → 'private')
   * oder CSV-Toggle im CsvSourcePanel.
   *
   * Wichtig: Eine Geschäftskunden-ID mit poolType='private' führt im Shop-Backend zu
   * 404 "Customer not found" — die korrekte Zuordnung muss im Frontend passieren.
   */
  poolType: text('pool_type', { enum: ['private', 'business'] }).notNull().default('private'),
  customDataJson: text('custom_data_json', { mode: 'json' }),
  createdAt: ts('created_at').notNull().default(now),
}, (t) => ({
  /**
   * Hinweis zur Eindeutigkeit: der frühere `uniqueIndex('recipients_campaign_email')`
   * hat in Marketing-Campaigns Doppel-Empfänger verhindert. Mit Trigger-Container-
   * Campaigns muss derselbe Empfänger aber legitim mehrfach vorkommen
   * (Order #1, Order #2, …). SQLite erlaubt in partiellen Indexen keine Subqueries,
   * daher fällt der Unique-Index weg — Dedup für Marketing-Campaigns liegt
   * vollständig in der Applikationsschicht (`createCampaignWithRecipients` sieht
   * eine bereits deduplizierte Recipient-Liste). Wir behalten einen non-unique
   * Performance-Index auf (campaignId, email).
   */
  campaignEmailIdx: index('recipients_campaign_email').on(t.campaignId, t.email),
}));

export const emailQueue = sqliteTable('email_queue', {
  id: id(),
  campaignId: text('campaign_id').notNull().references(() => campaigns.id),
  recipientId: text('recipient_id').notNull().references(() => recipients.id),
  variantId: text('variant_id').references(() => campaignVariants.id),
  status: text('status', {
    enum: ['pending', 'sending', 'sent', 'failed', 'bounced', 'skipped', 'champion_held'],
  }).notNull().default('pending'),
  scheduledAt: ts('scheduled_at').notNull().default(now),
  attempts: integer('attempts').notNull().default(0),
  lastError: text('last_error'),
  messageId: text('message_id'),
  sentAt: ts('sent_at'),
  createdAt: ts('created_at').notNull().default(now),
}, (t) => ({
  statusScheduledIdx: index('queue_status_scheduled').on(t.status, t.scheduledAt),
  campaignStatusIdx: index('queue_campaign_status').on(t.campaignId, t.status),
}));

export const trackingEvents = sqliteTable('tracking_events', {
  id: id(),
  campaignId: text('campaign_id').notNull().references(() => campaigns.id),
  recipientId: text('recipient_id').references(() => recipients.id),
  variantId: text('variant_id').references(() => campaignVariants.id),
  messageId: text('message_id').notNull(),
  /**
   * `conversion` (Phase-3) wurde nachträglich ergänzt — ein Conversion-Event entsteht
   * entweder durch das URL-Param-Pixel (3a) auf einer Thank-You-Page oder durch das
   * API-Polling von CRM-Systemen wie Cumas/Nova (3b). Siehe `source` zur Unterscheidung.
   *
   * `delivered` (Phase-4) wird vom Worker direkt nach erfolgreichem M365-Send geschrieben
   * — best-effort "von Microsoft akzeptiert", ersetzt aber nicht echte Delivery-Webhooks.
   */
  eventType: text('event_type', {
    enum: ['delivered', 'open', 'click', 'bounce', 'unsubscribe', 'complaint', 'conversion'],
  }).notNull(),
  linkUrl: text('link_url'),
  userAgent: text('user_agent'),
  ipHash: text('ip_hash'),
  /**
   * Flag für Apple Mail Privacy Protection: TRUE wenn der Open-Pixel-Aufruf
   * vermutlich von Apples Proxy stammt (UA='Mozilla/5.0' + Apple-Mask-IP-Range)
   * und NICHT von einem echten User-Open. Stats-Aggregat zeigt zwei Open-Zahlen:
   * `opens` (raw, mit MPP) und `opensVerified` (nur isMpp=0|null). Default null
   * für historische Events vor Phase-4.
   */
  isMpp: integer('is_mpp', { mode: 'boolean' }),
  /**
   * Optionaler Geld-/Punkt-Wert einer Conversion (z.B. EUR-Betrag eines Antrags).
   * Für andere Event-Types NULL.
   */
  value: integer('value'),
  /**
   * Frei-formatiges JSON-Metadaten-Bag — z.B. `{ part: 'hp', kind: 'form_submit' }`
   * für Pixel-Conversions oder `{ orderId: '…', payload: {…} }` fürs API-Polling.
   * Für Open/Click-Events typischerweise NULL.
   */
  metaJson: text('meta_json', { mode: 'json' }),
  /**
   * Herkunft des Events — wichtig für Conversions, weil dieselbe Order über
   * Pixel UND Polling reinkommen kann (Pixel triggert sofort beim Page-View,
   * Polling erst beim nächsten Tick). Im Stats-Aggregat wird üblicherweise
   * deduped per (recipientId, kind) — aber das hängt vom Use-Case ab.
   */
  source: text('source', {
    enum: ['pixel', 'api_poll', 'webhook', 'manual', 'system'],
  }),
  createdAt: ts('created_at').notNull().default(now),
}, (t) => ({
  campaignTsIdx: index('events_campaign_ts').on(t.campaignId, t.createdAt),
  messageIdx: index('events_message').on(t.messageId, t.eventType),
  // Phase 3: Multi-Touch-Attribution-Queries — alle Touches eines Recipients
  // chronologisch für ein Conversion-Lookup. Reihenfolge der Spalten ist wichtig
  // damit SQLite den Index für die häufige Variante (eq recipientId + range
  // createdAt + filter eventType IN) nutzen kann.
  recipientEventTsIdx: index('events_recipient_event_ts').on(t.recipientId, t.eventType, t.createdAt),
}));

/**
 * Onsite-Verhaltens-Events (Phase 4). Bewusst getrennt von `tracking_events`:
 * - `tracking_events` hält Email-Pixel/Click/Conversion-Daten — entstehen automatisch
 *   durch Marketing-Mail-Versand (kein separater Consent nötig laut DSGVO/TTDSG).
 * - `behavior_events` hält Verhalten auf Dritt-Seiten (Pageviews, Produkt-Klicks,
 *   Cart-Aktionen) und ist consent-pflichtig. Trennung erleichtert Datenschutz-Audits
 *   und gezielte Löschung wenn ein User Opt-Out macht.
 *
 * Eintrittspunkte:
 *  - `mf-track.js` Snippet auf Workspace-Zielseiten → POST /api/track/behavior
 *  - Shop-/CRM-Webhooks → POST /api/track/webhook/[workspaceSlug]
 */
export const behaviorEvents = sqliteTable('behavior_events', {
  id: id(),
  campaignId: text('campaign_id').notNull().references(() => campaigns.id),
  recipientId: text('recipient_id').notNull().references(() => recipients.id),
  variantId: text('variant_id').references(() => campaignVariants.id),
  type: text('type', {
    enum: ['pageview', 'product_view', 'add_to_cart', 'quote_form', 'scroll_depth', 'time_on_page', 'custom'],
  }).notNull(),
  /** Volle URL (mit Query) auf der das Event passierte. */
  url: text('url'),
  /** Externe Produkt-ID aus dem Shop (für product_view / add_to_cart). */
  productId: text('product_id'),
  /** Optionaler Geld-/Punkt-Wert (z.B. Warenkorb-Wert in EUR-Cent). */
  value: integer('value'),
  /** Aufenthaltsdauer in ms (für time_on_page-Events). */
  durationMs: integer('duration_ms'),
  /** Erreichter Scroll-Prozentsatz 0-100 (für scroll_depth-Events). */
  scrollPct: integer('scroll_pct'),
  /** Frei-formatiges JSON für event-spezifische Felder (formId, sectionLabel, …). */
  metaJson: text('meta_json', { mode: 'json' }),
  userAgent: text('user_agent'),
  ipHash: text('ip_hash'),
  /** Herkunft: 'js' (mf-track.js Beacon) oder 'webhook' (Shop-/CRM-Push). */
  source: text('source', { enum: ['js', 'webhook'] }).notNull(),
  createdAt: ts('created_at').notNull().default(now),
}, (t) => ({
  campaignTsIdx: index('behavior_campaign_ts').on(t.campaignId, t.createdAt),
  recipientIdx: index('behavior_recipient').on(t.recipientId),
  recipientTypeIdx: index('behavior_recipient_type').on(t.recipientId, t.type),
}));

export const assets = sqliteTable('assets', {
  id: id(),
  campaignId: text('campaign_id').references(() => campaigns.id),
  workspaceId: text('workspace_id').references(() => workspaces.id),
  kind: text('kind', { enum: ['hero', 'inline', 'logo'] }).notNull(),
  filePath: text('file_path').notNull(),
  publicUrl: text('public_url').notNull(),
  altText: text('alt_text'),
  promptHash: text('prompt_hash'),
  promptJson: text('prompt_json', { mode: 'json' }),
  width: integer('width'),
  height: integer('height'),
  bytes: integer('bytes'),
  createdAt: ts('created_at').notNull().default(now),
}, (t) => ({
  promptHashIdx: index('assets_prompt_hash').on(t.promptHash),
}));

export const unsubscribes = sqliteTable('unsubscribes', {
  id: id(),
  email: text('email').notNull().unique(),
  campaignId: text('campaign_id').references(() => campaigns.id),
  reason: text('reason'),
  source: text('source', { enum: ['link', 'header', 'manual', 'complaint'] }).notNull(),
  createdAt: ts('created_at').notNull().default(now),
});

export const sessions = sqliteTable('sessions', {
  id: id(),
  userId: text('user_id').notNull().references(() => users.id),
  token: text('token').notNull().unique(),
  expiresAt: ts('expires_at').notNull(),
  createdAt: ts('created_at').notNull().default(now),
});

export const magicLinks = sqliteTable('magic_links', {
  id: id(),
  email: text('email').notNull(),
  token: text('token').notNull().unique(),
  expiresAt: ts('expires_at').notNull(),
  usedAt: ts('used_at'),
  createdAt: ts('created_at').notNull().default(now),
});

/**
 * Trigger-Template = "freigeschaltete" Mail, die externe Systeme (Online-Shop, CRM)
 * per HTTP-API auslösen können (Transactional-Mailer-Pfad).
 *
 * Pattern: pro Template existiert eine versteckte Container-Campaign + 1 Variant
 * mit eingefrorenem MJML/HTML/Subject. Eingehende Trigger-Calls erzeugen einen
 * neuen `recipient` + `email_queue`-Job in dieser Container-Campaign — der Worker
 * personalisiert dann via existierender Pipeline (`customDataJson` → `{{KEY}}`-Tags).
 *
 * Auth: API-Key pro Template (Bearer-Token, SHA-256-Hash + Pepper).
 *  - Hochentropische 256-bit Random-Tokens → SHA-256 ist sicher (kein bcrypt nötig).
 *  - Klartext wird nie persistiert, einmalig im UI nach Erstellung angezeigt.
 *  - `apiKeyPrefix` (erste 10 Zeichen) wird im Klartext gespeichert für UI-Anzeige.
 */
export const triggerTemplates = sqliteTable('trigger_templates', {
  id: id(),
  /** URL-sicherer Slug, wird als Path-Param verwendet: POST /api/trigger/{key}. */
  key: text('key').notNull().unique(),
  name: text('name').notNull(),
  description: text('description'),
  workspaceId: text('workspace_id').notNull().references(() => workspaces.id),
  /** Versteckte Container-Campaign — hält die Empfänger + Queue-Jobs. */
  containerCampaignId: text('container_campaign_id').notNull().references(() => campaigns.id),
  /** Single Variant der Container-Campaign mit eingefrorenem HTML/MJML/Text. */
  containerVariantId: text('container_variant_id').notNull().references(() => campaignVariants.id),
  /** Snapshot der Mail beim Freischalten. Subject + Preheader können {{}}-Tags enthalten. */
  subject: text('subject').notNull(),
  preheader: text('preheader').notNull(),
  mjml: text('mjml').notNull(),
  html: text('html').notNull(),
  htmlText: text('html_text').notNull(),
  /**
   * Auto-extrahiertes Variablen-Schema aus MJML/Subject/Preheader.
   * Shape: Array<{ key: string; required: boolean; defaultValue?: string }>.
   * Eingehende Trigger-Payloads werden gegen dieses Schema validiert.
   */
  variableSchemaJson: text('variable_schema_json', { mode: 'json' }).notNull(),
  /** Optional: expliziter Sender (Email aus M365-Pool). null = Workspace-Default via companyId. */
  senderEmail: text('sender_email'),
  /**
   * SHA-256-Hex-Hash des API-Keys (mit Pepper aus ENV). Klartext wird nie persistiert.
   * Format Pepper: HMAC-SHA-256(key, MAILFORGE_API_KEY_PEPPER) → hex.
   */
  apiKeyHash: text('api_key_hash').notNull(),
  /** Erste 10 Zeichen des Klartext-Keys (z.B. 'mf_live_a3') — UI-Diskriminator. */
  apiKeyPrefix: text('api_key_prefix').notNull(),
  isActive: integer('is_active', { mode: 'boolean' }).notNull().default(true),
  utmDefaultsJson: text('utm_defaults_json', { mode: 'json' }),
  /** Optional pro Stunde: maximale Anzahl Trigger. null = kein Limit. */
  rateLimitPerHour: integer('rate_limit_per_hour'),
  lastTriggeredAt: ts('last_triggered_at'),
  triggerCount: integer('trigger_count').notNull().default(0),
  createdAt: ts('created_at').notNull().default(now),
  updatedAt: ts('updated_at').notNull().default(now),
});

/**
 * Audit-Log jedes Trigger-Calls (auch der abgewiesenen).
 * Wird im UI als "letzte 100 Trigger pro Template" angezeigt.
 *
 * `idempotencyKey`: vom Caller mitgegebener Schlüssel, deduped 24h-Fenster.
 * Doppelte Trigger mit gleichem Key → nicht erneut versendet, aber als 'duplicate' geloggt.
 */
export const triggerLogs = sqliteTable('trigger_logs', {
  id: id(),
  triggerTemplateId: text('trigger_template_id').notNull().references(() => triggerTemplates.id, { onDelete: 'cascade' }),
  recipientEmail: text('recipient_email').notNull(),
  idempotencyKey: text('idempotency_key'),
  status: text('status', {
    enum: ['queued', 'rejected_validation', 'rejected_unsubscribed', 'rejected_rate_limit', 'rejected_auth', 'duplicate'],
  }).notNull(),
  errorMessage: text('error_message'),
  /** Komplettes Request-Body (ohne Authorization-Header) für Debugging. */
  requestPayloadJson: text('request_payload_json', { mode: 'json' }),
  /** Verweis auf den entstandenen email_queue-Job. null bei rejected/duplicate. */
  emailQueueJobId: text('email_queue_job_id'),
  createdAt: ts('created_at').notNull().default(now),
}, (t) => ({
  templateCreatedIdx: index('trigger_logs_template_created').on(t.triggerTemplateId, t.createdAt),
  idempotencyIdx: index('trigger_logs_idempotency').on(t.triggerTemplateId, t.idempotencyKey),
}));

export type User = typeof users.$inferSelect;
export type Workspace = typeof workspaces.$inferSelect;
export type Template = typeof templates.$inferSelect;
export type Campaign = typeof campaigns.$inferSelect;
export type CampaignVariant = typeof campaignVariants.$inferSelect;
export type CampaignVersion = typeof campaignVersions.$inferSelect;
export type CampaignVersionInsert = typeof campaignVersions.$inferInsert;
export type Recipient = typeof recipients.$inferSelect;
export type EmailQueueJob = typeof emailQueue.$inferSelect;
export type TrackingEvent = typeof trackingEvents.$inferSelect;
export type BehaviorEvent = typeof behaviorEvents.$inferSelect;
export type BehaviorEventInsert = typeof behaviorEvents.$inferInsert;
export type Asset = typeof assets.$inferSelect;
export type AppSetting = typeof appSettings.$inferSelect;
export type WorkspaceInsert = typeof workspaces.$inferInsert;
export type SectionTemplate = typeof sectionTemplates.$inferSelect;
export type SectionTemplateInsert = typeof sectionTemplates.$inferInsert;
export type TriggerTemplate = typeof triggerTemplates.$inferSelect;
export type TriggerTemplateInsert = typeof triggerTemplates.$inferInsert;
export type TriggerLog = typeof triggerLogs.$inferSelect;
export type TriggerLogInsert = typeof triggerLogs.$inferInsert;
