// WhatsApp send pipeline. Mirrors the email helpers in visitors.controller
// but writes to the WhatsApp transport stub instead of nodemailer. Each
// public send-* is fire-and-forget from the perspective of the calling
// controller — callers wrap with `.catch(...)` the same way they do for
// the email sends.

import type { Request } from 'express';
import { prisma } from '../config/database';
import { sendWhatsApp } from '../config/whatsappTransport';
import { isGabsConfigured, sendGabsTemplate } from '../config/getgabs';
import { renderTemplate } from './templates';
import { publicBaseFromReq, webAppBaseUrl } from './publicUrl';

type WhatsAppKind = 'VISITOR_INVITE' | 'CHECK_IN_CONFIRMATION' | 'VISITOR_REMINDER' | 'APPROVER_REQUEST';

// Per-template GetGabs config — one entry per WhatsApp automation kind.
// Drives BOTH the automatic sends and the on-demand "pick a template & send"
// picker in the visitor list. Campaign id / approved template name / language
// come from env so each deployment points at its own approved templates.
//   • VISITOR_INVITE is fully wired (approved template gp_entry_qr_templete).
//   • The other three have their params defined (from the current WhatsApp
//     templates) but their campaign id + approved template name arrive later;
//     until then isKindSendable() returns false and the picker disables Send.
export interface WaTemplateParam { label: string; key: string; type?: 'text' | 'date' | 'time'; }
export interface WaTemplateDef {
  label: string;
  /** Visitor statuses this template may be sent for. */
  allowedStatuses: string[];
  campaignEnv: string;
  templateNameEnv: string;
  templateNameDefault: string;
  languageEnv: string;
  languageDefault: string;
  /** When true, the QR PNG is sent as the template HEADER image. */
  hasQrHeader: boolean;
  /** Ordered BODY parameters — label for the UI, key into visitor vars. */
  params: WaTemplateParam[];
}

export const WA_TEMPLATES: Record<WhatsAppKind, WaTemplateDef> = {
  // Approved template "gp_entry_qr_templete": HEADER = QR image; BODY =
  // Name, Invited_by, Reason, Date, Time, Address.
  VISITOR_INVITE: {
    label: 'Invite',
    allowedStatuses: ['EXPECTED'],
    campaignEnv: 'GETGABS_CAMPAIGN_INVITE',
    templateNameEnv: 'GETGABS_TEMPLATE_INVITE',
    templateNameDefault: 'gp_entry_qr_templete',
    languageEnv: 'GETGABS_LANG_INVITE',
    languageDefault: 'en_US',
    hasQrHeader: true,
    params: [
      { label: 'Name', key: 'visitor_name' },
      { label: 'Invited by', key: 'host_name' },
      { label: 'Reason', key: 'reason_for_visit' },
      { label: 'Date', key: 'visit_date', type: 'date' },
      { label: 'Time', key: 'visit_time', type: 'time' },
      { label: 'Address', key: 'organization' },
    ],
  },
  CHECK_IN_CONFIRMATION: {
    label: 'Check-in',
    allowedStatuses: ['EXPECTED'],
    campaignEnv: 'GETGABS_CAMPAIGN_CHECKIN',
    templateNameEnv: 'GETGABS_TEMPLATE_CHECKIN',
    templateNameDefault: '',
    languageEnv: 'GETGABS_LANG_CHECKIN',
    languageDefault: 'en_US',
    hasQrHeader: false,
    params: [
      { label: 'Name', key: 'visitor_name' },
      { label: 'Organization', key: 'organization' },
      { label: 'Host', key: 'host_name' },
      { label: 'Pass ID', key: 'visitor_id' },
    ],
  },
  VISITOR_REMINDER: {
    label: 'Reminder',
    allowedStatuses: ['EXPECTED'],
    campaignEnv: 'GETGABS_CAMPAIGN_REMINDER',
    templateNameEnv: 'GETGABS_TEMPLATE_REMINDER',
    templateNameDefault: '',
    languageEnv: 'GETGABS_LANG_REMINDER',
    languageDefault: 'en_US',
    hasQrHeader: true,
    params: [
      { label: 'Organization', key: 'organization' },
      { label: 'Date', key: 'visit_date', type: 'date' },
      { label: 'Time', key: 'visit_time', type: 'time' },
      { label: 'Host', key: 'host_name' },
      { label: 'Pass ID', key: 'visitor_id' },
    ],
  },
  APPROVER_REQUEST: {
    label: 'Approver request',
    allowedStatuses: ['AWAITING_APPROVAL'],
    campaignEnv: 'GETGABS_CAMPAIGN_APPROVER',
    templateNameEnv: 'GETGABS_TEMPLATE_APPROVER',
    templateNameDefault: '',
    languageEnv: 'GETGABS_LANG_APPROVER',
    languageDefault: 'en_US',
    hasQrHeader: false,
    params: [
      { label: 'Host', key: 'host_name' },
      { label: 'Name', key: 'visitor_name' },
      { label: 'Organization', key: 'organization' },
      { label: 'Reason', key: 'reason_for_visit' },
      { label: 'Mobile', key: 'visitor_mobile' },
    ],
  },
};

