import { Response } from 'express';
import { prisma } from '../config/database';
import { AuthRequest, ApproverAuthRequest } from '../middleware/auth';
import { emitToOwner, emitToCheckpoint, recordNotification, resolveAwaitingNotifications } from '../lib/events';
import { sendVisitorInvite } from './visitors.controller';
import { visitDateTimeIsInPast } from '../lib/visitTime';
import { defaultExpiry } from '../lib/visitExpiry';

// ─── Owner-side CRUD for approvers (unified Admin model) ───────────────────
// Approvers are now Admin rows with isApprover=true. The legacy Approver
// table is read-only from this step on — existing rows still serve the
// legacy /approver/login flow but no new writes touch them. Step 4 of the
// merge will drop the table entirely.

export async function listApprovers(req: AuthRequest, res: Response) {
  try {
    const [admins, owner] = await Promise.all([
      prisma.admin.findMany({
        where: { ownerId: req.ownerId, isApprover: true },
        orderBy: { createdAt: 'desc' },
        include: {
          // "Added by" — null for approvers the workspace owner created
          // directly. We fill that in with the workspace owner below so the
          // UI always shows a real person's name.
          createdByAdmin: { select: { id: true, name: true, email: true } },
        },
      }),
      prisma.owner.findUnique({
        where: { id: req.ownerId! },
        select: { id: true, name: true, email: true },
      }),
    ]);

    // Decision counts per admin-approver.
    const grouped = await prisma.visitor.groupBy({
      by: ['assignedAdminId', 'status'],
      where: { ownerId: req.ownerId, assignedAdminId: { not: null } },
      _count: { _all: true },
    });

    const byApprover: Record<string, { pending: number; approved: number; rejected: number; total: number }> = {};
    for (const row of grouped) {
      const aid = row.assignedAdminId!;
      if (!byApprover[aid]) byApprover[aid] = { pending: 0, approved: 0, rejected: 0, total: 0 };
      byApprover[aid].total += row._count._all;
      if (row.status === 'AWAITING_APPROVAL') byApprover[aid].pending += row._count._all;
      else if (row.status === 'ARRIVED') byApprover[aid].approved += row._count._all;
      else if (row.status === 'REJECTED') byApprover[aid].rejected += row._count._all;
    }

    // Resolve who added each approver into a single `addedBy` field so the
    // frontend never has to special-case "Owner vs admin" — it just renders
    // the name.
    const ownerLabel = owner ? (owner.name || owner.email.split('@')[0]) : 'Workspace owner';
    const enriched = admins.map((a) => {
      const addedBy = a.createdByAdmin
        ? { id: a.createdByAdmin.id, name: a.createdByAdmin.name, email: a.createdByAdmin.email, role: 'ADMIN' as const }
        : { id: owner?.id || null, name: ownerLabel, email: owner?.email || '', role: 'OWNER' as const };
      return {
        ...a,
        addedBy,
        _counts: byApprover[a.id] || { pending: 0, approved: 0, rejected: 0, total: 0 },
      };
    });

    res.json(enriched);
  } catch (error) {
    console.error('listApprovers error:', error);
    res.status(500).json({ error: 'Failed to fetch approvers' });
  }
}

export async function listAllApproverActivity(req: AuthRequest, res: Response) {
  try {
    const visitors = await prisma.visitor.findMany({
      where: { ownerId: req.ownerId, assignedAdminId: { not: null } },
      orderBy: { updatedAt: 'desc' },
      take: 500,
      select: {
        id: true, shortId: true, name: true, photoUrl: true,
        email: true, mobile: true,
        status: true, approvalNote: true,
        approvalRequestedAt: true, arrivedAt: true,
        visitDate: true, visitTime: true,
        createdAt: true, updatedAt: true,
        assignedAdmin: { select: { id: true, name: true, email: true } },
      },
    });
    // Project back into the legacy "assignedApprover" shape so the frontend
    // doesn't need a rename pass while the merge is in flight.
    res.json(visitors.map((v) => {
      const { assignedAdmin, ...rest } = v;
      return { ...rest, assignedApprover: assignedAdmin };
    }));
  } catch (error) {
    console.error('listAllApproverActivity error:', error);
    res.status(500).json({ error: 'Failed to fetch activity' });
  }
}

