import express from 'express';
import { Prisma, PrismaClient } from '@prisma/client';
import { requireAuth, type AuthTokenPayload } from '../middleware/auth';
import { sendEmail } from '../services/email';

const router = express.Router();
const prisma = new PrismaClient();
const FRONTEND_URL = process.env.FRONTEND_URL || 'http://localhost:5173';

type AuthedRequest = express.Request & { authUser?: AuthTokenPayload };
type MeetingForEmail = {
  id: string;
  title: string;
  agenda: string | null;
  date: Date;
  startTime: Date;
  endTime: Date;
  meetingLink: string | null;
};
type UserSummary = {
  id: string;
  fullName: string;
  email: string;
};

const normalizeEmail = (email: string) => email.trim().toLowerCase();
const isValidEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);

const getAuthUser = (req: express.Request) => (req as AuthedRequest).authUser as AuthTokenPayload;
const getRouteParam = (value: string | string[] | undefined) => (Array.isArray(value) ? value[0] || '' : value || '');

const getMeetingInclude = () => ({
  admin: {
    select: {
      id: true,
      fullName: true,
      email: true
    }
  },
  participants: {
    include: {
      user: {
        select: {
          id: true,
          fullName: true,
          email: true,
          profilePhoto: true
        }
      }
    },
    orderBy: { createdAt: 'asc' as const }
  }
});

const parseInvitees = (rawInvitees: unknown) => {
  const invitees = Array.isArray(rawInvitees)
    ? rawInvitees
    : typeof rawInvitees === 'string'
      ? rawInvitees.split(/[\n,;]+/)
      : [];

  return Array.from(
    new Set(
      invitees
        .map((value) => normalizeEmail(String(value || '')))
        .filter(Boolean)
    )
  );
};

const escapeHtml = (value: string) =>
  value
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');

const formatDate = (date: Date) =>
  new Intl.DateTimeFormat('en-US', {
    weekday: 'short',
    year: 'numeric',
    month: 'short',
    day: 'numeric'
  }).format(date);

const formatTime = (date: Date) =>
  new Intl.DateTimeFormat('en-US', {
    hour: '2-digit',
    minute: '2-digit'
  }).format(date);

const sendMeetingInviteEmail = async (to: string, meeting: MeetingForEmail, admin: UserSummary) => {
  const meetingUrl = `${FRONTEND_URL}/user/meetings?meetingId=${encodeURIComponent(meeting.id)}`;
  const meetingLinkHtml = meeting.meetingLink
    ? `<tr><td style="padding: 8px 0; border-bottom: 1px solid #eee;"><strong>Meeting Link:</strong></td><td style="padding: 8px 0; border-bottom: 1px solid #eee;"><a href="${escapeHtml(meeting.meetingLink)}">${escapeHtml(meeting.meetingLink)}</a></td></tr>`
    : '';
  const agendaHtml = meeting.agenda
    ? `<p><strong>Agenda:</strong> ${escapeHtml(meeting.agenda)}</p>`
    : '';

  await sendEmail(
    to,
    `Meeting invitation: ${meeting.title}`,
    `<div style="font-family: sans-serif; max-width: 600px; margin: auto; border: 1px solid #eee; padding: 20px;">
      <h1 style="color: #006039;">Meeting Invitation</h1>
      <p>${escapeHtml(admin.fullName)} invited you to a meeting.</p>
      <h2 style="margin-bottom: 4px;">${escapeHtml(meeting.title)}</h2>
      ${agendaHtml}
      <table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
        <tr><td style="padding: 8px 0; border-bottom: 1px solid #eee;"><strong>Date:</strong></td><td style="padding: 8px 0; border-bottom: 1px solid #eee;">${formatDate(meeting.date)}</td></tr>
        <tr><td style="padding: 8px 0; border-bottom: 1px solid #eee;"><strong>Time:</strong></td><td style="padding: 8px 0; border-bottom: 1px solid #eee;">${formatTime(meeting.startTime)} - ${formatTime(meeting.endTime)}</td></tr>
        ${meetingLinkHtml}
      </table>
      <p>Sign in with this email address to accept or decline the invitation.</p>
      <div style="text-align: center; margin: 30px 0;">
        <a href="${meetingUrl}" style="background-color: #006039; color: white; padding: 12px 25px; text-decoration: none; border-radius: 5px; font-weight: bold;">Open Invitation</a>
      </div>
      <p style="font-size: 13px; color: #666;">If you do not have an account yet, create one using this same email address, verify it, then open the invitation again.</p>
      <hr style="border: none; border-top: 1px solid #eee; margin-top: 30px;"/>
      <p style="font-size: 12px; color: #666;">AHRI Training Room Management System</p>
    </div>`
  );
};

