/**
 * Email policy (2026-09-22): two streams, category preferences, delivery
 * suppression, bounce rules. Pure — the central sender and the webhook
 * processor call these; nothing here does I/O.
 *
 *   transactional  info@      essential account + operational mail
 *   updates        updates@   campaigns, invitations, news, promotions
 *
 * "Marketing unsubscribe is category-specific. Hard bounce is delivery-wide."
 */

export type EmailStream = 'transactional' | 'updates'

export type EmailCategoryKey =
  | 'account_security' | 'company_verification' | 'billing_notifications'
  | 'trip_operational' | 'permit_notifications' | 'route_notifications'
  | 'product_updates' | 'newsletter' | 'marketing_campaigns'
  | 'pilot_opportunities' | 'broker_opportunities' | 'carrier_opportunities'

export interface EmailCategory {
  key: EmailCategoryKey
  stream: EmailStream
  label: string
  /** Cannot be turned off — needed to operate the account. */
  required: boolean
  /** Column on email_preferences, when the category can be turned off. */
  preferenceField: keyof EmailPreferences | null
  description: string
}

export interface EmailPreferences {
  account_security: boolean
  trip_operational: boolean
  permit_notifications: boolean
  route_notifications: boolean
  billing_notifications: boolean
  product_updates: boolean
  newsletter: boolean
  marketing_campaigns: boolean
  pilot_opportunities: boolean
  broker_opportunities: boolean
  carrier_opportunities: boolean
}

export const DEFAULT_PREFERENCES: EmailPreferences = {
  account_security: true, trip_operational: true, permit_notifications: true, route_notifications: true, billing_notifications: true,
  product_updates: true, newsletter: true, marketing_campaigns: true, pilot_opportunities: true, broker_opportunities: true, carrier_opportunities: true,
}

export const EMAIL_CATEGORIES: EmailCategory[] = [
  { key: 'account_security', stream: 'transactional', label: 'Account & Security', required: true, preferenceField: null, description: 'Password resets, verification, sign-in alerts. Required.' },
  { key: 'company_verification', stream: 'transactional', label: 'Company verification', required: true, preferenceField: null, description: 'Relationship requests, approvals, invitations to a company. Required.' },
  { key: 'billing_notifications', stream: 'transactional', label: 'Billing', required: false, preferenceField: 'billing_notifications', description: 'Invoices, receipts, plan changes.' },
  { key: 'trip_operational', stream: 'transactional', label: 'Trip notifications', required: false, preferenceField: 'trip_operational', description: 'Trip created, assignments, paperwork requests, trip activity.' },
  { key: 'permit_notifications', stream: 'transactional', label: 'Permit notifications', required: false, preferenceField: 'permit_notifications', description: 'Permit uploaded, permit updated, permit orders.' },
  { key: 'route_notifications', stream: 'transactional', label: 'Route notifications', required: false, preferenceField: 'route_notifications', description: 'Route ready, route attached.' },
  { key: 'product_updates', stream: 'updates', label: 'HeavyHaul Agent product updates', required: false, preferenceField: 'product_updates', description: 'New features and improvements.' },
  { key: 'newsletter', stream: 'updates', label: 'News & announcements', required: false, preferenceField: 'newsletter', description: 'Industry and company news.' },
  { key: 'marketing_campaigns', stream: 'updates', label: 'Marketing & promotions', required: false, preferenceField: 'marketing_campaigns', description: 'Offers, upgrades, campaigns.' },
  { key: 'pilot_opportunities', stream: 'updates', label: 'Pilot job alerts & invitations', required: false, preferenceField: 'pilot_opportunities', description: 'Pilot car opportunities and invitations.' },
  { key: 'broker_opportunities', stream: 'updates', label: 'Freight broker invitations', required: false, preferenceField: 'broker_opportunities', description: 'Invitations and opportunities for freight brokers.' },
  { key: 'carrier_opportunities', stream: 'updates', label: 'Carrier invitations', required: false, preferenceField: 'carrier_opportunities', description: 'Invitations and opportunities for carriers.' },
]

export function categoryByKey(key: string | null | undefined): EmailCategory | null {
  return EMAIL_CATEGORIES.find((c) => c.key === key) ?? null
}