const campaignFor = (kind: WhatsAppKind) => (process.env[WA_TEMPLATES[kind].campaignEnv] || '').trim();
const templateNameFor = (kind: WhatsAppKind) => (process.env[WA_TEMPLATES[kind].templateNameEnv] || WA_TEMPLATES[kind].templateNameDefault).trim();
const languageFor = (kind: WhatsAppKind) => (process.env[WA_TEMPLATES[kind].languageEnv] || WA_TEMPLATES[kind].languageDefault).trim();
// A kind can actually be dispatched only when the provider is configured AND
// this template's campaign id + approved template name are known.
const isKindSendable = (kind: WhatsAppKind) => isGabsConfigured() && !!campaignFor(kind) && !!templateNameFor(kind);

function buildBodyParams(kind: WhatsAppKind, vars: Record<string, string>): string[] {
  return WA_TEMPLATES[kind].params.map((p) => {
    // Address has no dedicated field — allow a static env override, else org.
    if (kind === 'VISITOR_INVITE' && p.label === 'Address') {
      return process.env.GETGABS_INVITE_ADDRESS || vars.organization || '';
    }
    return vars[p.key] ?? '';
  });
}

// Try to dispatch `kind` via the GetGabs approved-template transport. Returns
// true when it handled the send, false when the caller should fall back to the
// free-text transport (kind not sendable yet).
async function trySendViaGabs(
  kind: WhatsAppKind,
  toNumber: string,
  receiverName: string,
  vars: Record<string, string>,
): Promise<boolean> {
  if (!isKindSendable(kind)) return false;
  const d = WA_TEMPLATES[kind];
  await sendGabsTemplate({
    to: toNumber,
    receiverName,
    campaignId: campaignFor(kind),
    templateName: templateNameFor(kind),
    languageCode: languageFor(kind),
    headerImageUrl: d.hasQrHeader ? vars.qr_code : undefined,
    bodyParams: buildBodyParams(kind, vars),
  });
  return true;
}

interface WAVisitor {
  id?: string;
  name: string;
  email: string | null;
  mobile: string | null;
  shortId: string;
  visitDate: Date | null;
  visitTime: string | null;
  notes: string | null;
  reasonForVisit: string | null;
  createdByAdminId?: string | null;
  assignedAdminId?: string | null;
  assignedApproverId?: string | null;
}

// Workspace-level master gate. Returns false if the row doesn't exist (the
// owner hasn't visited the WhatsApp settings yet) so we err on the side of
// "no surprises" for fresh workspaces.
async function isAutomationOn(ownerId: string, kind: WhatsAppKind): Promise<boolean> {
  const row = await prisma.whatsAppAutomation.findUnique({ where: { ownerId } });
  if (!row) return false;
  if (kind === 'VISITOR_INVITE') return row.inviteEnabled;
  if (kind === 'CHECK_IN_CONFIRMATION') return row.checkInEnabled;
  if (kind === 'VISITOR_REMINDER') return row.reminderEnabled;
  return row.approverRequestEnabled;
}

