import { Request, Response } from 'express';
import jwt = require('jsonwebtoken');
import bcrypt = require('bcryptjs');
import { prisma } from '../config/database';
import { AuthRequest, CheckpointAuthRequest } from '../middleware/auth';
import { fireOnFirstScan } from '../utils/email-triggers';

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

export async function scannerLogin(req: Request, res: Response): Promise<void> {
  try {
    const { mobile, password, checkpointId } = req.body;

    if (!mobile || !password) {
      res.status(400).json({ error: 'Mobile and password are required' });
      return;
    }

    // Normalize mobile — search with and without + prefix
    const mobileClean = mobile.replace(/[\s\-()]/g, '');
    const mobileVariants = [mobileClean];
    if (mobileClean.startsWith('+')) {
      mobileVariants.push(mobileClean.slice(1));
    } else {
      mobileVariants.push('+' + mobileClean);
    }

    const checkpoints = await prisma.checkpoint.findMany({
      where: { mobile: { in: mobileVariants }, isActive: true },
      include: { event: { select: { id: true, name: true, startDate: true } } },
    });

    if (checkpoints.length === 0) {
      res.status(401).json({ error: 'Invalid credentials' });
      return;
    }

    // Verify password — try bcrypt compare, fallback to plain text for legacy data
    let matched = false;
    for (const cp of checkpoints) {
      if (cp.passwordHash.startsWith('$2')) {
        // Bcrypt hash
        const ok = await bcrypt.compare(password, cp.passwordHash);
        if (ok) { matched = true; break; }
      } else {
        // Plain text (legacy)
        if (password === cp.passwordHash) { matched = true; break; }
      }
    }
    if (!matched) {
      res.status(401).json({ error: 'Invalid credentials' });
      return;
    }

    // If multiple checkpoints and no specific one selected, return the list
    if (checkpoints.length > 1 && !checkpointId) {
      res.json({
        selectCheckpoint: true,
        checkpoints: checkpoints.map((cp) => ({
          id: cp.id,
          name: cp.name,
          personName: cp.personName,
          eventId: cp.eventId,
          eventName: cp.event.name,
          eventDate: cp.event.startDate,
        })),
      });
      return;
    }

    // Pick the checkpoint
    const checkpoint = checkpointId
      ? checkpoints.find((cp) => cp.id === checkpointId)
      : checkpoints[0];

    if (!checkpoint) {
      res.status(404).json({ error: 'Checkpoint not found' });
      return;
    }

    const token = jwt.sign(
      { checkpointId: checkpoint.id, eventId: checkpoint.eventId },
      JWT_SECRET,
      { expiresIn: '30d' }
    );

    res.json({
      token,
      checkpoint: {
        id: checkpoint.id,
        name: checkpoint.name,
        personName: checkpoint.personName,
        eventId: checkpoint.eventId,
        eventName: checkpoint.event.name,
        categories: checkpoint.categories,
      },
    });
  } catch (error) {
    console.error('scannerLogin error:', error);
    res.status(500).json({ error: 'Failed to login' });
  }
}

export async function getScannerStats(req: CheckpointAuthRequest, res: Response): Promise<void> {
  try {
    const [total, arrived] = await Promise.all([
      prisma.attendee.count({ where: { eventId: req.eventId } }),
      prisma.attendee.count({ where: { eventId: req.eventId, attendance: 'ARRIVED' } }),
    ]);
    res.json({ total, arrived });
  } catch (error) {
    console.error('getScannerStats error:', error);
    res.status(500).json({ error: 'Failed to fetch stats' });
  }
}

