import { Response } from 'express';
import { prisma } from '../config/database';
import { AuthRequest } from '../middleware/auth';
import {
  renderTemplate, VISITOR_INVITE_SAMPLE,
  DEFAULT_VISITOR_INVITE, DEFAULT_CHECK_IN_CONFIRMATION, DEFAULT_VISITOR_REMINDER,
  DEFAULT_APPROVER_REQUEST,
  KNOWN_VISITOR_INVITE_KEYS, APPROVER_REQUEST_EXTRA_KEYS,
} from '../lib/templates';
import { publicBaseFromReq } from '../lib/publicUrl';

// All four transactional email kinds share the same variable surface, so
// the editor UI works for any of them unchanged.
const SUPPORTED_TYPES = ['VISITOR_INVITE', 'CHECK_IN_CONFIRMATION', 'VISITOR_REMINDER', 'APPROVER_REQUEST'] as const;
type SupportedType = typeof SUPPORTED_TYPES[number];

function defaultsFor(type: SupportedType) {
  if (type === 'VISITOR_INVITE') return DEFAULT_VISITOR_INVITE;
  if (type === 'CHECK_IN_CONFIRMATION') return DEFAULT_CHECK_IN_CONFIRMATION;
  if (type === 'VISITOR_REMINDER') return DEFAULT_VISITOR_REMINDER;
  if (type === 'APPROVER_REQUEST') return DEFAULT_APPROVER_REQUEST;
  return { subject: '', body: '' };
}

function variablesFor(type: SupportedType): string[] {
  // Visitor-facing emails get the full variable set (incl. qr_code).
  // Approver-facing email drops qr_code (the host doesn't need to scan
  // anything) but adds the one-shot decision URLs that drive the
  // Approve / Reject email buttons.
  if (type === 'APPROVER_REQUEST') {
    const base = (KNOWN_VISITOR_INVITE_KEYS as unknown as string[]).filter((k) => k !== 'qr_code');
    return [...base, ...(APPROVER_REQUEST_EXTRA_KEYS as unknown as string[])];
  }
  return KNOWN_VISITOR_INVITE_KEYS as unknown as string[];
}

async function ensureDefault(ownerId: string, type: SupportedType) {
  let tpl = await prisma.emailTemplate.findUnique({
    where: { ownerId_type: { ownerId, type } },
  });
  if (!tpl) {
    const d = defaultsFor(type);
    tpl = await prisma.emailTemplate.create({
      data: { ownerId, type, subject: d.subject, body: d.body },
    });
  }
  return tpl;
}

export async function listEmailTemplates(req: AuthRequest, res: Response) {
  try {
    const templates = await Promise.all(
      SUPPORTED_TYPES.map((t) => ensureDefault(req.ownerId!, t).then((tpl) => ({
        ...tpl,
        variables: variablesFor(t),
      })))
    );
    res.json(templates);
  } catch (error) {
    console.error('listEmailTemplates', error);
    res.status(500).json({ error: 'Failed to fetch email templates' });
  }
}

// Accept only `#rgb` / `#rrggbb` / `#rrggbbaa` so a malformed payload can't
// break the email shell's inline styles when the value gets interpolated
// directly into a `background-color:` declaration.
function sanitizeColor(v: unknown): string | undefined {
  return typeof v === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(v) ? v : undefined;
}

