import type { Request, Response } from 'express';
import asyncHandler from 'express-async-handler';
import bcrypt from 'bcrypt';
import { prisma } from '../config/db.ts';
import { generateReferralCode } from '../utils/utils.ts';
import { toIST, tokenExpireTime } from '../utils/time.ts';
import { sendEmail } from '../utils/mail.ts';
import { verificationTemplate } from '../utils/verificationMail.ts';
import { v4 as uuidv4 } from 'uuid';

export const createStaff = asyncHandler(async (req: Request, res: Response) => {
    const { firstName, lastName, email, phone, password, roleId, designation, subjects } = req.body;

    if (!firstName) {
        res.status(400).json({ message: "First name is required" });
        return;
    }
    if (!email) {
        res.status(400).json({ message: "Email is required" });
        return;
    }
    if (!password) {
        res.status(400).json({ message: "Password is required" });
        return;
    }
    if (!roleId) {
        res.status(400).json({ message: "Role ID is required" });
        return;
    }

    const exists = await prisma.user.findFirst({ where: { email, isDeleted: false } });
    if (exists) {
        res.status(400).json({ message: "Email already used" });
        return;
    }

    const hash = await bcrypt.hash(password, 10);
    const user = (req as any).user;
    const adminId = user.id;

    const referralCode = await generateReferralCode();

    const result = await prisma.$transaction(async (tx) => {
        const newUser = await tx.user.create({
            data: {
                firstName,
                lastName,
                email,
                phone,
                password: hash,
                role: "STAFF",
                createdById: adminId,
                expiresAt: tokenExpireTime(),
            }
        });

        const staffInfo = await tx.staff.create({
            data: {
                userId: newUser.id,
                roleId,
                designation,
                subjects: {
                    connect: (subjects || []).map((id: string) => ({ id }))
                },
                institutionId: user.institutionId,
                referralCode,
            }
        });

        const tokenStr = uuidv4();
        await tx.token.create({
            data: {
                userId: newUser.id,
                token: tokenStr,
                institutionId: user.institutionId,
                type: "VERIFY",
                expiresAt: tokenExpireTime(),
            }
        });

        return { userId: newUser.id, staffId: staffInfo.id, token: tokenStr };
    });

    const verifyUrl = `${process.env.BASE_URL}/verify/${result.token}`;

    const emailRes = await sendEmail(
        email,
        "Verify your email",
        verificationTemplate({ firstName, companyName: "ExamInfra", verifyUrl })
    );
    if (!emailRes.success) {
        res.status(500).json({ message: "Failed to send verification email. Please try again later." });
        return;
    }

    res.status(201).json({
        message: "Staff created successfully. Verification email sent.",
        data: { userId: result.userId, staffId: result.staffId }
    });
});

export const listStaffs = asyncHandler(async (req, res) => {
    const user = (req as any).user;
    const search = (req.query.search as string)?.trim();
    const isVerified = (req.query.isVerified as string)?.trim();
    const roleId = (req.query.roleId as string)?.trim();
    const subjectId = (req.query.subjectId as string)?.trim();
    const regStartDate = (req.query.regStartDate as string)?.trim();
    const regEndDate = (req.query.regEndDate as string)?.trim();
    const page = Math.max(parseInt(req.query.page as string) || 1, 1);
    const limit = Math.max(parseInt(req.query.limit as string) || 10, 1);
    const skip = (page - 1) * limit;

    const institutionId = user.role === "ADMIN"
        ? (req.query.institutionId as string | undefined)
        : user.institutionId;

    const where: any = {
        user: { isDeleted: false },
    };

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

    if (search) {
        const searchWords = search.split(/\s+/).filter(Boolean);

        where.OR = [
            { user: { firstName: { contains: search } } },
            { user: { lastName: { contains: search } } },
            { user: { email: { contains: search } } },
            { user: { phone: { contains: search } } },
            { role: { roleName: { contains: search } } },
            { subjects: { some: { subjectName: { contains: search } } } },
            ...(searchWords.length > 1 ? [{
                user: {
                    AND: searchWords.map(word => ({
                        OR: [
                            { firstName: { contains: word } },
                            { lastName: { contains: word } }
                        ]
                    }))
                }
            }] : [])
        ];
    }

    if (isVerified) {
        where.user.isVerified = isVerified === "true";
    }

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

    if (subjectId) {
        where.subjects = {
            some: { id: subjectId }
        };
    }

    if (regStartDate || regEndDate) {
        where.user.createdAt = {};

        const convertToISOFormat = (dateStr: string): string => {
            const parts = dateStr.split('/');
            if (parts.length === 3) {
                const [day, month, year] = parts;
                return `${year}-${month}-${day}`;
            }
            return dateStr;
        };

        if (regStartDate) {
            const formattedStartDate = convertToISOFormat(regStartDate);
            where.user.createdAt.gte = new Date(`${formattedStartDate}T00:00:00.000Z`);
        }
        if (regEndDate) {
            const formattedEndDate = convertToISOFormat(regEndDate);
            where.user.createdAt.lte = new Date(`${formattedEndDate}T23:59:59.999Z`);
        }
    }

    const [total, staffs] = await Promise.all([
        prisma.staff.count({ where }),
        prisma.staff.findMany({
            where,
            orderBy: { user: { firstName: 'asc' } },
            include: {
                user: true,
                role: true,
                // exams: true,
                subjects: true
            },
            skip,
            take: limit,
        }),
    ]);

    const structuredList = staffs.map((staff: any) => {
        const userData = staff.user || {};
        return {
            id: staff.id,
            firstName: userData.firstName ?? "",
            lastName: userData.lastName ?? "",
            email: userData.email ?? "",
            phone: userData.phone ?? "",
            designation: staff.designation ?? "",
            createdBy: userData.createdById ?? "",
            roleId: staff.role?.id || staff.roleId,
            roleName: staff.role?.roleName || "",
            referralCode: staff.referralCode || "",
            // exams: staff.exams,
            subjects: staff.subjects,
            isVerified: userData.isVerified,
            createdAt: toIST(staff.createdAt),
            updatedAt: toIST(staff.updatedAt),
        };
    });

    res.status(200).json({
        message: "Successfully fetched staff list",
        data: structuredList,
        meta: {
            total,
            page,
            limit,
            totalPages: Math.ceil(total / limit),
        },
    });
});

