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

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

// ─── Auth: OTP login for platform admins ────────────────────────────────────

// Separate from the customer OTP flow because (a) we want platform admins
// on their own allow-list (the PlatformAdmin table), and (b) the JWT
// shape is different — no ownerId, just platformAdminId. Same OtpToken
// table is fine; the email scope is implicit.
export async function platformSendOtp(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 admin = await prisma.platformAdmin.findUnique({ where: { email: normalized } });
    if (!admin || !admin.isActive) {
      res.status(403).json({ error: 'This email is not authorised for the platform console.' });
      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) },
    });

    const exposeOtp = process.env['EXPOSE_OTP_IN_RESPONSE'] === 'true';
    if (!exposeOtp) {
      await sendMail(normalized, `${otp} is your Gate Pass platform console code`,
        `<p>Your platform console verification code is: <strong>${otp}</strong></p><p>Expires in 10 minutes.</p>`);
    }
    res.json({
      message: 'OTP sent successfully',
      ...(exposeOtp ? { devOtp: otp } : {}),
    });
  } catch (e) {
    console.error('platformSendOtp', e);
    res.status(500).json({ error: 'Failed to send OTP' });
  }
}

export async function platformVerifyOtp(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 = String(email).trim().toLowerCase();

    const admin = await prisma.platformAdmin.findUnique({ where: { email: normalized } });
    if (!admin || !admin.isActive) {
      res.status(403).json({ error: 'This email is not authorised for the platform console.' });
      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 } });

    const token = jwt.sign({ platformAdminId: admin.id }, JWT_SECRET, { expiresIn: '7d' });
    res.json({
      role: 'PLATFORM_ADMIN',
      token,
      platformAdmin: { id: admin.id, name: admin.name, email: admin.email },
    });
  } catch (e) {
    console.error('platformVerifyOtp', e);
    res.status(500).json({ error: 'Failed to verify OTP' });
  }
}

// ─── Org (Owner) management ─────────────────────────────────────────────────

// Onboard a new customer organization from the console without waiting
// for them to walk through OTP first. Creates the Owner row + the
// AllowedEmail allowlist entry so the listed primary contact can sign
// in directly. Idempotent on email — re-creating with the same email
// returns 409 so the operator can't accidentally clobber an existing
// workspace.
export async function createOrg(req: PlatformAuthRequest, res: Response): Promise<void> {
  try {
    const { email, name } = req.body;
    if (!email || typeof email !== 'string') {
      res.status(400).json({ error: 'Primary contact email is required' });
      return;
    }
    const normalized = email.trim().toLowerCase();
    const existing = await prisma.owner.findUnique({ where: { email: normalized } });
    if (existing) {
      res.status(409).json({ error: 'An organization already exists for that email' });
      return;
    }
    const owner = await prisma.owner.create({
      data: {
        email: normalized,
        name: typeof name === 'string' && name.trim() ? name.trim() : null,
      },
    });
    // Whitelist the email so OTP login works the first time.
    await prisma.allowedEmail.upsert({
      where: { email: normalized },
      create: { email: normalized, isActive: true, note: 'Primary owner (created from platform console)' },
      update: { isActive: true },
    });
    res.status(201).json({
      ...owner,
      _count: { visitors: 0, admins: 0, visitorCheckpoints: 0 },
    });
  } catch (e) {
    console.error('createOrg', e);
    res.status(500).json({ error: 'Failed to create organization' });
  }
}