export async function updateEmailTemplate(req: AuthRequest, res: Response) {
  try {
    const type = req.params.type as SupportedType;
    if (!SUPPORTED_TYPES.includes(type)) {
      res.status(400).json({ error: 'Unknown template type' });
      return;
    }
    const { subject, body, designJson, style } = req.body;
    if (typeof subject !== 'string' || typeof body !== 'string') {
      res.status(400).json({ error: 'Subject and body are required' });
      return;
    }
    await ensureDefault(req.ownerId!, type);
    // designJson is the legacy Unlayer designer's source-of-truth tree; we
    // accept anything truthy as-is and explicit null to clear it. `style`
    // is the new lightweight color-override surface; it's merged into the
    // designJson object so we don't add a separate column for one shape.
    const data: { subject: string; body: string; designJson?: any } = { subject, body };
    if (style && typeof style === 'object') {
      const next: { style: Record<string, string> } = { style: {} };
      const hb = sanitizeColor(style.headerBg);
      const ht = sanitizeColor(style.headerText);
      const ib = sanitizeColor(style.infoBoxBg);
      if (hb) next.style.headerBg = hb;
      if (ht) next.style.headerText = ht;
      if (ib) next.style.infoBoxBg = ib;
      // Preserve any other keys (e.g. Unlayer's design tree) that already
      // live on designJson so the new style merge doesn't clobber them.
      const existing = await prisma.emailTemplate.findUnique({
        where: { ownerId_type: { ownerId: req.ownerId!, type } },
        select: { designJson: true },
      });
      const prev = (existing?.designJson && typeof existing.designJson === 'object') ? existing.designJson as Record<string, unknown> : {};
      data.designJson = { ...prev, ...next };
    } else if (designJson !== undefined) {
      data.designJson = designJson;
    }
    const tpl = await prisma.emailTemplate.update({
      where: { ownerId_type: { ownerId: req.ownerId!, type } },
      data,
    });
    res.json({ ...tpl, variables: variablesFor(type) });
  } catch (error) {
    console.error('updateEmailTemplate', error);
    res.status(500).json({ error: 'Failed to update email template' });
  }
}

// Reset the row to the shipped defaults (subject + body + cleared
// designJson). Lets owners refresh in-place when shipped defaults change
// without us needing a one-off migration script.
export async function restoreEmailTemplateDefaults(req: AuthRequest, res: Response) {
  try {
    const type = req.params.type as SupportedType;
    if (!SUPPORTED_TYPES.includes(type)) {
      res.status(400).json({ error: 'Unknown template type' });
      return;
    }
    await ensureDefault(req.ownerId!, type);
    const d = defaultsFor(type);
    const tpl = await prisma.emailTemplate.update({
      where: { ownerId_type: { ownerId: req.ownerId!, type } },
      data: { subject: d.subject, body: d.body, designJson: null as any },
    });
    res.json({ ...tpl, variables: variablesFor(type) });
  } catch (error) {
    console.error('restoreEmailTemplateDefaults', error);
    res.status(500).json({ error: 'Failed to restore defaults' });
  }
}

// Renders the saved email template against a real visitor and returns the
// rendered subject + plain-text body. Used by the Add Visitor share modal
// so the `mailto:` link pre-fills with the same body the owner edited in
// Templates → Email → Invite (variables resolved). Mailto can only carry
// plain text, so the saved HTML body is stripped down to text before
// being returned.
//
// Authenticated — the visitor must belong to the caller's workspace.
export async function renderEmailTemplateForVisitor(req: AuthRequest, res: Response) {
  try {
    const type = req.params.type as SupportedType;
    if (!SUPPORTED_TYPES.includes(type)) {
      res.status(400).json({ error: 'Unknown template type' });
      return;
    }
    const visitorId = req.params.visitorId;
    const visitor = await prisma.visitor.findFirst({
      where: { id: visitorId, ownerId: req.ownerId! },
      select: {
        id: true, name: true, email: true, mobile: true, shortId: true,
        visitDate: true, visitTime: true, notes: true, reasonForVisit: true,
        createdByAdminId: true,
      },
    });
    if (!visitor) { res.status(404).json({ error: 'Visitor not found' }); return; }

    const tpl = await prisma.emailTemplate.findUnique({
      where: { ownerId_type: { ownerId: req.ownerId!, type } },
    });
    if (!tpl) { res.status(404).json({ error: 'Template not configured' }); return; }

    // Resolve host the same way buildAndSendVisitorEmail does — admin
    // creator first, else workspace owner — so the rendered body matches
    // what an actual sent email would say.
    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;
      }
    }
    if (!hostName) {
      const owner = await prisma.owner.findUnique({ where: { id: req.ownerId! }, select: { name: true, email: true } });
      hostName = owner?.name || owner?.email?.split('@')[0] || 'Your host';
      hostEmail = owner?.email || '';
    }
    const visitDateStr = visitor.visitDate
      ? new Date(visitor.visitDate).toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' })
      : '';
    const visitTimeStr = visitor.visitTime ? ` at ${visitor.visitTime}` : '';
    // QR link as plain URL — mailto can't attach files; the recipient
    // taps the link and views the PNG in their browser. Priority for the
    // origin: explicit env → live request origin → empty (relative).
    // The live-request fallback means a fresh prod box works without
    // env config and the link is always a real clickable absolute URL.
    const apiBase = (process.env.API_PUBLIC_URL || process.env.APP_URL || publicBaseFromReq(req) || '').replace(/\/$/, '');
    const qrUrl = apiBase ? `${apiBase}/api/public/qr/${visitor.shortId}.png` : `/api/public/qr/${visitor.shortId}.png`;

    const vars = {
      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,
      // For plain-text rendering we resolve these inline instead of using
      // the HTML sentinels the send pipeline relies on.
      visit_details: [
        visitDateStr ? `When: ${visitDateStr}${visitTimeStr}` : '',
        visitor.reasonForVisit ? `Reason: ${visitor.reasonForVisit}` : '',
        `Pass ID: ${visitor.shortId}`,
      ].filter(Boolean).join('\n'),
      qr_code: qrUrl,
    };
    const subject = renderTemplate(tpl.subject, vars);
    const rawBody = renderTemplate(tpl.body, vars);
    // Strip HTML so the body is readable in a mailto: window. Preserves
    // paragraph breaks and unordered lists in the most basic way.
    const text = rawBody
      .replace(/<br\s*\/?>/gi, '\n')
      .replace(/<\/(p|div|h[1-6]|li)>/gi, '\n')
      .replace(/<li[^>]*>/gi, '• ')
      .replace(/<[^>]+>/g, '')
      .replace(/&nbsp;/g, ' ')
      .replace(/&amp;/g, '&')
      .replace(/&lt;/g, '<')
      .replace(/&gt;/g, '>')
      .replace(/&quot;/g, '"')
      .replace(/&#39;/g, "'")
      .replace(/\n{3,}/g, '\n\n')
      .replace(/^\n+|\n+$/g, '');
    res.json({ subject, body: text, qrUrl });
  } catch (error) {
    console.error('renderEmailTemplateForVisitor', error);
    res.status(500).json({ error: 'Failed to render template' });
  }
}