export async function getApproverHistory(req: AuthRequest, res: Response) {
  try {
    const existing = await prisma.admin.findFirst({
      where: { id: req.params.id, ownerId: req.ownerId, isApprover: true },
    });
    if (!existing) {
      res.status(404).json({ error: 'Approver not found' });
      return;
    }

    const visitors = await prisma.visitor.findMany({
      where: { assignedAdminId: req.params.id, ownerId: req.ownerId },
      orderBy: { updatedAt: 'desc' },
      take: 200,
      select: {
        id: true, shortId: true, name: true, photoUrl: true,
        email: true, mobile: true,
        status: true, approvalNote: true,
        approvalRequestedAt: true, arrivedAt: true,
        visitDate: true, visitTime: true,
        createdAt: true, updatedAt: true,
      },
    });

    res.json(visitors);
  } catch (error) {
    console.error('getApproverHistory error:', error);
    res.status(500).json({ error: 'Failed to fetch approver history' });
  }
}

export async function createApprover(req: AuthRequest, res: Response) {
  try {
    const { name, email, phone, designation, department, canAddVisitors, canScanCheckpoint } = req.body;
    if (!name || !email) {
      res.status(400).json({ error: 'Name and email are required' });
      return;
    }
    const normalisedEmail = String(email).trim().toLowerCase();

    const existingAdmin = await prisma.admin.findUnique({ where: { email: normalisedEmail } });
    if (existingAdmin) {
      res.status(409).json({ error: 'A team member with that email already exists' });
      return;
    }

    const admin = await prisma.admin.create({
      data: {
        ownerId: req.ownerId!,
        name,
        email: normalisedEmail,
        phone: phone || null,
        designation: designation || null,
        department: department || null,
        isApprover: true,
        canAddVisitors: Boolean(canAddVisitors),
        canScanCheckpoint: Boolean(canScanCheckpoint),
        canApproveRequests: true,
        canManageVisitors: false,
        canManageApprovers: false,
        canManageSettings: false,
        // Stamp the creator so the Approvers list can show "Added by …".
        // Null when the workspace owner adds the approver directly.
        createdByAdminId: req.adminId || null,
      },
      include: {
        createdByAdmin: { select: { id: true, name: true, email: true } },
      },
    });

    // Whitelist the email so the OTP gate lets them in
    await prisma.allowedEmail.upsert({
      where: { email: normalisedEmail },
      update: { isActive: true },
      create: { email: normalisedEmail, note: `Approver: ${name}`, isActive: true },
    });

    res.status(201).json(admin);
  } catch (error) {
    console.error('createApprover error:', error);
    res.status(500).json({ error: 'Failed to create approver' });
  }
}

export async function updateApprover(req: AuthRequest, res: Response) {
  try {
    const existing = await prisma.admin.findFirst({
      where: { id: req.params.id, ownerId: req.ownerId, isApprover: true },
    });
    if (!existing) {
      res.status(404).json({ error: 'Approver not found' });
      return;
    }
    const { name, email, phone, designation, department, isActive, canAddVisitors, canScanCheckpoint } = req.body;
    const data: any = {};
    if (name !== undefined) data.name = name;
    if (email !== undefined) data.email = String(email).trim().toLowerCase();
    if (phone !== undefined) data.phone = phone || null;
    if (designation !== undefined) data.designation = designation || null;
    if (department !== undefined) data.department = department || null;
    if (isActive !== undefined) data.isActive = isActive;
    if (canAddVisitors !== undefined) data.canAddVisitors = Boolean(canAddVisitors);
    if (canScanCheckpoint !== undefined) data.canScanCheckpoint = Boolean(canScanCheckpoint);

    const admin = await prisma.admin.update({ where: { id: req.params.id }, data });

    if (data.email && data.email !== existing.email) {
      await prisma.allowedEmail.upsert({
        where: { email: data.email },
        update: { isActive: true },
        create: { email: data.email, note: `Approver: ${admin.name}`, isActive: true },
      });
    }
    res.json(admin);
  } catch (error) {
    console.error('updateApprover error:', error);
    res.status(500).json({ error: 'Failed to update approver' });
  }
}

