import { Request, Response } from 'express';
import jwt = require('jsonwebtoken');
import { prisma } from '../config/database';
import { generateOtp } from '../utils/otp';
import { sendOtpEmail } from '../config/mailer';
import { AuthRequest } from '../middleware/auth';

const JWT_SECRET = process.env['JWT_SECRET'] || 'dev-secret';

// Resolves the workspace-owner an email belongs to (whether the email is
// the owner itself, an admin under that owner, or an approver) and reports
// whether that workspace is currently suspended from the platform console.
// New-signup emails (no matching row anywhere) return null so the OTP flow
// can fall through to its auto-create-owner path.
async function workspaceSuspendedForEmail(email: string): Promise<{ suspendedAt: Date | null } | null> {
  const owner = await prisma.owner.findUnique({
    where: { email },
    select: { suspendedAt: true },
  });
  if (owner) return owner;
  const admin = await prisma.admin.findUnique({
    where: { email },
    select: { owner: { select: { suspendedAt: true } } },
  });
  if (admin?.owner) return admin.owner;
  const approver = await prisma.approver.findUnique({
    where: { email },
    select: { owner: { select: { suspendedAt: true } } },
  });
  if (approver?.owner) return approver.owner;
  return null;
}

export async function sendOtp(req: Request, res: Response): Promise<void> {
  try {
    const { email } = req.body;

    if (!email || typeof email !== 'string') {
      res.status(400).json({ error: 'Valid email is required' });
      return;
    }

    const normalized = email.trim().toLowerCase();

    const allowed = await prisma.allowedEmail.findUnique({ where: { email: normalized } });
    if (!allowed || !allowed.isActive) {
      res.status(403).json({ error: 'This email is not authorized to access the portal.' });
      return;
    }

    // Workspace suspension gate. If the platform console marked the owner
    // (or the owner of an admin/approver email) as suspended, refuse to
    // mint an OTP — surfaces the block at the first step instead of
    // letting the user fill OTP and hit a 403 on every API call afterwards.
    const workspace = await workspaceSuspendedForEmail(normalized);
    if (workspace?.suspendedAt) {
      res.status(403).json({ error: 'Workspace suspended. Contact support to restore access.' });
      return;
    }

    const otp = generateOtp();

    await prisma.otpToken.updateMany({
      where: { email: normalized, used: false },
      data: { used: true },
    });

    await prisma.otpToken.create({
      data: {
        email: normalized,
        otp,
        expiresAt: new Date(Date.now() + 10 * 60 * 1000),
      },
    });

    // Two delivery modes for the OTP, picked by the EXPOSE_OTP_IN_RESPONSE
    // env flag:
    //   true  — dev / staging: surface the OTP in the JSON response so the
    //           developer doesn't need a working SMTP. Email is skipped.
    //   false — production: never include the OTP in the response, send it
    //           via the configured SMTP transport instead.
    const exposeOtp = process.env['EXPOSE_OTP_IN_RESPONSE'] === 'true';
    if (!exposeOtp) {
      await sendOtpEmail(normalized, otp);
    }
    res.json({
      message: 'OTP sent successfully',
      ...(exposeOtp ? { devOtp: otp } : {}),
    });
  } catch (error) {
    console.error('sendOtp error:', error);
    res.status(500).json({ error: 'Failed to send OTP' });
  }
}

// Dev-only convenience: return the latest unused OTP for an email so the
// mobile OTP screen can auto-display it without forcing the user to dig in
// the API console. Gated by the same EXPOSE_OTP_IN_RESPONSE env flag —
// returns 404 in production so the endpoint can't be used to bypass email.
export async function peekDevOtp(req: Request, res: Response): Promise<void> {
  try {
    if (process.env['EXPOSE_OTP_IN_RESPONSE'] !== 'true') {
      res.status(404).json({ error: 'Not found' });
      return;
    }
    const email = (req.query.email as string || '').trim().toLowerCase();
    if (!email) { res.status(400).json({ error: 'email is required' }); return; }
    const row = await prisma.otpToken.findFirst({
      where: { email, used: false, expiresAt: { gt: new Date() } },
      orderBy: { createdAt: 'desc' },
      select: { otp: true, expiresAt: true },
    });
    if (!row) { res.status(404).json({ error: 'No active OTP for this email' }); return; }
    res.json({ devOtp: row.otp, expiresAt: row.expiresAt });
  } catch (error) {
    console.error('peekDevOtp error:', error);
    res.status(500).json({ error: 'Failed to peek OTP' });
  }
}