export async function previewEmailTemplate(req: AuthRequest, res: Response) {
  try {
    const type = req.params.type as SupportedType;
    if (!SUPPORTED_TYPES.includes(type)) {
      res.status(400).json({ error: 'Unknown template type' });
      return;
    }
    const { subject, body, style } = req.body;
    if (typeof subject !== 'string' || typeof body !== 'string') {
      res.status(400).json({ error: 'Subject and body are required' });
      return;
    }
    // VISITOR_INVITE sample shape matches both other kinds (shared
    // variables), so it doubles as the preview seed.
    const sample = VISITOR_INVITE_SAMPLE;
    const renderedSubject = renderTemplate(subject, sample as any);
    const renderedBody = renderTemplate(body, sample as any);

    // Build the same branded HTML the recipient actually sees, using the
    // sample variables. Pulling renderInviteHtml in dynamically avoids a
    // top-level circular import with visitors.controller.
    const { renderInviteHtmlPreview } = await import('./visitors.controller');
    // Live preview accepts the in-flight style overrides directly (the
    // editor sends them on every debounced refresh), so the preview can
    // reflect color tweaks before they're persisted.
    const previewStyle = (style && typeof style === 'object') ? {
      headerBg: sanitizeColor(style.headerBg),
      headerText: sanitizeColor(style.headerText),
      infoBoxBg: sanitizeColor(style.infoBoxBg),
    } : undefined;
    const html = await renderInviteHtmlPreview({
      kind: type,
      body: renderedBody,
      visitor: {
        id: 'preview',
        shortId: sample.visitor_id,
        ownerId: 'preview',
        name: sample.visitor_name,
        email: sample.visitor_email,
        mobile: sample.visitor_mobile,
        reasonForVisit: sample.reason_for_visit,
        notes: sample.visit_notes,
        visitDate: new Date(),
        visitTime: '10:00',
        shortIdQr: sample.visitor_id,
      } as any,
      hostName: sample.host_name,
      hostEmail: sample.host_email,
      visitDate: sample.visit_date,
      visitTime: ` at ${sample.visit_time}`,
      style: previewStyle,
    });

    res.json({ subject: renderedSubject, body: renderedBody, html });
  } catch (error) {
    console.error('previewEmailTemplate', error);
    res.status(500).json({ error: 'Failed to render preview' });
  }
}