// Common variable set used by the three visitor-facing templates. Approver
// request adds its own decision URLs on top.
//
// `req` is optional but recommended. When passed, the QR / pass URLs fall
// back to the request's public origin if no APP_URL / API_PUBLIC_URL env
// is set — so production deployments work out-of-the-box without manual
// env config and the recipient's "Your QR" link is always a real clickable
// URL, not a localhost or relative one.
export async function visitorVars(ownerId: string, visitor: WAVisitor, req?: Request) {
  let hostName = '';
  let hostEmail = '';
  if (visitor.createdByAdminId) {
    const adminHost = await prisma.admin.findUnique({
      where: { id: visitor.createdByAdminId },
      select: { name: true, email: true },
    });
    if (adminHost) {
      hostName = adminHost.name || adminHost.email.split('@')[0];
      hostEmail = adminHost.email;
    }
  }
  let orgName = '';
  if (!hostName || !orgName) {
    const owner = await prisma.owner.findUnique({ where: { id: ownerId }, select: { name: true, email: true } });
    if (!hostName) {
      hostName = owner?.name || owner?.email?.split('@')[0] || 'Your host';
      hostEmail = owner?.email || '';
    }
    orgName = owner?.name || owner?.email?.split('@')[0] || '';
  }
  const visitDateStr = visitor.visitDate
    ? new Date(visitor.visitDate).toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' })
    : '';
  const visitTimeStr = visitor.visitTime ? `${visitor.visitTime}` : '';
  // Resolve absolute origins for the recipient-facing links. Priority:
  //   1. Explicit env (APP_URL / API_PUBLIC_URL) — usually set in prod.
  //   2. The current request's public origin (honors X-Forwarded-* via
  //      Express's `trust proxy`) — so a freshly deployed box works
  //      without any env config.
  //   3. Localhost fallback — only used in dev / cron jobs without a req.
  const liveBase = publicBaseFromReq(req);
  const appUrl = (process.env.APP_URL || liveBase || 'http://localhost:3200').replace(/\/$/, '');
  // The QR header image MUST be a public HTTPS URL that WhatsApp/Meta can
  // fetch — a localhost URL (from the request host in local dev) fails with
  // "#100 Invalid parameter". Prefer the configured public API origin. NOTE:
  // the env var is PUBLIC_API_URL (API_PUBLIC_URL kept for backward compat).
  const apiUrl = (process.env.PUBLIC_API_URL || liveBase || appUrl).replace(/\/$/, '');
  const passUrl = `${appUrl}/pass/${visitor.shortId}`;
  // QR is served from a public endpoint so the WhatsApp recipient can tap
  // the link and view the PNG in their browser. WhatsApp doesn't render
  // inline images from a templated text message — this is the practical
  // equivalent.
  const qrUrl = `${apiUrl}/public/qr/${visitor.shortId}.png`;
  return {
    visitor_name: visitor.name,
    visitor_email: visitor.email || '',
    visitor_mobile: visitor.mobile || '',
    visitor_id: visitor.shortId,
    visit_date: visitDateStr,
    visit_time: visitTimeStr,
    visit_notes: visitor.notes || '',
    reason_for_visit: visitor.reasonForVisit || '',
    host_name: hostName,
    host_email: hostEmail,
    organization: orgName,
    pass_url: passUrl,
    qr_code: qrUrl,
    approve_url: '',
    reject_url: '',
  };
}

// {{qr_code}} is special-cased: instead of interpolating the URL into the
// text, we strip the whole line that contains it and pass the URL as a
// media attachment. A real provider will render the PNG inline; our stub
// just logs it. Author writes templates naturally ("*Your QR:* {{qr_code}}")
// and the recipient sees an image, not a raw link.
export function extractQrAttachment(body: string, qrUrl: string): { body: string; mediaUrl?: string } {
  if (!/\{\{\s*qr_code\s*\}\}/.test(body)) return { body };
  const stripped = body
    .split('\n')
    .filter((line) => !/\{\{\s*qr_code\s*\}\}/.test(line))
    .join('\n')
    .replace(/\n{3,}/g, '\n\n')
    .replace(/^\n+|\n+$/g, '');
  return { body: stripped, mediaUrl: qrUrl };
}

