import { Response } from 'express';
import { prisma } from '../config/database';
import { AuthRequest } from '../middleware/auth';

// ─── Permission dependency graph ────────────────────────────────────────────
// Declarative "this flag only holds while ANY of these other flags is true"
// map. This is the single source of truth for permission dependencies —
// enforced here on every write (so it can't be bypassed by calling the API
// directly) and mirrored in web/src/pages/admins/AdminFormPage.tsx for the
// UI (disabling rows + auto-clearing them when their dependency drops).
//
// To add a new dependency later: add one line below (and the matching line
// in AdminFormPage.tsx's copy of this map). Nothing else needs to change —
// enforcePermissionDependencies() walks the map generically and iterates to
// a fixed point, so multi-level chains (A depends on B depends on C) resolve
// correctly without extra code.
const PERMISSION_DEPENDENCIES: Record<string, string[]> = {
  // Backdating only makes sense while this admin can add visitors.
  canBackdateVisitor: ['canAddVisitors'],
  // Check-in policies only matter while this admin can create or edit a
  // visitor to apply them to.
  canPolicyAuto: ['canAddVisitors', 'canManageVisitors'],
  canPolicyLive: ['canAddVisitors', 'canManageVisitors'],
  canPolicyPre: ['canAddVisitors', 'canManageVisitors'],
  canPolicyWalkIn: ['canAddVisitors', 'canManageVisitors'],
  canPolicyManual: ['canAddVisitors', 'canManageVisitors'],
};

// Zeroes out any flag in `flags` whose dependency isn't satisfied by the
// rest of the set. Mutates in place. Iterates to a fixed point so chained
// dependencies (not currently used, but supported) resolve fully.
function enforcePermissionDependencies(flags: Record<string, boolean>): void {
  let changed = true;
  while (changed) {
    changed = false;
    for (const [key, deps] of Object.entries(PERMISSION_DEPENDENCIES)) {
      if (flags[key] && !deps.some((d) => flags[d])) {
        flags[key] = false;
        changed = true;
      }
    }
  }
}

// Self — for the logged-in sub-admin to fetch their own flags after login.
// Also returns `organization` (the parent owner's name + email) so the
// shell can show the org chip in the header without an extra round-trip.
export async function adminMe(req: AuthRequest, res: Response): Promise<void> {
  if (!req.adminId) {
    res.status(403).json({ error: 'Not an admin session' });
    return;
  }
  try {
    const admin = await prisma.admin.findFirst({
      where: { id: req.adminId, ownerId: req.ownerId },
      include: { owner: { select: { id: true, name: true, email: true, photoCaptureStage: true } } },
    });
    if (!admin) { res.status(404).json({ error: 'Admin not found' }); return; }
    const { owner, ...rest } = admin;
    // Workspace invite-email automation gate. Sub-admins can't read the
    // owner-only /email-automation endpoint, so surface just the invite flag
    // here — the Add Visitor form disables its per-visitor "Email the QR
    // invite" toggle when the workspace-level invite automation is off (else
    // the toggle promises an email that the automation gate silently blocks).
    // No automation row = off, matching isAutomationOn on the send path.
    const ea = await prisma.emailAutomation.findUnique({
      where: { ownerId: req.ownerId! },
      select: { inviteEnabled: true },
    });
    res.json({ ...rest, photoCaptureStage: owner?.photoCaptureStage, inviteEmailEnabled: !!ea?.inviteEnabled, organization: owner });
  } catch (error) {
    console.error('adminMe error:', error);
    res.status(500).json({ error: 'Failed to load admin profile' });
  }
}

// ─── Owner-only CRUD (managing sub-admins) ──────────────────────────────────

export async function listAdmins(req: AuthRequest, res: Response): Promise<void> {
  try {
    // Owner sees every admin in the workspace; an admin with
    // canManageSubAdmins only sees their own receptionists. Per-row
    // edit/delete is also gated by createdByAdminId below.
    const where: any = { ownerId: req.ownerId };
    if (req.adminId) where.createdByAdminId = req.adminId;
    const admins = await prisma.admin.findMany({
      where,
      orderBy: { name: 'asc' },
    });
    res.json(admins);
  } catch (error) {
    console.error('listAdmins error:', error);
    res.status(500).json({ error: 'Failed to fetch admins' });
  }
}