const queueMeetingInviteEmails = (invitees: string[], meeting: MeetingForEmail, admin: UserSummary) => {
  if (invitees.length === 0) return;

  Promise.all(invitees.map((email) => sendMeetingInviteEmail(email, meeting, admin)))
    .catch((error) => {
      console.error('Meeting invitation email queue failed:', error);
    });
};

const getExistingUsersByEmail = async (emails: string[]) => {
  if (emails.length === 0) {
    return new Map<string, UserSummary>();
  }

  const users = await prisma.user.findMany({
    where: { email: { in: emails } },
    select: { id: true, fullName: true, email: true }
  });

  return new Map(users.map((user) => [normalizeEmail(user.email), user]));
};

const getMeetingForAdmin = async (meetingId: string, adminId: string) => {
  const meeting = await prisma.meeting.findUnique({
    where: { id: meetingId },
    include: {
      participants: true
    }
  });

  if (!meeting) {
    return { meeting: null, status: 404, error: 'Meeting not found.' };
  }

  if (meeting.adminId !== adminId) {
    return { meeting: null, status: 403, error: 'Only the meeting admin can manage this meeting.' };
  }

  return { meeting, status: 200, error: null };
};

router.get('/my', requireAuth, async (req, res) => {
  try {
    const authUser = getAuthUser(req);
    const email = normalizeEmail(authUser.email);

    const meetings = await prisma.meeting.findMany({
      where: {
        OR: [
          { adminId: authUser.id },
          {
            participants: {
              some: {
                OR: [
                  { userId: authUser.id },
                  { email }
                ]
              }
            }
          }
        ]
      },
      include: getMeetingInclude(),
      orderBy: [
        { date: 'asc' },
        { startTime: 'asc' }
      ]
    });

    res.json(meetings);
  } catch (error) {
    console.error('Fetch meetings error:', error);
    res.status(500).json({ error: 'Failed to fetch meetings.' });
  }
});

router.get('/:id', requireAuth, async (req, res) => {
  try {
    const authUser = getAuthUser(req);
    const email = normalizeEmail(authUser.email);
    const meetingId = getRouteParam(req.params.id);

    const meeting = await prisma.meeting.findFirst({
      where: {
        id: meetingId,
        OR: [
          { adminId: authUser.id },
          {
            participants: {
              some: {
                OR: [
                  { userId: authUser.id },
                  { email }
                ]
              }
            }
          }
        ]
      },
      include: getMeetingInclude()
    });

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

    res.json(meeting);
  } catch (error) {
    console.error('Fetch meeting error:', error);
    res.status(500).json({ error: 'Failed to fetch meeting.' });
  }
});