export const updateStaff = asyncHandler(async (req, res) => {
    const staffId = req.params.id;
    const { firstName, lastName, phone, designation, roleId, subjects } = req.body;

    const user = (req as any).user;
    const staffRecord = await prisma.staff.findFirst({
        where: { id: staffId, institutionId: user.institutionId, user: { isDeleted: false } }
    });
    if (!staffRecord) {
        res.status(404).json({ message: "Staff record not found" });
        return;
    }

    const updates: any = {};
    if (firstName || lastName || phone) {
        const userUpdates: any = {};
        if (firstName) userUpdates.firstName = firstName;
        if (lastName) userUpdates.lastName = lastName;
        if (phone) userUpdates.phone = phone;

        await prisma.user.update({
            where: { id: staffRecord.userId },
            data: userUpdates
        });
    }

    const staffUpdates: any = {};
    if (designation) staffUpdates.designation = designation;
    if (roleId) staffUpdates.roleId = roleId;
    if (subjects) {
        staffUpdates.subjects = {
            set: subjects.map((id: string) => ({ id }))
        };
    }
    const updatedStaff = await prisma.staff.update({
        where: { id: staffId },
        data: staffUpdates,
        include: {
            user: true,
            role: true,
            subjects: true
        }
    });

    res.status(200).json({
        message: "Staff updated successfully",
        data: {
            id: updatedStaff.id,
            firstName: updatedStaff.user.firstName,
            lastName: updatedStaff.user.lastName,
            email: updatedStaff.user.email,
            phone: updatedStaff.user.phone,
            designation: updatedStaff.designation,
            roleId: updatedStaff.roleId,
            subjects: updatedStaff.subjects,
        },
    });
});

export const deleteStaff = asyncHandler(async (req, res) => {
    const staffId = req.params.id;

    const user = (req as any).user;
    const staffRecord = await prisma.staff.findFirst({
        where: { id: staffId, institutionId: user.institutionId, user: { isDeleted: false } },
        select: {
            userId: true,
            user: true,
        }
    });
    if (!staffRecord) {
        res.status(400).json({ message: "Staff not found" });
        return;
    }
    const userRecord = staffRecord.user;
    const userId = staffRecord.userId;
    if (userId === user.id) {
        res.status(400).json({ message: "You can not delete yourself" });
        return;
    }
    await prisma.user.update({
        where: { id: userId },
        data: {
            oldEmail: userRecord.email,
            details: {
                email: userRecord.email,
                phone: userRecord.phone,
                firstName: userRecord.firstName,
                lastName: userRecord.lastName,
                createdAt: userRecord.createdAt,
                updatedAt: userRecord.updatedAt,
                createdById: userRecord.createdById,
                role: userRecord.role,
                isVerified: userRecord.isVerified
            },
            email: null,
            phone: null,
            isDeleted: true,
            sessionToken: null
        }
    });
    // await prisma.user.delete({ where: { id: userId } });

    res.status(200).json({
        message: "Staff deleted successfully",
        deletedStaffId: staffId,
        deletedUserId: userId,
    });
});

export const verifyReferralCode = asyncHandler(async (req: Request, res: Response) => {
    const { code } = req.params;
    const user = (req as any).user;

    const staff = await prisma.staff.findFirst({
        where: {
            referralCode: code,
            institutionId: user.institutionId,
            user: { isDeleted: false, isVerified: true }
        },
        include: {
            user: {
                select: {
                    firstName: true,
                    lastName: true,
                    id: true
                }
            }
        }
    });

    if (!staff) {
        res.status(404).json({ message: "Invalid Referral Code" });
        return;
    }

    res.status(200).json({
        message: "Valid Code",
        data: {
            staffName: `${staff.user.firstName} ${staff.user.lastName}`,
            staffUserId: staff.user.id
        }
    });
});