export async function deleteApprover(req: AuthRequest, res: Response) {
  try {
    const existing = await prisma.admin.findFirst({
      where: { id: req.params.id, ownerId: req.ownerId, isApprover: true },
    });
    if (!existing) {
      res.status(404).json({ error: 'Approver not found' });
      return;
    }
    // Unassign visitors still pointing here.
    await prisma.visitor.updateMany({
      where: { assignedAdminId: req.params.id },
      data: { assignedAdminId: null },
    });
    await prisma.admin.delete({ where: { id: req.params.id } });

    // If no Owner shares this email, remove from AllowedEmail too so they can't sign in
    const ownerWithEmail = await prisma.owner.findFirst({ where: { email: existing.email } });
    if (!ownerWithEmail) {
      await prisma.allowedEmail.deleteMany({ where: { email: existing.email } });
    }
    res.json({ message: 'Approver deleted' });
  } catch (error) {
    console.error('deleteApprover error:', error);
    res.status(500).json({ error: 'Failed to delete approver' });
  }
}

// ─── Approver-side endpoints (use approver JWT) ─────────────────────────────

export async function approverMe(req: ApproverAuthRequest, res: Response): Promise<void> {
  try {
    const approver = await prisma.approver.findFirst({
      where: { id: req.approverId, ownerId: req.ownerId },
      select: { id: true, name: true, email: true, ownerId: true, createdAt: true, canAddVisitors: true, canScanCheckpoint: true },
    });
    if (!approver) {
      res.status(404).json({ error: 'Approver not found' });
      return;
    }
    // Org chip on the shell wants the parent workspace's display name.
    // Fetch once here so the frontend doesn't need a follow-up call.
    const owner = await prisma.owner.findUnique({
      where: { id: approver.ownerId },
      select: { id: true, name: true, email: true, photoCaptureStage: true },
    });
    // Workspace invite-email automation gate (see adminMe) — lets the Add
    // Visitor form disable the per-visitor invite toggle when the org-level
    // invite automation is off. No automation row = off.
    const ea = await prisma.emailAutomation.findUnique({
      where: { ownerId: approver.ownerId },
      select: { inviteEnabled: true },
    });
    res.json({ ...approver, photoCaptureStage: owner?.photoCaptureStage, inviteEmailEnabled: !!ea?.inviteEnabled, organization: owner });
  } catch (error) {
    console.error('approverMe error:', error);
    res.status(500).json({ error: 'Failed to fetch approver' });
  }
}

/** Look up a single visitor by shortId — used by the mobile approval popup
 *  which receives the shortId in the push payload. Scoped to the approver's
 *  workspace so cross-tenant access is impossible. */
export async function approverGetVisitorByShortId(req: ApproverAuthRequest, res: Response): Promise<void> {
  try {
    const { shortId } = req.params;
    const visitor = await prisma.visitor.findFirst({
      where: { shortId, ownerId: req.ownerId },
      include: {
        // Most-recent scan log carries the checkpoint the visitor arrived at,
        // shown as the checkpoint name in the mobile approval popup.
        scanLogs: {
          include: { checkpoint: { select: { name: true, personName: true } } },
          orderBy: { scannedAt: 'desc' },
          take: 1,
        },
      },
    });
    if (!visitor) {
      res.status(404).json({ error: 'Visitor not found' });
      return;
    }
    res.json(visitor);
  } catch (error) {
    console.error('approverGetVisitorByShortId error:', error);
    res.status(500).json({ error: 'Failed to fetch visitor' });
  }
}

/** All visitors related to this approver — either assigned for approval OR created by them. */
export async function approverListVisitors(req: ApproverAuthRequest, res: Response): Promise<void> {
  try {
    const visitors = await prisma.visitor.findMany({
      where: {
        ownerId: req.ownerId,
        OR: [
          { assignedApproverId: req.approverId },
          { createdByApproverId: req.approverId },
        ],
      },
      orderBy: { createdAt: 'desc' },
      include: {
        owner: { select: { id: true, name: true, email: true } },
        assignedApprover: { select: { id: true, name: true, email: true } },
        createdByApprover: { select: { id: true, name: true, email: true } },
        createdByAdmin: { select: { id: true, name: true, email: true } },
        scanLogs: {
          include: { checkpoint: { select: { name: true, personName: true } } },
          orderBy: { scannedAt: 'desc' },
          take: 1,
        },
        visitorRequest: { select: { id: true } },
      },
    });
    res.json(visitors);
  } catch (error) {
    console.error('approverListVisitors error:', error);
    res.status(500).json({ error: 'Failed to fetch visitors' });
  }
}

