import { Request, Response } from 'express';
import { prisma } from '../config/database';
import {
  emitToOwner, emitToApprover, emitToCheckpoint,
  recordNotification, resolveAwaitingNotifications,
} from '../lib/events';

// Public token-based approve/reject endpoints used from the email's
// Approve / Reject buttons. The token sits on Visitor.decisionToken and
// is single-use — cleared after the decision lands so the link can't be
// replayed. The token IS the auth, so no JWT/session is required.

async function loadByToken(token: string) {
  if (!token || token.length < 16) return null;
  return prisma.visitor.findUnique({
    where: { decisionToken: token },
    include: {
      assignedApprover: { select: { id: true, name: true, email: true } },
      assignedAdmin: { select: { id: true, name: true, email: true } },
      owner: { select: { id: true, name: true, email: true } },
    },
  });
}

// GET /api/public/decision/:token — used by the /decide/:token page to
// render the visitor summary before the user clicks a button.
export async function getDecisionTarget(req: Request, res: Response): Promise<void> {
  try {
    const visitor = await loadByToken(req.params.token);
    if (!visitor) { res.status(404).json({ error: 'Link expired or already used' }); return; }
    const host = visitor.assignedAdmin || visitor.assignedApprover || visitor.owner;
    res.json({
      visitorName: visitor.name,
      visitorMobile: visitor.mobile,
      visitorEmail: visitor.email,
      shortId: visitor.shortId,
      reason: visitor.reasonForVisit,
      notes: visitor.notes,
      status: visitor.status,
      hostName: host?.name || host?.email || '',
      // Whether the decision is still actionable. The link can land here
      // after someone already approved/rejected via the bell — surface
      // that state so the page shows "already decided" instead of buttons.
      pending: visitor.status === 'AWAITING_APPROVAL',
    });
  } catch (e) {
    console.error('getDecisionTarget', e);
    res.status(500).json({ error: 'Failed to load decision target' });
  }
}

// POST /api/public/decision/:token  body: { action: 'approve' | 'reject', note?: string }
// Mirrors approverApproveVisitor / approverRejectVisitor side-effects so
// the rest of the app (dashboard sockets, checkpoint UI, bell entries)
// reflects the decision exactly as if the host had clicked in-app.
export async function submitDecision(req: Request, res: Response): Promise<void> {
  try {
    const { action, note } = req.body as { action?: string; note?: string };
    if (action !== 'approve' && action !== 'reject') {
      res.status(400).json({ error: 'action must be "approve" or "reject"' });
      return;
    }
    const visitor = await loadByToken(req.params.token);
    if (!visitor) { res.status(404).json({ error: 'Link expired or already used' }); return; }
    if (visitor.status !== 'AWAITING_APPROVAL') {
      res.status(409).json({ error: 'Already decided', status: visitor.status });
      return;
    }

    const decisionAt = new Date();
    const actor = visitor.assignedAdmin || visitor.assignedApprover || visitor.owner;
    const actorName = actor?.name || actor?.email || 'Host';

    if (action === 'approve') {
      const updated = await prisma.visitor.update({
        where: { id: visitor.id },
        data: {
          status: 'ARRIVED',
          arrivedAt: decisionAt,
          approvalNote: note || null,
          ...(visitor.isFrequent ? {} : { expiresAt: decisionAt }),
          // Clear the token so the link can't be replayed.
          decisionToken: null,
        },
      });
      emitToOwner(visitor.ownerId, 'visitor.decided', { visitor: updated });
      await recordNotification({
        recipientType: 'OWNER', recipientId: visitor.ownerId,
        type: 'visitor.decided',
        title: `${updated.name} approved`,
        body: `Approved via email by ${actorName} · #${updated.shortId}`,
        link: '/visitors',
      });
      const assigneeIds = [visitor.assignedAdminId, visitor.assignedApproverId].filter((x): x is string => !!x);
      for (const id of assigneeIds) {
        emitToApprover(id, 'visitor.decided', { visitor: updated });
        await recordNotification({
          recipientType: 'APPROVER', recipientId: id,
          type: 'visitor.decided',
          title: `You approved ${updated.name}`,
          body: `Approved from your email · #${updated.shortId}`,
          link: '/approver',
        });
      }
      // Refresh the checkpoint that last scanned them (live-approval path).
      const lastScan = await prisma.visitorScanLog.findFirst({
        where: { visitorId: visitor.id }, orderBy: { scannedAt: 'desc' }, select: { checkpointId: true },
      });
      if (lastScan?.checkpointId) {
        emitToCheckpoint(lastScan.checkpointId, 'visitor.decided', { visitor: updated });
        await recordNotification({
          recipientType: 'CHECKPOINT', recipientId: lastScan.checkpointId,
          type: 'visitor.decided',
          title: `${updated.name} approved`,
          body: `Host cleared entry via email — please let them in · #${updated.shortId}`,
          link: '/visitor-scanner',
        });
      }
      await resolveAwaitingNotifications(updated.shortId);
      res.json({ ok: true, action: 'approve', status: updated.status, visitorName: updated.name });
      return;
    }

    // reject
    const updated = await prisma.visitor.update({
      where: { id: visitor.id },
      data: {
        status: 'REJECTED',
        approvalNote: note || null,
        decisionToken: null,
      },
    });
    emitToOwner(visitor.ownerId, 'visitor.decided', { visitor: updated });
    await recordNotification({
      recipientType: 'OWNER', recipientId: visitor.ownerId,
      type: 'visitor.decided',
      title: `${updated.name} rejected`,
      body: `Rejected via email by ${actorName} · #${updated.shortId}`,
      link: '/visitors',
    });
    const assigneeIds = [visitor.assignedAdminId, visitor.assignedApproverId].filter((x): x is string => !!x);
    for (const id of assigneeIds) {
      emitToApprover(id, 'visitor.decided', { visitor: updated });
      await recordNotification({
        recipientType: 'APPROVER', recipientId: id,
        type: 'visitor.decided',
        title: `You rejected ${updated.name}`,
        body: `Rejected from your email · #${updated.shortId}`,
        link: '/approver',
      });
    }
    const lastScan = await prisma.visitorScanLog.findFirst({
      where: { visitorId: visitor.id }, orderBy: { scannedAt: 'desc' }, select: { checkpointId: true },
    });
    if (lastScan?.checkpointId) {
      emitToCheckpoint(lastScan.checkpointId, 'visitor.decided', { visitor: updated });
      await recordNotification({
        recipientType: 'CHECKPOINT', recipientId: lastScan.checkpointId,
        type: 'visitor.decided',
        title: `${updated.name} rejected`,
        body: `Host rejected via email — please turn them away · #${updated.shortId}`,
        link: '/visitor-scanner',
      });
    }
    await resolveAwaitingNotifications(updated.shortId);
    res.json({ ok: true, action: 'reject', status: updated.status, visitorName: updated.name });
  } catch (e) {
    console.error('submitDecision', e);
    res.status(500).json({ error: 'Failed to record decision' });
  }
}