async function buildAndSend(ownerId: string, kind: WhatsAppKind, visitor: WAVisitor, req?: Request): Promise<void> {
  if (!visitor.mobile) return; // can't WhatsApp without a number
  // Workspace-level toggle in the WhatsApp templates section is the gate —
  // nothing is sent unless the owner turned this template on.
  if (!(await isAutomationOn(ownerId, kind))) return;
  const vars = await visitorVars(ownerId, visitor, req);
  // Preferred path: GetGabs approved-template send (when this kind has a
  // campaign configured). The QR PNG is passed as the HEADER image link.
  if (await trySendViaGabs(kind, visitor.mobile, visitor.name, vars as any)) return;
  // Fallback path: free-text transport using the workspace's editable
  // template body (dev stub until GetGabs is configured).
  const tpl = await prisma.whatsAppTemplate.findUnique({
    where: { ownerId_type: { ownerId, type: kind } },
  });
  if (!tpl) return;
  const { body: bodyNoQr, mediaUrl } = extractQrAttachment(tpl.body, vars.qr_code);
  const body = renderTemplate(bodyNoQr, vars as any);
  await sendWhatsApp({ to: visitor.mobile, body, mediaUrl });
}

export async function sendVisitorInviteWA(ownerId: string, visitor: WAVisitor, req?: Request): Promise<void> {
  return buildAndSend(ownerId, 'VISITOR_INVITE', visitor, req);
}
export async function sendCheckInConfirmationWA(ownerId: string, visitor: WAVisitor, req?: Request): Promise<void> {
  return buildAndSend(ownerId, 'CHECK_IN_CONFIRMATION', visitor, req);
}
export async function sendVisitReminderWA(ownerId: string, visitor: WAVisitor, req?: Request): Promise<void> {
  return buildAndSend(ownerId, 'VISITOR_REMINDER', visitor, req);
}

// Approver request goes to the assigned admin/approver, not the visitor.
// We look up their phone and mint the same decision URLs the email side
// uses so a single click flows through the existing /decide/<token> route.
export async function sendApproverRequestWA(
  ownerId: string,
  visitor: WAVisitor,
  req?: Request,
): Promise<void> {
  const kind: WhatsAppKind = 'APPROVER_REQUEST';
  if (!(await isAutomationOn(ownerId, kind))) return;
  const tpl = await prisma.whatsAppTemplate.findUnique({
    where: { ownerId_type: { ownerId, type: kind } },
  });
  if (!tpl) return;

  let approverName = '';
  let approverPhone = '';
  if (visitor.assignedAdminId) {
    const a = await prisma.admin.findUnique({
      where: { id: visitor.assignedAdminId },
      select: { name: true, email: true, phone: true },
    });
    if (a) { approverName = a.name || a.email.split('@')[0]; approverPhone = a.phone || ''; }
  }
  if (!approverPhone && visitor.assignedApproverId) {
    const a = await prisma.approver.findUnique({
      where: { id: visitor.assignedApproverId },
      select: { name: true, email: true, phone: true },
    });
    if (a) { approverName = a.name || a.email.split('@')[0]; approverPhone = a.phone || ''; }
  }
  if (!approverPhone) return;

  // Reuse the existing decisionToken if already minted (the email send
  // path persists it on the visitor row when it fires first). Otherwise
  // mint one so the WhatsApp message can carry valid Approve / Reject URLs
  // even when WhatsApp is the only configured channel.
  let decisionToken: string | null = null;
  if (visitor.id) {
    const current = await prisma.visitor.findUnique({
      where: { id: visitor.id },
      select: { decisionToken: true },
    });
    decisionToken = current?.decisionToken ?? null;
    if (!decisionToken) {
      const { nanoid } = await import('nanoid');
      decisionToken = nanoid(32);
      await prisma.visitor.update({ where: { id: visitor.id }, data: { decisionToken } });
    }
  }
  // Web-app origin for the /decide links — resolved per-environment so the
  // WhatsApp Approve / Reject links use the real domain, not localhost.
  const appUrl = webAppBaseUrl(req);
  const approveUrl = decisionToken ? `${appUrl}/decide/${decisionToken}?action=approve` : '';
  const rejectUrl = decisionToken ? `${appUrl}/decide/${decisionToken}?action=reject` : '';

  const baseVars = await visitorVars(ownerId, visitor, req);
  const vars = {
    ...baseVars,
    // {{host_name}} in this template addresses the recipient (the
    // approver). Aligns with the email side's convention so authors don't
    // have to context-switch when editing the WhatsApp version.
    host_name: approverName,
    approve_url: approveUrl,
    reject_url: rejectUrl,
  };
  const { body: bodyNoQr, mediaUrl } = extractQrAttachment(tpl.body, vars.qr_code);
  const body = renderTemplate(bodyNoQr, vars as any);
  await sendWhatsApp({ to: approverPhone, body, mediaUrl });
}