/** Aggregate counts scoped to this approver — mirrors /api/visitors/counts shape (but no walk-ins). */
export async function approverCounts(req: ApproverAuthRequest, res: Response): Promise<void> {
  try {
    const orFilter = {
      ownerId: req.ownerId,
      OR: [
        { assignedApproverId: req.approverId },
        { createdByApproverId: req.approverId },
      ],
    };
    const [awaitingApproval, expected, arrived, total, walkInPending] = await Promise.all([
      prisma.visitor.count({ where: { ...orFilter, status: 'AWAITING_APPROVAL' } }),
      prisma.visitor.count({ where: { ...orFilter, status: 'EXPECTED' } }),
      prisma.visitor.count({ where: { ...orFilter, status: 'ARRIVED' } }),
      prisma.visitor.count({ where: orFilter }),
      prisma.visitorRequest.count({ where: { ownerId: req.ownerId, assignedApproverId: req.approverId, status: 'PENDING' } }),
    ]);
    res.json({ awaitingApproval, walkInPending, expected, arrived, total });
  } catch (error) {
    console.error('approverCounts error:', error);
    res.status(500).json({ error: 'Failed to fetch counts' });
  }
}

export async function approverPending(req: ApproverAuthRequest, res: Response): Promise<void> {
  try {
    const visitors = await prisma.visitor.findMany({
      where: {
        ownerId: req.ownerId,
        assignedApproverId: req.approverId,
        status: 'AWAITING_APPROVAL',
      },
      orderBy: { approvalRequestedAt: 'desc' },
      include: {
        scanLogs: {
          include: { checkpoint: { select: { name: true, personName: true } } },
          orderBy: { scannedAt: 'desc' },
          take: 1,
        },
      },
    });
    res.json(visitors);
  } catch (error) {
    console.error('approverPending error:', error);
    res.status(500).json({ error: 'Failed to fetch pending approvals' });
  }
}

export async function approverHistory(req: ApproverAuthRequest, res: Response): Promise<void> {
  try {
    const visitors = await prisma.visitor.findMany({
      where: {
        ownerId: req.ownerId,
        assignedApproverId: req.approverId,
        status: { in: ['ARRIVED', 'REJECTED'] },
      },
      orderBy: { updatedAt: 'desc' },
      take: 50,
    });
    res.json(visitors);
  } catch (error) {
    console.error('approverHistory error:', error);
    res.status(500).json({ error: 'Failed to fetch history' });
  }
}