export async function verifyOtp(req: Request, res: Response): Promise<void> {
  try {
    const { email, otp } = req.body;

    if (!email || !otp) {
      res.status(400).json({ error: 'Email and OTP are required' });
      return;
    }

    const normalized = email.trim().toLowerCase();

    const allowed = await prisma.allowedEmail.findUnique({ where: { email: normalized } });
    if (!allowed || !allowed.isActive) {
      res.status(403).json({ error: 'This email is not authorized to access the portal.' });
      return;
    }

    // Belt-and-braces suspension check before issuing a JWT — even if
    // sendOtp got past somehow (cached OTP, replay), don't mint a token
    // for a suspended workspace. Mirrors the runtime requireAuth guard.
    const workspace = await workspaceSuspendedForEmail(normalized);
    if (workspace?.suspendedAt) {
      res.status(403).json({ error: 'Workspace suspended. Contact support to restore access.' });
      return;
    }

    const otpToken = await prisma.otpToken.findFirst({
      where: {
        email: normalized,
        otp,
        used: false,
        expiresAt: { gt: new Date() },
      },
      orderBy: { createdAt: 'desc' },
    });

    if (!otpToken) {
      res.status(400).json({ error: 'Invalid or expired OTP' });
      return;
    }

    await prisma.otpToken.update({
      where: { id: otpToken.id },
      data: { used: true },
    });

    // Owner takes precedence — if the email matches an existing Owner, treat as Owner.
    const ownerByEmail = await prisma.owner.findUnique({ where: { email: normalized } });

    if (!ownerByEmail) {
      // No Owner row yet — check if this email is registered as an Admin (sub-user
      // of an owner) or an Approver.
      const admin = await prisma.admin.findUnique({ where: { email: normalized } });
      if (admin && admin.isActive) {
        const adminFlags = {
          canManageVisitors: admin.canManageVisitors,
          canManageApprovers: admin.canManageApprovers,
          canManageSettings: admin.canManageSettings,
          canApproveRequests: admin.canApproveRequests,
          canScanCheckpoint: admin.canScanCheckpoint,
          canManageSubAdmins: admin.canManageSubAdmins,
          canSeeAllVisitors: admin.canSeeAllVisitors,
        };
        // When createdByAdminId is set AND points at another admin (not
        // null, which means the owner created them), this admin is a
        // sub-admin / receptionist. parentAdminId on the JWT drives the
        // scoped-data view on subsequent requests.
        const isSubAdmin = !!admin.createdByAdminId;
        const token = jwt.sign(
          {
            ownerId: admin.ownerId,
            adminId: admin.id,
            adminFlags,
            ...(isSubAdmin ? { parentAdminId: admin.createdByAdminId } : {}),
          },
          JWT_SECRET,
          { expiresIn: '30d' }
        );
        res.json({
          role: 'ADMIN',
          token,
          admin: {
            id: admin.id,
            name: admin.name,
            email: admin.email,
            ownerId: admin.ownerId,
            parentAdminId: admin.createdByAdminId,
            ...adminFlags,
          },
        });
        return;
      }
      const approver = await prisma.approver.findUnique({ where: { email: normalized } });
      if (approver && approver.isActive) {
        // Carry permission flags in the JWT so requireScanAccess can gate
        // the scanner endpoints without an extra DB lookup.
        const approverFlags = {
          canScanCheckpoint: approver.canScanCheckpoint,
        };
        const token = jwt.sign(
          { approverId: approver.id, ownerId: approver.ownerId, approverFlags },
          JWT_SECRET,
          { expiresIn: '30d' }
        );
        res.json({
          role: 'APPROVER',
          token,
          approver: {
            id: approver.id,
            name: approver.name,
            email: approver.email,
            ownerId: approver.ownerId,
            ...approverFlags,
          },
        });
        return;
      }

      // Deactivated-account guard. If an Admin or Approver row exists for
      // this email but isActive=false, refuse the login. Without this guard
      // the code falls through to the auto-Owner-creation path below and
      // silently escalates a disabled team member into a brand-new workspace
      // owner — a privilege-escalation bug.
      if (admin && !admin.isActive) {
        res.status(403).json({
          error: 'This admin account has been deactivated. Please contact the workspace owner.',
        });
        return;
      }
      if (approver && !approver.isActive) {
        res.status(403).json({
          error: 'This approver account has been deactivated. Please contact the workspace owner.',
        });
        return;
      }
    }

    let owner = ownerByEmail;
    let isNew = false;
    if (!owner) {
      isNew = true;
      owner = await prisma.owner.create({ data: { email: normalized } });
    }

    const token = jwt.sign({ ownerId: owner.id }, JWT_SECRET, { expiresIn: '30d' });

    res.json({ role: 'OWNER', token, owner, isNew });
  } catch (error) {
    console.error('verifyOtp error:', error);
    res.status(500).json({ error: 'Failed to verify OTP' });
  }
}

export async function getMe(req: AuthRequest, res: Response): Promise<void> {
  try {
    const owner = await prisma.owner.findUnique({ where: { id: req.ownerId } });
    if (!owner) {
      res.status(404).json({ error: 'Owner not found' });
      return;
    }
    res.json(owner);
  } catch (error) {
    console.error('getMe error:', error);
    res.status(500).json({ error: 'Failed to load profile' });
  }
}

export async function updateMe(req: AuthRequest, res: Response): Promise<void> {
  try {
    const { name } = req.body;
    if (typeof name !== 'string') {
      res.status(400).json({ error: 'Name is required' });
      return;
    }
    const trimmed = name.trim();
    if (trimmed.length === 0 || trimmed.length > 100) {
      res.status(400).json({ error: 'Name must be 1–100 characters' });
      return;
    }
    const owner = await prisma.owner.update({
      where: { id: req.ownerId },
      data: { name: trimmed },
    });
    res.json(owner);
  } catch (error) {
    console.error('updateMe error:', error);
    res.status(500).json({ error: 'Failed to update profile' });
  }
}