// Platform dashboard stats. Aggregate counts across every org so the
// vendor sees system-wide totals at a glance. Cheap query — all hits
// indexed columns, no joins.
export async function platformStats(_req: PlatformAuthRequest, res: Response): Promise<void> {
  try {
    const [totalOrgs, suspendedOrgs, totalAdmins, totalVisitors, arrivedVisitors, awaitingApproval, totalCheckpoints] = await Promise.all([
      prisma.owner.count(),
      prisma.owner.count({ where: { suspendedAt: { not: null } } }),
      prisma.admin.count(),
      prisma.visitor.count(),
      prisma.visitor.count({ where: { status: 'ARRIVED' } }),
      prisma.visitor.count({ where: { status: 'AWAITING_APPROVAL' } }),
      prisma.visitorCheckpoint.count(),
    ]);
    res.json({
      totalOrgs,
      suspendedOrgs,
      activeOrgs: totalOrgs - suspendedOrgs,
      totalAdmins,
      totalVisitors,
      arrivedVisitors,
      awaitingApproval,
      totalCheckpoints,
    });
  } catch (e) {
    console.error('platformStats', e);
    res.status(500).json({ error: 'Failed to fetch platform stats' });
  }
}

export async function listOrgs(_req: PlatformAuthRequest, res: Response): Promise<void> {
  try {
    // Pull owner rows + aggregate stats. The counts are inexpensive
    // single-column aggregates; doing them inline here saves a network
    // round-trip per org from the console.
    const orgs = await prisma.owner.findMany({
      orderBy: { createdAt: 'desc' },
      select: {
        id: true, email: true, name: true, suspendedAt: true,
        createdAt: true,
        _count: {
          select: {
            visitors: true,
            admins: true,
            visitorCheckpoints: true,
          },
        },
      },
    });
    res.json(orgs);
  } catch (e) {
    console.error('listOrgs', e);
    res.status(500).json({ error: 'Failed to fetch organizations' });
  }
}

export async function getOrg(req: PlatformAuthRequest, res: Response): Promise<void> {
  try {
    const org = await prisma.owner.findUnique({
      where: { id: req.params.id },
      select: {
        id: true, email: true, name: true, suspendedAt: true, createdAt: true, updatedAt: true,
        _count: { select: { visitors: true, admins: true, visitorCheckpoints: true, walkInQRs: true } },
      },
    });
    if (!org) { res.status(404).json({ error: 'Org not found' }); return; }
    res.json(org);
  } catch (e) {
    console.error('getOrg', e);
    res.status(500).json({ error: 'Failed to fetch org' });
  }
}

export async function renameOrg(req: PlatformAuthRequest, res: Response): Promise<void> {
  try {
    const raw = req.body?.name;
    // Empty string is a valid request — it clears the name and the UI falls
    // back to showing the email. Null/undefined is rejected so we don't
    // silently no-op a malformed call.
    if (raw === undefined || raw === null) {
      res.status(400).json({ error: 'name is required (pass "" to clear)' });
      return;
    }
    const name = String(raw).trim();
    if (name.length > 120) {
      res.status(400).json({ error: 'Organization name must be 120 characters or fewer' });
      return;
    }
    const org = await prisma.owner.update({
      where: { id: req.params.id },
      data: { name: name || null },
      select: { id: true, name: true, email: true, suspendedAt: true, createdAt: true },
    });
    res.json(org);
  } catch (e) {
    console.error('renameOrg', e);
    res.status(500).json({ error: 'Failed to rename org' });
  }
}

export async function suspendOrg(req: PlatformAuthRequest, res: Response): Promise<void> {
  try {
    const org = await prisma.owner.update({
      where: { id: req.params.id },
      data: { suspendedAt: new Date() },
    });
    res.json({ id: org.id, suspendedAt: org.suspendedAt });
  } catch (e) {
    console.error('suspendOrg', e);
    res.status(500).json({ error: 'Failed to suspend org' });
  }
}

export async function unsuspendOrg(req: PlatformAuthRequest, res: Response): Promise<void> {
  try {
    const org = await prisma.owner.update({
      where: { id: req.params.id },
      data: { suspendedAt: null },
    });
    res.json({ id: org.id, suspendedAt: org.suspendedAt });
  } catch (e) {
    console.error('unsuspendOrg', e);
    res.status(500).json({ error: 'Failed to unsuspend org' });
  }
}