export async function approverApproveVisitor(req: ApproverAuthRequest, res: Response): Promise<void> {
  try {
    const { id } = req.params;
    const { approvalNote } = req.body;

    const existing = await prisma.visitor.findFirst({
      where: { id, ownerId: req.ownerId, assignedApproverId: req.approverId },
    });
    if (!existing) {
      res.status(404).json({ error: 'Visitor not assigned to you' });
      return;
    }
    if (existing.status !== 'AWAITING_APPROVAL') {
      res.status(400).json({ error: 'Visitor is not awaiting approval' });
      return;
    }

    // Approval always lands at ARRIVED. Pre-approval = approver clears the
    // visitor from the dashboard (no scan needed); live approval = approver
    // clears at scan time. Either way the visitor is "in", so arrivedAt is
    // stamped now. isAdvance is kept around to label the notification.
    const isAdvance = existing.isPreApproval;
    const decisionAt = new Date();
    const visitor = await prisma.visitor.update({
      where: { id },
      data: {
        status: 'ARRIVED',
        arrivedAt: decisionAt,
        approvalNote: approvalNote || null,
        // One-shot QR (see approveVisitorScan). Frequent visitors are
        // exempt — their QR is reused across many visits.
        ...(existing.isFrequent ? {} : { expiresAt: decisionAt }),
      },
    });

    // Look up the approver's name once so we can include it in the
    // owner-facing notification copy ("Devang Shah approved by Reception").
    const actor = req.approverId
      ? await prisma.approver.findUnique({ where: { id: req.approverId }, select: { name: true, email: true } })
      : null;
    const actorName = actor?.name || actor?.email || 'Approver';

    // Notify owner so dashboard reflects decision
    emitToOwner(req.ownerId!, 'visitor.decided', { visitor });
    await recordNotification({
      recipientType: 'OWNER', recipientId: req.ownerId!,
      type: 'visitor.decided',
      title: `${visitor.name} approved`,
      body: `Approved by ${actorName} — visitor marked arrived · #${visitor.shortId}`,
      link: '/visitors',
      push: { data: { visitorId: visitor.id, shortId: visitor.shortId, kind: 'visitor.decided' } },
    });
    // Log the decision in the approver's own notification list too so they
    // have a personal history of "approved X" / "rejected Y".
    if (req.approverId) {
      await recordNotification({
        recipientType: 'APPROVER', recipientId: req.approverId,
        type: 'visitor.decided',
        title: `You approved ${visitor.name}`,
        body: `Visitor marked arrived · #${visitor.shortId}`,
        link: '/approver',
      });
    }
    // Notify the checkpoint(s) only when this was a live-at-scan approval —
    // pre-approvals have no scanner to refresh yet.
    if (!isAdvance) {
      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 });
        await recordNotification({
          recipientType: 'CHECKPOINT', recipientId: lastScan.checkpointId,
          type: 'visitor.decided',
          title: `${visitor.name} approved`,
          body: visitor.approvalNote
            ? `Host cleared entry — ${visitor.approvalNote} · #${visitor.shortId}`
            : `Host cleared entry — please let them in · #${visitor.shortId}`,
          link: '/visitor-scanner',
          push: { data: { visitorId: visitor.id, shortId: visitor.shortId, kind: 'visitor.decided' } },
        });
      }
    }

    // Decision made → clear the bell entries that asked for it.
    await resolveAwaitingNotifications(visitor.shortId);

    res.json(visitor);
  } catch (error) {
    console.error('approverApproveVisitor error:', error);
    res.status(500).json({ error: 'Failed to approve visitor' });
  }
}