// ─── On-demand template picker (visitor list WhatsApp icon) ─────────────────
// Powers the "select a template & send to this visitor" flow. A template is
// offered only when (a) its toggle is ON in the WhatsApp settings AND (b) the
// visitor's current status is allowed for that template.

export type WhatsAppSendKind = WhatsAppKind;

export interface SendableTemplate {
  type: WhatsAppKind;
  label: string;
  /** Prefilled BODY parameters shown to the operator before sending. */
  params: { label: string; value: string }[];
  /** Whether the QR image rides along as the template header. */
  qrIncluded: boolean;
  /** False when the campaign/template isn't configured yet (Send disabled). */
  sendable: boolean;
}

// Templates available for a visitor given the toggle + status gates.
export async function listSendableTemplatesForVisitor(
  ownerId: string,
  visitor: WAVisitor,
  status: string,
  req?: Request,
): Promise<SendableTemplate[]> {
  const vars = await visitorVars(ownerId, visitor, req);
  const out: SendableTemplate[] = [];
  for (const kind of Object.keys(WA_TEMPLATES) as WhatsAppKind[]) {
    const d = WA_TEMPLATES[kind];
    if (!d.allowedStatuses.includes(status)) continue;   // status gate
    if (!(await isAutomationOn(ownerId, kind))) continue; // toggle gate
    out.push({
      type: kind,
      label: d.label,
      params: buildBodyParams(kind, vars as any).map((value, i) => ({ label: d.params[i].label, value, type: d.params[i].type || 'text' })),
      qrIncluded: d.hasQrHeader,
      sendable: isKindSendable(kind),
    });
  }
  return out;
}

// Dispatch a specific template to a visitor on demand. Re-validates the toggle
// + status gates server-side so a stale UI can't bypass them.
export async function sendTemplateForVisitor(
  ownerId: string,
  visitor: WAVisitor,
  kind: WhatsAppKind,
  status: string,
  req?: Request,
  toOverride?: string,
  paramsOverride?: string[],
): Promise<{ ok: boolean; error?: string }> {
  const d = WA_TEMPLATES[kind];
  console.log(`[WA/send] request: kind=${kind} visitor=${visitor.shortId} status=${status} to=${toOverride || visitor.mobile || '(none)'}`);
  const block = (error: string) => { console.warn(`[WA/send] blocked (${kind}): ${error}`); return { ok: false as const, error }; };
  if (!d) return block('Unknown template.');
  if (!d.allowedStatuses.includes(status)) {
    return block(`${d.label} can only be sent to ${d.allowedStatuses.join(' / ')} visitors.`);
  }
  if (!(await isAutomationOn(ownerId, kind))) {
    return block(`${d.label} is turned off in WhatsApp settings.`);
  }
  // Recipient: the operator can verify/edit the number in the picker, so an
  // explicit override wins over the visitor's stored mobile.
  const to = (toOverride || visitor.mobile || '').trim();
  if (!to) return block('A recipient mobile number is required.');
  if (!isKindSendable(kind)) {
    return block(`${d.label} isn't configured for sending yet (provider or campaign not set).`);
  }
  const vars = await visitorVars(ownerId, visitor, req);
  // Body params: use the operator's edited values when supplied (must match
  // the template's expected count), else the prefilled values from visitor data.
  const bodyParams = Array.isArray(paramsOverride) && paramsOverride.length === d.params.length
    ? paramsOverride.map((v) => String(v ?? ''))
    : buildBodyParams(kind, vars as any);
  const result = await sendGabsTemplate({
    to,
    receiverName: visitor.name,
    campaignId: campaignFor(kind),
    templateName: templateNameFor(kind),
    languageCode: languageFor(kind),
    headerImageUrl: d.hasQrHeader ? vars.qr_code : undefined,
    bodyParams,
  });
  if (!result.ok) return { ok: false, error: 'WhatsApp provider rejected the message.' };
  return { ok: true };
}