export async function checkin(req: CheckpointAuthRequest, res: Response): Promise<void> {
  try {
    const { shortId, scanMethod } = req.body;

    if (!shortId) {
      res.status(400).json({ error: 'Attendee shortId is required' });
      return;
    }

    const attendee = await prisma.attendee.findFirst({
      where: { shortId, eventId: req.eventId },
      include: { category: true },
    });

    if (!attendee) {
      res.status(404).json({ error: 'Attendee not found' });
      return;
    }

    // Block rejected attendees
    if (attendee.status === 'REJECTED') {
      res.status(403).json({ error: 'Entry rejected. This attendee has been rejected by the organizer.', attendee, rejected: true });
      return;
    }

    // Block pending attendees
    if (attendee.status === 'PENDING') {
      res.status(403).json({ error: 'Approval pending. This attendee has not been approved yet.', attendee, pending: true });
      return;
    }

    // Enforce category binding (if checkpoint is restricted to specific categories)
    if (req.checkpointId) {
      const checkpoint = await prisma.checkpoint.findUnique({
        where: { id: req.checkpointId },
        select: { categories: true, name: true },
      });
      if (checkpoint && checkpoint.categories.length > 0) {
        if (!attendee.categoryId || !checkpoint.categories.includes(attendee.categoryId)) {
          const catName = attendee.category?.name;
          const msg = catName
            ? `This checkpoint is not authorized to scan ${catName} tickets.`
            : `This checkpoint is not authorized to scan uncategorized tickets.`;
          res.status(403).json({
            error: msg,
            attendee,
            categoryMismatch: true,
          });
          return;
        }
      }
    }

    const alreadyArrived = attendee.attendance === 'ARRIVED';
    const allowMultipleScan = attendee.category?.triggerIdOnScan ?? false;

    // If already arrived and multiple scan is NOT allowed, reject
    if (alreadyArrived && !allowMultipleScan) {
      res.status(400).json({ error: 'Already scanned. Multiple scan is not allowed for this category.', attendee });
      return;
    }

    // Create scan log
    const scanLogData: any = {
      eventId: req.eventId!,
      attendeeId: attendee.id,
      scanMethod: scanMethod === 'MANUAL' ? 'MANUAL' : 'QR',
    };
    if (req.checkpointId) {
      scanLogData.checkpointId = req.checkpointId;
    }

    const [updatedAttendee] = await Promise.all([
      alreadyArrived
        ? prisma.attendee.findUnique({ where: { id: attendee.id }, include: { category: true } })
        : prisma.attendee.update({
            where: { id: attendee.id },
            data: { attendance: 'ARRIVED' },
            include: { category: true },
          }),
      prisma.scanLog.create({ data: scanLogData }),
    ]);

    if (alreadyArrived) {
      res.json({ ...updatedAttendee, alreadyArrived: true });
    } else {
      // Fire first-scan email trigger (non-blocking)
      if (req.eventId) fireOnFirstScan(req.eventId, attendee.id).catch(() => {});
      res.json(updatedAttendee);
    }
  } catch (error: any) {
    console.error('checkin error:', error?.message || error);
    res.status(500).json({ error: 'Failed to check in attendee', details: error?.message });
  }
}

export async function searchAttendee(req: CheckpointAuthRequest, res: Response): Promise<void> {
  try {
    const { query } = req.params;

    if (!query) {
      res.status(400).json({ error: 'Search query is required' });
      return;
    }

    const attendees = await prisma.attendee.findMany({
      where: {
        eventId: req.eventId,
        OR: [
          { shortId: { equals: query, mode: 'insensitive' } },
          { name: { contains: query, mode: 'insensitive' } },
          { mobile: { contains: query } },
        ],
      },
      include: { category: true },
      take: 20,
    });

    res.json(attendees);
  } catch (error) {
    console.error('searchAttendee error:', error);
    res.status(500).json({ error: 'Failed to search attendees' });
  }
}

export async function getScannerHistory(req: AuthRequest, res: Response): Promise<void> {
  try {
    const { eventId } = req.params;
    const { checkpointId, startDate, endDate, page = '1', limit = '20' } = req.query;

    const event = await prisma.event.findFirst({
      where: { id: eventId, organizerId: req.organizerId },
    });

    if (!event) {
      res.status(404).json({ error: 'Event not found' });
      return;
    }

    const pageNum = Math.max(1, parseInt(page as string, 10));
    const limitNum = Math.max(1, Math.min(100, parseInt(limit as string, 10)));
    const skip = (pageNum - 1) * limitNum;

    const where: any = { eventId };

    if (checkpointId) {
      where.checkpointId = checkpointId;
    }

    if (startDate || endDate) {
      where.scannedAt = {};
      if (startDate) where.scannedAt.gte = new Date(startDate as string);
      if (endDate) where.scannedAt.lte = new Date(endDate as string);
    }

    const [logs, total] = await Promise.all([
      prisma.scanLog.findMany({
        where,
        include: {
          attendee: true,
          checkpoint: true,
        },
        orderBy: { scannedAt: 'desc' },
        skip,
        take: limitNum,
      }),
      prisma.scanLog.count({ where }),
    ]);

    res.json({
      logs,
      pagination: {
        page: pageNum,
        limit: limitNum,
        total,
        totalPages: Math.ceil(total / limitNum),
      },
    });
  } catch (error) {
    console.error('getScannerHistory error:', error);
    res.status(500).json({ error: 'Failed to fetch scanner history' });
  }
}