export async function approverCreateVisitor(req: ApproverAuthRequest, res: Response): Promise<void> {
  try {
    // Permission check
    const me = await prisma.approver.findFirst({
      where: { id: req.approverId, ownerId: req.ownerId },
      select: { canAddVisitors: true, isActive: true },
    });
    if (!me || !me.isActive) { res.status(401).json({ error: 'Approver inactive' }); return; }
    if (!me.canAddVisitors) { res.status(403).json({ error: 'You do not have permission to add visitors' }); return; }

    const { name, email, mobile, designation, companyName, reasonForVisit, notes, visitDate, visitTime, expiresAt, requiresApproval, isWalkIn, sendInviteEmail } = req.body;
    if (!name) { res.status(400).json({ error: 'Name is required' }); return; }
    if (!reasonForVisit || !String(reasonForVisit).trim()) {
      res.status(400).json({ error: 'Reason for visit is required' });
      return;
    }

    // Approvers (legacy Approver table) don't carry a backdating flag and
    // never can — the workspace owner has to add visitors via Add Visitor
    // if they want to backdate. Approvers logging into admin accounts use
    // the admin path which respects canBackdateVisitor.
    if (visitDateTimeIsInPast(visitDate, visitTime)) {
      res.status(400).json({ error: "Visit time can't be in the past." });
      return;
    }

    // Re-use the same QR generation pipeline as the owner-side createVisitor
    const { nanoid } = await import('nanoid');
    const { generateQRCodeBuffer } = await import('../utils/qrcode');
    const { saveUpload } = await import('../config/storage');

    const shortId = nanoid(10).toUpperCase();
    const qrBuffer = await generateQRCodeBuffer(shortId);
    const qrKey = `visitors/qr/${shortId}.png`;
    const qrUrl = await saveUpload(qrBuffer, qrKey, 'image/png', req);

    // Approver-created visitors: if the approver chose "Require my approval",
    // the approver themselves becomes the decider (owner doesn't gate it but
    // still gets the awareness notification). If they chose Auto, no approval
    // needed and scan = ARRIVED immediately.
    //
    // Walk-in (isWalkIn) short-circuits the QR-scan step: visitor lands in
    // AWAITING_APPROVAL on creation, approvalRequestedAt=now, so the host
    // can clear it from the Approvals tab without anyone scanning.
    const needsApproval = Boolean(requiresApproval);
    const walkIn = Boolean(isWalkIn) && needsApproval;
    const nowTs = new Date();
    const visitor = await prisma.visitor.create({
      data: {
        ownerId: req.ownerId!,
        shortId,
        name,
        email: email || null,
        mobile: mobile || null,
        designation: designation || null,
        companyName: companyName || null,
        reasonForVisit: String(reasonForVisit).trim(),
        notes: notes || null,
        visitDate: visitDate ? new Date(visitDate) : null,
        visitTime: visitTime || null,
        qrCodeUrl: qrUrl,
        expiresAt: expiresAt ? new Date(expiresAt) : defaultExpiry(),
        requiresApproval: needsApproval,
        isPreApproval: walkIn,
        status: walkIn ? 'AWAITING_APPROVAL' : 'EXPECTED',
        approvalRequestedAt: walkIn ? nowTs : null,
        assignedApproverId: needsApproval ? req.approverId! : null,
        createdByApproverId: req.approverId!,
      },
    });

    // Per-visitor "Send invite email" toggle (same semantics as the owner path).
    if (visitor.email && sendInviteEmail === true) {
      sendVisitorInvite(req.ownerId!, visitor).catch((e) =>
        console.error('sendVisitorInvite (approver) failed', e)
      );
    }
    // WhatsApp templates are never auto-sent — the host sends them manually
    // from the WhatsApp picker (POST /visitors/:id/whatsapp-send).

    // Walk-in created here: notify the owner immediately so their dashboard
    // refreshes. The approver themselves is the decider — they just clicked
    // Save, so no self-notification needed.
    if (walkIn) {
      emitToOwner(req.ownerId!, 'visitor.awaiting', { visitor });
      await recordNotification({
        recipientType: 'OWNER', recipientId: req.ownerId!,
        type: 'visitor.awaiting',
        title: `Approval needed for ${visitor.name}`,
        body: `Walk-in at reception — waiting for host approval · #${visitor.shortId}`,
        link: '/visitors',
        push: { data: { visitorId: visitor.id, shortId: visitor.shortId, kind: 'visitor.awaiting' } },
      });
    }

    res.status(201).json(visitor);
  } catch (error) {
    console.error('approverCreateVisitor error:', error);
    res.status(500).json({ error: 'Failed to create visitor' });
  }
}

/** Walk-in requests routed to this approver. */
export async function approverListWalkInRequests(req: ApproverAuthRequest, res: Response): Promise<void> {
  try {
    const requests = await prisma.visitorRequest.findMany({
      where: { ownerId: req.ownerId, assignedApproverId: req.approverId },
      orderBy: { createdAt: 'desc' },
      include: {
        checkpoint: { select: { name: true, personName: true } },
        assignedApprover: { select: { id: true, name: true, email: true } },
      },
    });
    res.json(requests);
  } catch (error) {
    console.error('approverListWalkInRequests error:', error);
    res.status(500).json({ error: 'Failed to fetch walk-in requests' });
  }
}