/** Which category each shipped template belongs to. A template's stream follows its category. */
export const TEMPLATE_CATEGORY: Record<string, EmailCategoryKey> = {
  // transactional — account & security
  email_verification: 'account_security',
  email_code: 'account_security',
  login_email_verification: 'account_security',
  moderator_access_invitation: 'account_security',
  broker_intake_invitation: 'trip_operational',
  // transactional — company relationships (account-level, required)
  company_access_approval: 'company_verification',
  company_relationship_requested: 'company_verification',
  company_relationship_approved: 'company_verification',
  company_relationship_rejected: 'company_verification',
  company_relationship_revoked: 'company_verification',
  company_verification_reminder: 'company_verification',
  company_dispatcher_invitation: 'company_verification',
  company_dispatcher_bulk_invitation: 'company_verification',
  broker_relationship_verification_request: 'company_verification',
  broker_relationship_verified: 'company_verification',
  broker_relationship_rejected: 'company_verification',
  broker_relationship_reminder: 'company_verification',
  broker_relationship_revoked: 'company_verification',
  broker_company_admin_invitation: 'company_verification',
  broker_company_invitation: 'company_verification',
  broker_company_bulk_invitation: 'company_verification',
  // Auto-Participants (2026-09-30): consent requests always reach the person; the "added" notice is operational (§24).
  auto_participant_request: 'account_security',
  auto_participant_company_request: 'account_security',
  auto_participant_added: 'trip_operational',
  // transactional — operational
  pilot_paperwork_request: 'trip_operational',
  pilot_dispatch_trip_invite: 'trip_operational',
  pilot_additional_trip_invite: 'trip_operational',
  carrier_upload_request: 'permit_notifications',
  synchron_processing_request: 'permit_notifications',
  permit_reorder_request: 'permit_notifications',
  permit_dimension_alert: 'permit_notifications',
  pilot_invoice: 'billing_notifications',
  // updates — acquisition campaigns
  broker_lead_invitation: 'broker_opportunities',
  broker_lead_reminder: 'broker_opportunities',
  broker_lead_final_notice: 'broker_opportunities',
  carrier_lead_invitation: 'carrier_opportunities',
  carrier_lead_reminder: 'carrier_opportunities',
  carrier_lead_final_notice: 'carrier_opportunities',
  pilot_invitation_initial: 'pilot_opportunities',
  pilot_invitation_follow_up: 'pilot_opportunities',
}

export interface TemplateMeta { stream: EmailStream; category: EmailCategoryKey; required: boolean }

/** Stream/category/required for a template: the saved override wins, then the shipped map, then a safe transactional default. */
export function resolveTemplateMeta(key: string, saved?: { email_stream?: string | null; category_key?: string | null; required?: boolean | null } | null): TemplateMeta {
  const cat = categoryByKey(saved?.category_key) ?? categoryByKey(TEMPLATE_CATEGORY[key]) ?? categoryByKey('trip_operational')!
  // A dimension mismatch is a safety/operational alert and cannot be disabled
  // with optional permit-notification preferences.
  const required = key === 'permit_dimension_alert' ? true : (saved?.required ?? cat.required)
  return { stream: cat.stream, category: cat.key, required }
}

/** §28: reject combinations that would send marketing through the transactional agent or vice versa. */
export function validateTemplateMeta(input: { email_stream: string; category_key: string; required: boolean }): { ok: true } | { ok: false; error: string } {
  const cat = categoryByKey(input.category_key)
  if (!cat) return { ok: false, error: 'Unknown email category.' }
  if (cat.stream !== input.email_stream) return { ok: false, error: `"${cat.label}" belongs to the ${cat.stream} stream and cannot be sent through ${input.email_stream}.` }
  if (input.required && cat.stream === 'updates') return { ok: false, error: 'An updates / marketing template can never be required — every recipient must be able to unsubscribe.' }
  return { ok: true }
}

/* ---------------- pre-send decision (§31) ---------------- */

export interface SuppressionLike { scope: 'global' | 'marketing' | 'campaign_type'; campaign_type: string | null; expires_at: string | null; overridden_at: string | null }
export interface DeliveryStateLike { status: 'ok' | 'soft_bouncing' | 'hard_bounced' }

export type SendDecision = { allowed: true } | { allowed: false; reason: 'global_suppression' | 'hard_bounced' | 'marketing_suppression' | 'campaign_suppression' | 'preference_off' | 'template_inactive' }