export async function createAdmin(req: AuthRequest, res: Response): Promise<void> {
  try {
    const {
      name, email, isActive,
      canManageVisitors, canManageApprovers, canManageSettings, canApproveRequests,
      isApprover, phone, designation, department, canAddVisitors, canDeleteVisitors, canBackdateVisitor,
      canViewWalkInQr, canAddWalkInQr, canDeleteWalkInQr,
      canScanCheckpoint, canManageSubAdmins, canSeeAllVisitors,
      canPolicyAuto, canPolicyLive, canPolicyPre, canPolicyWalkIn, canPolicyManual,
    } = req.body;
    if (!name || !String(name).trim()) { res.status(400).json({ error: 'Name is required' }); return; }
    if (!email || !String(email).trim()) { res.status(400).json({ error: 'Email is required' }); return; }
    const normalized = String(email).trim().toLowerCase();

    // Make sure the email isn't already a known Owner / Admin. We allow a
    // legacy Approver row to exist for the same email — it'll be linked via
    // the mirror logic below until step 3 of the merge removes the table.
    const conflict = await Promise.all([
      prisma.owner.findUnique({ where: { email: normalized } }),
      prisma.admin.findUnique({ where: { email: normalized } }),
    ]);
    if (conflict.some(Boolean)) {
      res.status(409).json({ error: 'That email is already in use by another role' });
      return;
    }

    // When an admin is creating a sub-admin, cap every flag at the
    // parent's value — they can't grant a permission they don't have
    // themselves. Sub-admins also never get canManageSubAdmins (no
    // sub-sub-admins, 3-tier hard cap).
    let parent: Awaited<ReturnType<typeof prisma.admin.findUnique>> | null = null;
    if (req.adminId) {
      parent = await prisma.admin.findUnique({ where: { id: req.adminId } });
      if (!parent) { res.status(403).json({ error: 'Parent admin not found' }); return; }
    }
    const cap = (requested: any, parentVal: boolean | undefined): boolean => {
      if (!parent) return !!requested; // owner — no cap
      return !!requested && !!parentVal;
    };

    // canManageVisitors = "Edit visitors" permission. canAddVisitors /
    // canDeleteVisitors are independent visitor-CRUD permissions — the web
    // form's "Manage visitors" section header is a pure UI show/hide
    // affordance with no effect here. Build the full flag set first, then
    // run it through the dependency graph once, generically, instead of
    // hand-coding each dependency inline.
    const flags: Record<string, boolean> = {
      isApprover: cap(isApprover, parent?.isApprover),
      canManageVisitors: cap(canManageVisitors, parent?.canManageVisitors),
      canManageApprovers: cap(canManageApprovers, parent?.canManageApprovers),
      canManageSettings: cap(canManageSettings, parent?.canManageSettings),
      canApproveRequests: cap(canApproveRequests, parent?.canApproveRequests),
      canAddVisitors: cap(canAddVisitors, parent?.canAddVisitors),
      canDeleteVisitors: cap(canDeleteVisitors, parent?.canDeleteVisitors),
      canViewWalkInQr: cap(canViewWalkInQr, parent?.canViewWalkInQr),
      canAddWalkInQr: cap(canAddWalkInQr, parent?.canAddWalkInQr),
      canDeleteWalkInQr: cap(canDeleteWalkInQr, parent?.canDeleteWalkInQr),
      canBackdateVisitor: cap(canBackdateVisitor, parent?.canBackdateVisitor),
      canScanCheckpoint: cap(canScanCheckpoint, parent?.canScanCheckpoint),
      // Hard 3-tier cap: sub-admins never get canManageSubAdmins,
      // regardless of what the form sent.
      canManageSubAdmins: parent ? false : !!canManageSubAdmins,
      // Org-wide visitor visibility — only an owner can grant this.
      // Sub-admins inherit their parent's scope rules, not the
      // workspace-wide one.
      canSeeAllVisitors: parent ? false : !!canSeeAllVisitors,
      // Allowed check-in policies. If the create payload omits a flag we
      // default to true (parity with existing admins) so the owner can
      // tighten later from Settings → Admins.
      canPolicyAuto: parent
        ? !!(canPolicyAuto !== undefined ? canPolicyAuto : true) && parent.canPolicyAuto
        : (canPolicyAuto !== undefined ? !!canPolicyAuto : true),
      canPolicyLive: parent
        ? !!(canPolicyLive !== undefined ? canPolicyLive : true) && parent.canPolicyLive
        : (canPolicyLive !== undefined ? !!canPolicyLive : true),
      canPolicyPre: parent
        ? !!(canPolicyPre !== undefined ? canPolicyPre : true) && parent.canPolicyPre
        : (canPolicyPre !== undefined ? !!canPolicyPre : true),
      canPolicyWalkIn: parent
        ? !!(canPolicyWalkIn !== undefined ? canPolicyWalkIn : true) && parent.canPolicyWalkIn
        : (canPolicyWalkIn !== undefined ? !!canPolicyWalkIn : true),
      canPolicyManual: parent
        ? !!(canPolicyManual !== undefined ? canPolicyManual : true) && parent.canPolicyManual
        : (canPolicyManual !== undefined ? !!canPolicyManual : true),
    };
    enforcePermissionDependencies(flags);

    const admin = await prisma.admin.create({
      data: {
        ownerId: req.ownerId!,
        name: String(name).trim(),
        email: normalized,
        phone: phone || null,
        designation: designation || null,
        department: department || null,
        isActive: isActive !== false,
        ...flags,
        // Track who in the team added this admin. Null when the workspace
        // owner adds them directly.
        createdByAdminId: req.adminId || null,
      },
    });


    // Allow the sub-admin to actually log in via the OTP flow.
    await prisma.allowedEmail.upsert({
      where: { email: normalized },
      create: { email: normalized, isActive: true, note: 'Admin (sub-user)' },
      update: { isActive: true },
    });

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

export async function updateAdmin(req: AuthRequest, res: Response): Promise<void> {
  try {
    // Owners can edit any admin in their workspace. Admins-with-flag can
    // only edit their own sub-admins (createdByAdminId === req.adminId).
    const where: any = { id: req.params.id, ownerId: req.ownerId };
    if (req.adminId) where.createdByAdminId = req.adminId;
    const existing = await prisma.admin.findFirst({ where });
    if (!existing) { res.status(404).json({ error: 'Admin not found' }); return; }

    const {
      name, isActive,
      canManageVisitors, canManageApprovers, canManageSettings, canApproveRequests,
      isApprover, phone, designation, department, canAddVisitors, canDeleteVisitors, canBackdateVisitor,
      canViewWalkInQr, canAddWalkInQr, canDeleteWalkInQr,
      canScanCheckpoint, canManageSubAdmins, canSeeAllVisitors,
      canPolicyAuto, canPolicyLive, canPolicyPre, canPolicyWalkIn, canPolicyManual,
    } = req.body;
    const data: any = {};
    if (name !== undefined) data.name = String(name).trim();
    if (isActive !== undefined) data.isActive = !!isActive;
    if (canManageVisitors !== undefined) data.canManageVisitors = !!canManageVisitors;
    if (canManageApprovers !== undefined) data.canManageApprovers = !!canManageApprovers;
    if (canManageSettings !== undefined) data.canManageSettings = !!canManageSettings;
    if (canApproveRequests !== undefined) data.canApproveRequests = !!canApproveRequests;
    if (isApprover !== undefined) data.isApprover = !!isApprover;
    if (phone !== undefined) data.phone = phone || null;
    if (designation !== undefined) data.designation = designation || null;
    if (department !== undefined) data.department = department || null;
    if (canAddVisitors !== undefined) data.canAddVisitors = !!canAddVisitors;
    if (canDeleteVisitors !== undefined) data.canDeleteVisitors = !!canDeleteVisitors;
    if (canViewWalkInQr !== undefined) data.canViewWalkInQr = !!canViewWalkInQr;
    if (canAddWalkInQr !== undefined) data.canAddWalkInQr = !!canAddWalkInQr;
    if (canDeleteWalkInQr !== undefined) data.canDeleteWalkInQr = !!canDeleteWalkInQr;
    if (canBackdateVisitor !== undefined) data.canBackdateVisitor = !!canBackdateVisitor;
    if (canScanCheckpoint !== undefined) data.canScanCheckpoint = !!canScanCheckpoint;
    // Only the workspace owner can grant canManageSubAdmins (giving an
    // admin permission to create receptionists). Sub-admins themselves
    // never get this — keeps the hierarchy 3 tiers deep.
    if (canManageSubAdmins !== undefined && !req.adminId) data.canManageSubAdmins = !!canManageSubAdmins;
    // Same owner-only rule for the org-wide visibility flag — front-
    // desk role is a sensitive permission the owner controls.
    if (canSeeAllVisitors !== undefined && !req.adminId) data.canSeeAllVisitors = !!canSeeAllVisitors;
    if (canPolicyAuto !== undefined) data.canPolicyAuto = !!canPolicyAuto;
    if (canPolicyLive !== undefined) data.canPolicyLive = !!canPolicyLive;
    if (canPolicyPre !== undefined) data.canPolicyPre = !!canPolicyPre;
    if (canPolicyWalkIn !== undefined) data.canPolicyWalkIn = !!canPolicyWalkIn;
    if (canPolicyManual !== undefined) data.canPolicyManual = !!canPolicyManual;

    // If the caller is an admin (managing their own sub-admin), cap each
    // flag at the parent admin's value so they can't elevate a sub-admin
    // above themselves.
    if (req.adminId) {
      const parent = await prisma.admin.findUnique({ where: { id: req.adminId } });
      if (parent) {
        for (const k of [
          'isApprover', 'canManageVisitors', 'canManageApprovers',
          'canManageSettings', 'canApproveRequests', 'canAddVisitors', 'canDeleteVisitors',
          'canViewWalkInQr', 'canAddWalkInQr', 'canDeleteWalkInQr',
          'canBackdateVisitor', 'canScanCheckpoint',
          'canPolicyAuto', 'canPolicyLive', 'canPolicyPre', 'canPolicyWalkIn', 'canPolicyManual',
        ] as const) {
          if (data[k] === true && (parent as any)[k] !== true) data[k] = false;
        }
      }
    }

    // Re-enforce the dependency graph against the *effective* post-save
    // state — existing stored values merged with whatever this update
    // touched — so e.g. turning off canAddVisitors in one request still
    // clears a canBackdateVisitor that was granted in an earlier request.
    // Only keys that appear in the graph (as a dependent or a dependency)
    // need to be considered; picked up generically so a future addition to
    // PERMISSION_DEPENDENCIES doesn't need any code here to change.
    const relevantKeys = new Set<string>();
    for (const [key, deps] of Object.entries(PERMISSION_DEPENDENCIES)) {
      relevantKeys.add(key);
      deps.forEach((d) => relevantKeys.add(d));
    }
    const effective: Record<string, boolean> = {};
    for (const key of relevantKeys) {
      effective[key] = data[key] !== undefined ? data[key] : !!(existing as any)[key];
    }
    enforcePermissionDependencies(effective);
    for (const key of Object.keys(PERMISSION_DEPENDENCIES)) {
      data[key] = effective[key];
    }

    const admin = await prisma.admin.update({ where: { id: req.params.id }, data });
    res.json(admin);
  } catch (error) {
    console.error('updateAdmin error:', error);
    res.status(500).json({ error: 'Failed to update admin' });
  }
}

export async function deleteAdmin(req: AuthRequest, res: Response): Promise<void> {
  try {
    // Same parent-scope rule as updateAdmin — admins can only delete
    // their own receptionists, owner can delete anyone.
    const where: any = { id: req.params.id, ownerId: req.ownerId };
    if (req.adminId) where.createdByAdminId = req.adminId;
    const existing = await prisma.admin.findFirst({ where });
    if (!existing) { res.status(404).json({ error: 'Admin not found' }); return; }

    // Soft-revoke the allowed-email so they can no longer log in. Don't drop
    // the email outright in case another role legitimately re-uses it later.
    await prisma.allowedEmail.updateMany({
      where: { email: existing.email },
      data: { isActive: false },
    });
    // Null-out createdByAdminId on any visitors they created so the FK
    // delete doesn't fail.
    await prisma.visitor.updateMany({
      where: { createdByAdminId: existing.id },
      data: { createdByAdminId: null },
    });
    await prisma.admin.delete({ where: { id: req.params.id } });
    res.json({ message: 'Admin deleted' });
  } catch (error) {
    console.error('deleteAdmin error:', error);
    res.status(500).json({ error: 'Failed to delete admin' });
  }
}