export async function approverApproveWalkIn(req: ApproverAuthRequest, res: Response): Promise<void> {
  try {
    const { id } = req.params;
    const { ownerNote } = req.body;
    const existing = await prisma.visitorRequest.findFirst({
      where: { id, ownerId: req.ownerId, assignedApproverId: req.approverId },
    });
    if (!existing) { res.status(404).json({ error: 'Walk-in request not assigned to you' }); return; }
    if (existing.status !== 'PENDING') { res.status(400).json({ error: 'Request already processed' }); return; }

    const updated = await prisma.visitorRequest.update({
      where: { id },
      data: { status: 'APPROVED', ownerNote: ownerNote || null },
      include: { checkpoint: { select: { name: true, personName: true } } },
    });

    // Flip the linked Visitor (created when reception submitted the walk-in)
    // to ARRIVED. Fall back to creating one for legacy rows that pre-date the
    // up-front-visitor model.
    let visitor;
    if (existing.visitorId) {
      visitor = await prisma.visitor.update({
        where: { id: existing.visitorId },
        data: { status: 'ARRIVED', arrivedAt: new Date(), approvalNote: ownerNote || null },
      });
    } else {
      const { nanoid } = await import('nanoid');
      const { generateQRCodeBuffer } = await import('../utils/qrcode');
      const { saveUpload } = await import('../config/storage');
      const shortId = nanoid(10).toUpperCase();
      const qrBuffer = await generateQRCodeBuffer(shortId);
      const qrUrl = await saveUpload(qrBuffer, `visitors/qr/${shortId}.png`, 'image/png', req);
      visitor = await prisma.visitor.create({
        data: {
          ownerId: req.ownerId!,
          shortId,
          name: existing.name,
          email: existing.email || null,
          mobile: existing.phone || null,
          reasonForVisit: existing.reason,
          notes: existing.company ? `Walk-in from ${existing.company}` : null,
          visitDate: new Date(),
          qrCodeUrl: qrUrl,
          status: 'ARRIVED',
          arrivedAt: new Date(),
          approvalNote: ownerNote || null,
          assignedApproverId: req.approverId!,
          createdByApproverId: req.approverId!,
        },
      });
      await prisma.visitorScanLog.create({
        data: { visitorId: visitor.id, checkpointId: existing.checkpointId },
      });
      await prisma.visitorRequest.update({ where: { id: updated.id }, data: { visitorId: visitor.id } });
    }

    emitToOwner(req.ownerId!, 'request.decided', { request: updated, visitor });
    emitToOwner(req.ownerId!, 'visitor.arrived', { visitor });
    await recordNotification({
      recipientType: 'OWNER', recipientId: req.ownerId!,
      type: 'visitor.arrived',
      title: `${updated.name} approved`,
      body: updated.ownerNote
        ? `Walk-in approved by host — ${updated.ownerNote} · #${visitor.shortId}`
        : `Walk-in approved by host — visitor cleared for entry · #${visitor.shortId}`,
      link: '/visitors',
      push: { data: { visitorId: visitor.id, shortId: visitor.shortId, kind: 'visitor.arrived' } },
    });
    emitToCheckpoint(updated.checkpointId, 'request.decided', { request: updated, visitor });
    await recordNotification({
      recipientType: 'CHECKPOINT', recipientId: updated.checkpointId,
      type: 'request.decided',
      title: `${updated.name} approved`,
      body: updated.ownerNote
        ? `Host approved walk-in — ${updated.ownerNote}`
        : 'Host approved walk-in — please let them in',
      link: '/visitor-scanner',
      push: { data: { visitorId: visitor.id, shortId: visitor.shortId, kind: 'visitor.decided' } },
    });

    res.json({ ...updated, visitor });
  } catch (error) {
    console.error('approverApproveWalkIn error:', error);
    res.status(500).json({ error: 'Failed to approve walk-in' });
  }
}