export function preSendDecision(params: {
  meta: TemplateMeta
  templateActive: boolean
  preferences: EmailPreferences | null
  suppressions: SuppressionLike[]
  delivery: DeliveryStateLike | null
  campaignType?: string | null
  now?: number
}): SendDecision {
  const now = params.now ?? Date.now()
  if (!params.templateActive) return { allowed: false, reason: 'template_inactive' }
  const active = params.suppressions.filter((s) => !s.overridden_at && (!s.expires_at || new Date(s.expires_at).getTime() > now))
  // Delivery-wide: nothing goes to a dead mailbox, transactional included.
  if (active.some((s) => s.scope === 'global')) return { allowed: false, reason: 'global_suppression' }
  if (params.delivery?.status === 'hard_bounced') return { allowed: false, reason: 'hard_bounced' }
  if (params.meta.stream === 'transactional') {
    // Marketing opt-outs never touch transactional mail; optional operational categories honour the preference.
    const cat = categoryByKey(params.meta.category)!
    if (!params.meta.required && cat.preferenceField && params.preferences && params.preferences[cat.preferenceField] === false) return { allowed: false, reason: 'preference_off' }
    return { allowed: true }
  }
  // updates stream
  if (active.some((s) => s.scope === 'marketing')) return { allowed: false, reason: 'marketing_suppression' }
  if (params.campaignType && active.some((s) => s.scope === 'campaign_type' && s.campaign_type === params.campaignType)) return { allowed: false, reason: 'campaign_suppression' }
  const cat = categoryByKey(params.meta.category)!
  if (cat.preferenceField && params.preferences && params.preferences[cat.preferenceField] === false) return { allowed: false, reason: 'preference_off' }
  return { allowed: true }
}

/** "Unsubscribe from updates" turns every updates category off and leaves transactional untouched (§35). */
export function unsubscribeFromUpdates(prefs: EmailPreferences): EmailPreferences {
  return { ...prefs, product_updates: false, newsletter: false, marketing_campaigns: false, pilot_opportunities: false, broker_opportunities: false, carrier_opportunities: false }
}

/* ---------------- webhook normalisation + bounce rules ---------------- */

export type NormalizedEventType = 'delivered' | 'soft_bounce' | 'hard_bounce' | 'opened' | 'clicked' | 'spam_complaint' | 'other'

export function normalizeEventType(rawName: string): NormalizedEventType {
  const n = rawName.toLowerCase().replace(/[^a-z]/g, '')
  if (n.includes('hard')) return 'hard_bounce'
  if (n.includes('soft')) return 'soft_bounce'
  if (n.includes('complaint') || n.includes('spam') || n.includes('abuse') || n.includes('feedback')) return 'spam_complaint'
  if (n.includes('bounce')) return 'hard_bounce'
  if (n.includes('deliver')) return 'delivered'
  if (n.includes('click')) return 'clicked'
  if (n.includes('open')) return 'opened'
  return 'other'
}

export interface SoftBouncePolicy { threshold: number; windowDays: number; suppressDays: number }
export const DEFAULT_SOFT_BOUNCE_POLICY: SoftBouncePolicy = { threshold: 3, windowDays: 14, suppressDays: 7 }

/** §12: record the first soft bounces; repeated ones inside the window suppress delivery temporarily. */
export function softBounceDecision(state: { soft_bounce_count: number; first_soft_bounce_at: string | null }, policy: SoftBouncePolicy, now = Date.now()): { count: number; firstAt: string; suppress: boolean } {
  const first = state.first_soft_bounce_at ? new Date(state.first_soft_bounce_at).getTime() : now
  const inWindow = now - first <= policy.windowDays * 86_400_000
  const count = inWindow ? state.soft_bounce_count + 1 : 1
  return { count, firstAt: inWindow && state.first_soft_bounce_at ? state.first_soft_bounce_at : new Date(now).toISOString(), suppress: count >= policy.threshold }
}

/** §30: pause a campaign whose hard-bounce rate crosses the line (after a minimum sample). */
export function campaignPauseDecision(c: { sent_count: number; hard_bounce_count: number }, policy: { minSent: number; pausePct: number }): boolean {
  if (c.sent_count < policy.minSent) return false
  return (c.hard_bounce_count / c.sent_count) * 100 >= policy.pausePct
}

/* ---------------- streams ---------------- */

export const STREAM_SENDERS: Record<EmailStream, { name: string; defaultFrom: string; agent: string }> = {
  transactional: { name: 'HeavyHaul Agent', defaultFrom: 'info@heavyhaulagent.com', agent: 'HHA Transactional' },
  updates: { name: 'HeavyHaul Agent Updates', defaultFrom: 'updates@heavyhaulagent.com', agent: 'HHA Updates' },
}