router.post('/', requireAuth, async (req, res) => {
  try {
    const authUser = getAuthUser(req);
    const title = String(req.body.title || '').trim();
    const agenda = String(req.body.agenda || '').trim();
    const meetingLink = String(req.body.meetingLink || '').trim();
    const rawInvitees = parseInvitees(req.body.invitees);

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

    const start = new Date(String(req.body.startTime || ''));
    const end = new Date(String(req.body.endTime || ''));
    const meetingDate = new Date(String(req.body.date || ''));

    if (Number.isNaN(meetingDate.getTime()) || Number.isNaN(start.getTime()) || Number.isNaN(end.getTime()) || start >= end) {
      return res.status(400).json({ error: 'Invalid meeting date or time range.' });
    }

    const admin = await prisma.user.findUnique({
      where: { id: authUser.id },
      select: { id: true, fullName: true, email: true }
    });

    if (!admin) {
      return res.status(404).json({ error: 'Your account was not found. Please sign in again.' });
    }

    const adminEmail = normalizeEmail(admin.email);
    const invitees = rawInvitees.filter((email) => email !== adminEmail);
    const invalidEmails = invitees.filter((email) => !isValidEmail(email));

    if (invalidEmails.length > 0) {
      return res.status(400).json({ error: `Invalid email address: ${invalidEmails[0]}` });
    }

    const usersByEmail = await getExistingUsersByEmail(invitees);

    const meeting = await prisma.meeting.create({
      data: {
        title,
        agenda: agenda || null,
        date: meetingDate,
        startTime: start,
        endTime: end,
        meetingLink: meetingLink || null,
        adminId: admin.id,
        participants: {
          create: [
            {
              email: adminEmail,
              userId: admin.id,
              role: 'ADMIN',
              status: 'ACCEPTED',
              invitedById: admin.id
            },
            ...invitees.map((email) => ({
              email,
              userId: usersByEmail.get(email)?.id || null,
              role: 'MEMBER' as const,
              status: 'PENDING' as const,
              invitedById: admin.id
            }))
          ]
        }
      },
      include: getMeetingInclude()
    });

    queueMeetingInviteEmails(invitees, meeting, admin);

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

router.patch('/:id', requireAuth, async (req, res) => {
  try {
    const authUser = getAuthUser(req);
    const meetingId = getRouteParam(req.params.id);
    const { meeting, status, error } = await getMeetingForAdmin(meetingId, authUser.id);

    if (!meeting) {
      return res.status(status).json({ error });
    }

    const updateData: Prisma.MeetingUpdateInput = {};

    if (req.body.title !== undefined) {
      const title = String(req.body.title || '').trim();
      if (!title) {
        return res.status(400).json({ error: 'Meeting title is required.' });
      }
      updateData.title = title;
    }

    if (req.body.agenda !== undefined) {
      const agenda = String(req.body.agenda || '').trim();
      updateData.agenda = agenda || null;
    }

    if (req.body.meetingLink !== undefined) {
      const meetingLink = String(req.body.meetingLink || '').trim();
      updateData.meetingLink = meetingLink || null;
    }

    if (req.body.date !== undefined) {
      const date = new Date(String(req.body.date || ''));
      if (Number.isNaN(date.getTime())) {
        return res.status(400).json({ error: 'Invalid meeting date.' });
      }
      updateData.date = date;
    }

    if (req.body.startTime !== undefined || req.body.endTime !== undefined) {
      const start = req.body.startTime !== undefined ? new Date(String(req.body.startTime || '')) : meeting.startTime;
      const end = req.body.endTime !== undefined ? new Date(String(req.body.endTime || '')) : meeting.endTime;

      if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime()) || start >= end) {
        return res.status(400).json({ error: 'Invalid meeting time range.' });
      }

      updateData.startTime = start;
      updateData.endTime = end;
    }

    const updatedMeeting = await prisma.meeting.update({
      where: { id: meeting.id },
      data: updateData,
      include: getMeetingInclude()
    });

    res.json(updatedMeeting);
  } catch (error) {
    console.error('Update meeting error:', error);
    res.status(500).json({ error: 'Failed to update meeting.' });
  }
});

router.post('/:id/invite', requireAuth, async (req, res) => {
  try {
    const authUser = getAuthUser(req);
    const meetingId = getRouteParam(req.params.id);
    const { meeting, status, error } = await getMeetingForAdmin(meetingId, authUser.id);

    if (!meeting) {
      return res.status(status).json({ error });
    }

    const admin = await prisma.user.findUnique({
      where: { id: authUser.id },
      select: { id: true, fullName: true, email: true }
    });

    if (!admin) {
      return res.status(404).json({ error: 'Your account was not found. Please sign in again.' });
    }

    const existingEmails = new Set(meeting.participants.map((participant) => normalizeEmail(participant.email)));
    const invitees = parseInvitees(req.body.invitees).filter((email) => !existingEmails.has(email));
    const invalidEmails = invitees.filter((email) => !isValidEmail(email));

    if (invalidEmails.length > 0) {
      return res.status(400).json({ error: `Invalid email address: ${invalidEmails[0]}` });
    }

    const usersByEmail = await getExistingUsersByEmail(invitees);

    if (invitees.length > 0) {
      await prisma.meetingParticipant.createMany({
        data: invitees.map((email) => ({
          meetingId: meeting.id,
          email,
          userId: usersByEmail.get(email)?.id || null,
          role: 'MEMBER',
          status: 'PENDING',
          invitedById: admin.id
        })),
        skipDuplicates: true
      });

      queueMeetingInviteEmails(invitees, meeting, admin);
    }

    const refreshedMeeting = await prisma.meeting.findUnique({
      where: { id: meeting.id },
      include: getMeetingInclude()
    });

    res.json(refreshedMeeting);
  } catch (error) {
    console.error('Invite members error:', error);
    res.status(500).json({ error: 'Failed to invite members.' });
  }
});

router.put('/:id/respond', requireAuth, async (req, res) => {
  try {
    const authUser = getAuthUser(req);
    const email = normalizeEmail(authUser.email);
    const status = String(req.body.status || '').trim().toUpperCase();
    const meetingId = getRouteParam(req.params.id);

    if (!['ACCEPTED', 'DECLINED'].includes(status)) {
      return res.status(400).json({ error: 'Response must be ACCEPTED or DECLINED.' });
    }

    const participant = await prisma.meetingParticipant.findFirst({
      where: {
        meetingId,
        OR: [
          { userId: authUser.id },
          { email }
        ]
      }
    });

    if (!participant) {
      return res.status(404).json({ error: 'Invitation not found for your account.' });
    }

    await prisma.meetingParticipant.update({
      where: { id: participant.id },
      data: {
        status: status as 'ACCEPTED' | 'DECLINED',
        userId: participant.userId || authUser.id
      }
    });

    const meeting = await prisma.meeting.findUnique({
      where: { id: meetingId },
      include: getMeetingInclude()
    });

    res.json(meeting);
  } catch (error) {
    console.error('Respond to meeting error:', error);
    res.status(500).json({ error: 'Failed to update meeting response.' });
  }
});

router.delete('/:id/participants/:participantId', requireAuth, async (req, res) => {
  try {
    const authUser = getAuthUser(req);
    const meetingId = getRouteParam(req.params.id);
    const participantId = getRouteParam(req.params.participantId);
    const { meeting, status, error } = await getMeetingForAdmin(meetingId, authUser.id);

    if (!meeting) {
      return res.status(status).json({ error });
    }

    const participant = await prisma.meetingParticipant.findUnique({
      where: { id: participantId }
    });

    if (!participant || participant.meetingId !== meeting.id) {
      return res.status(404).json({ error: 'Participant not found.' });
    }

    if (participant.role === 'ADMIN') {
      return res.status(400).json({ error: 'The meeting admin cannot be removed.' });
    }

    await prisma.meetingParticipant.delete({
      where: { id: participant.id }
    });

    const refreshedMeeting = await prisma.meeting.findUnique({
      where: { id: meeting.id },
      include: getMeetingInclude()
    });

    res.json(refreshedMeeting);
  } catch (error) {
    console.error('Remove meeting participant error:', error);
    res.status(500).json({ error: 'Failed to remove participant.' });
  }
});

router.delete('/:id', requireAuth, async (req, res) => {
  try {
    const authUser = getAuthUser(req);
    const meetingId = getRouteParam(req.params.id);
    const { meeting, status, error } = await getMeetingForAdmin(meetingId, authUser.id);

    if (!meeting) {
      return res.status(status).json({ error });
    }

    await prisma.meeting.delete({
      where: { id: meeting.id }
    });

    res.json({ message: 'Meeting cancelled successfully.' });
  } catch (error) {
    console.error('Cancel meeting error:', error);
    res.status(500).json({ error: 'Failed to cancel meeting.' });
  }
});

export default router;