export async function approverRejectWalkIn(req: ApproverAuthRequest, res: Response): Promise<void> {
  try {
    const { id } = req.params;
    const { ownerNote } = req.body;
    const existing = await prisma.visitorRequest.findFirst({
      where: { id, ownerId: req.ownerId, assignedApproverId: req.approverId },
    });
    if (!existing) { res.status(404).json({ error: 'Walk-in request not assigned to you' }); return; }
    if (existing.status !== 'PENDING') { res.status(400).json({ error: 'Request already processed' }); return; }

    const updated = await prisma.visitorRequest.update({
      where: { id },
      data: { status: 'REJECTED', ownerNote: ownerNote || null },
      include: { checkpoint: { select: { name: true, personName: true } } },
    });

    let visitor = null;
    if (existing.visitorId) {
      visitor = await prisma.visitor.update({
        where: { id: existing.visitorId },
        data: { status: 'REJECTED', approvalNote: ownerNote || null },
      });
      emitToOwner(req.ownerId!, 'visitor.decided', { visitor });
    }

    emitToOwner(req.ownerId!, 'request.decided', { request: updated, visitor });
    await recordNotification({
      recipientType: 'OWNER', recipientId: req.ownerId!,
      type: 'visitor.decided',
      title: `${updated.name} rejected`,
      body: updated.ownerNote
        ? `Walk-in rejected by host — ${updated.ownerNote}${visitor ? ` · #${visitor.shortId}` : ''}`
        : `Walk-in rejected by host — entry denied${visitor ? ` · #${visitor.shortId}` : ''}`,
      link: '/visitors',
      push: visitor ? { data: { visitorId: visitor.id, shortId: visitor.shortId, kind: 'visitor.decided' } } : undefined,
    });
    emitToCheckpoint(updated.checkpointId, 'request.decided', { request: updated, visitor });
    await recordNotification({
      recipientType: 'CHECKPOINT', recipientId: updated.checkpointId,
      type: 'request.decided',
      title: `${updated.name} rejected`,
      body: updated.ownerNote
        ? `Host rejected walk-in — ${updated.ownerNote}`
        : 'Host rejected walk-in — please deny entry',
      link: '/visitor-scanner',
      push: { data: { visitorId: visitor?.id ?? '', shortId: visitor?.shortId ?? updated.name, kind: 'visitor.decided' } },
    });

    res.json({ ...updated, visitor });
  } catch (error) {
    console.error('approverRejectWalkIn error:', error);
    res.status(500).json({ error: 'Failed to reject walk-in' });
  }
}

export async function approverRejectVisitor(req: ApproverAuthRequest, res: Response): Promise<void> {
  try {
    const { id } = req.params;
    const { approvalNote } = req.body;

    const existing = await prisma.visitor.findFirst({
      where: { id, ownerId: req.ownerId, assignedApproverId: req.approverId },
    });
    if (!existing) {
      res.status(404).json({ error: 'Visitor not assigned to you' });
      return;
    }
    if (existing.status !== 'AWAITING_APPROVAL') {
      res.status(400).json({ error: 'Visitor is not awaiting approval' });
      return;
    }

    const visitor = await prisma.visitor.update({
      where: { id },
      data: {
        status: 'REJECTED',
        approvalNote: approvalNote || null,
        // One-shot QR (mirrors approveVisitorScan). Frequent visitors
        // are exempt so they can still attempt a future visit.
        ...(existing.isFrequent ? {} : { expiresAt: new Date() }),
      },
    });

    const rejActor = req.approverId
      ? await prisma.approver.findUnique({ where: { id: req.approverId }, select: { name: true, email: true } })
      : null;
    const rejActorName = rejActor?.name || rejActor?.email || 'Approver';

    emitToOwner(req.ownerId!, 'visitor.decided', { visitor });
    await recordNotification({
      recipientType: 'OWNER', recipientId: req.ownerId!,
      type: 'visitor.decided',
      title: `${visitor.name} rejected`,
      body: visitor.approvalNote
        ? `Rejected by ${rejActorName} — ${visitor.approvalNote} · #${visitor.shortId}`
        : `Rejected by ${rejActorName} — entry denied · #${visitor.shortId}`,
      link: '/visitors',
      push: { data: { visitorId: visitor.id, shortId: visitor.shortId, kind: 'visitor.decided' } },
    });
    if (req.approverId) {
      await recordNotification({
        recipientType: 'APPROVER', recipientId: req.approverId,
        type: 'visitor.decided',
        title: `You rejected ${visitor.name}`,
        body: visitor.approvalNote
          ? `Entry denied — ${visitor.approvalNote} · #${visitor.shortId}`
          : `Entry denied · #${visitor.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 });
      await recordNotification({
        recipientType: 'CHECKPOINT', recipientId: lastScan.checkpointId,
        type: 'visitor.decided',
        title: `${visitor.name} rejected`,
        body: visitor.approvalNote
          ? `Host denied entry — ${visitor.approvalNote} · #${visitor.shortId}`
          : `Host denied entry · #${visitor.shortId}`,
        link: '/visitor-scanner',
        push: { data: { visitorId: visitor.id, shortId: visitor.shortId, kind: 'visitor.decided' } },
      });
    }

    await resolveAwaitingNotifications(visitor.shortId);

    res.json(visitor);
  } catch (error) {
    console.error('approverRejectVisitor error:', error);
    res.status(500).json({ error: 'Failed to reject visitor' });
  }
}
