import type { Request, Response } from 'express';
import asyncHandler from 'express-async-handler';
import { prisma } from '../config/db.ts';
import { toIST } from '../utils/time.ts';

export const createPlan = asyncHandler(async (req: Request, res: Response) => {
    const { planName, planType, actualPrice, discountPercentage, gstPercentage, duration, features, description, institutionIds } = req.body;

    if (!planName || !planType || !actualPrice || discountPercentage === undefined || gstPercentage === undefined || !duration) {
        res.status(400).json({ message: "Missing required fields" });
        return;
    }

    const exists = await prisma.subscriptionPlan.findFirst({
        where: {
            planType: planType,
        }
    });

    if (exists) {
        res.status(400).json({ message: "Plan with this type already exists" });
        return;
    }

    const priceAfterDiscount = parseFloat(actualPrice) - (parseFloat(actualPrice) * parseFloat(discountPercentage) / 100);
    // const gstAmount = priceAfterDiscount * parseFloat(gstPercentage) / 100;
    const finalPrice = priceAfterDiscount;
    //  gstAmount;

    const plan = await prisma.subscriptionPlan.create({
        data: {
            planName: planName,
            planType: planType,
            actualPrice: parseFloat(actualPrice),
            discountPercentage: parseFloat(discountPercentage),
            gstPercentage: parseFloat(gstPercentage),
            priceAfterDiscount: priceAfterDiscount,
            duration: parseInt(duration),
            features: Array.isArray(features) ? features : [],
            description,
            ...(institutionIds && Array.isArray(institutionIds) ? {
                institutionSubscriptions: {
                    create: institutionIds.map((id: string) => ({
                        institutionId: id
                    }))
                }
            } : {}),
            isActive: true,
            isDeleted: false
        }
    });

    res.status(201).json({
        message: "Subscription plan created successfully",
        data: {
            ...plan,
            finalPrice,
            createdAt: toIST(plan.createdAt),
            updatedAt: toIST(plan.updatedAt),
        }
    });
});

export const listPlans = asyncHandler(async (req: Request, res: Response) => {
    const search = req.query.search?.toString() || "";
    const institutionId = req.query.institutionId?.toString();
    const includeHidden = req.query.includeHidden === 'true';
    const page = Math.max(1, Number(req.query.page || 1));
    const limit = Math.max(1, Number(req.query.limit || 10));
    const skip = (page - 1) * limit;
    const user = (req as any).user;
    const isAdmin = user.role === "ADMIN";
    const andConditions: any[] = [
        { isDeleted: false }
    ];

    let targetInstitutionId: string | undefined = undefined;
    if (user.role === "INSTITUTION" || user.role === "STAFF") {
        targetInstitutionId = user.institutionId;
    } else if (institutionId) {
        targetInstitutionId = institutionId;
    }

    let institutionHasStudents = true;
    if (targetInstitutionId) {
        const studentCount = await prisma.studentInstitution.count({ where: { institutionId: targetInstitutionId, deletedAt: null } });
        institutionHasStudents = studentCount > 0;
    }

    if (!includeHidden) {
        andConditions.push({ isActive: true });
    }

    if (user.role === "INSTITUTION" || user.role === "STAFF") {
        if (includeHidden) {
            const orArr: any[] = [
                { institutionSubscriptions: { some: { institutionId: user.institutionId } } }
            ];
            if (institutionHasStudents) {
                orArr.push({ AND: [{ institutionSubscriptions: { none: {} } }, { planType: "trial" }] });
            }
            andConditions.push({ OR: orArr });
        } else {
            andConditions.push({
                NOT: {
                    institutionSubscriptions: {
                        some: { institutionId: user.institutionId, isVisible: false }
                    }
                }
            });
            const orArr: any[] = [
                { institutionSubscriptions: { some: { institutionId: user.institutionId } } }
            ];
            if (institutionHasStudents) {
                orArr.push({ AND: [{ institutionSubscriptions: { none: {} } }, { planType: "trial" }] });
            }
            andConditions.push({ OR: orArr });
        }
    } else if (institutionId) {
        if (includeHidden) {
            const orArr: any[] = [
                { institutionSubscriptions: { some: { institutionId } } }
            ];
            if (institutionHasStudents) {
                orArr.push({ AND: [{ institutionSubscriptions: { none: {} } }, { planType: "trial" }] });
            }
            andConditions.push({ OR: orArr });
        } else {
            andConditions.push({
                NOT: {
                    institutionSubscriptions: {
                        some: { institutionId, isVisible: false }
                    }
                }
            });
            const orArr: any[] = [
                { institutionSubscriptions: { some: { institutionId } } }
            ];
            if (institutionHasStudents) {
                orArr.push({ AND: [{ institutionSubscriptions: { none: {} } }, { planType: "trial" }] });
            }
            andConditions.push({ OR: orArr });
        }
    }

    if (search) {
        andConditions.push({
            OR: [
                { planName: { contains: search } },
                { planType: { contains: search } },
                { description: { contains: search } },
            ]
        });
    }

    const where: any = andConditions.length > 1 ? { AND: andConditions } : andConditions[0];

    const orderBy: any = { priceAfterDiscount: 'asc' };

    const [plans, totalPlans] = await Promise.all([
        prisma.subscriptionPlan.findMany({
            where,
            include: {
                institutionSubscriptions: {
                    include: {
                        institution: {
                            select: {
                                id: true,
                                user: {
                                    select: { institutionName: true }
                                }
                            }
                        }
                    }
                },
                _count: {
                    select: {
                        subscriptions: true,
                    }
                }
            },
            orderBy,
            skip,
            take: limit,
        }),
        prisma.subscriptionPlan.count({ where })
    ]);

    const planTypes = [...new Set(plans.map((p: any) => p.planType))];
    const activeSubscriptions = await prisma.subscription.findMany({
        where: { plan: { planType: { in: planTypes } } },
        select: {
            plan: { select: { planType: true } },
            institutionId: true,
            studentInstitution: { select: { institutionId: true } },
            student: { select: { institutionId: true } }
        }
    });

    const activeMap = new Map<string, Set<string>>();
    for (const sub of activeSubscriptions) {
        if (!sub.plan?.planType) continue;
        const pType = sub.plan.planType.toLowerCase();
        if (!activeMap.has(pType)) activeMap.set(pType, new Set());
        const set = activeMap.get(pType)!;
        
        if (sub.institutionId) set.add(sub.institutionId);
        if (sub.studentInstitution?.institutionId) set.add(sub.studentInstitution.institutionId);
        if (sub.student?.institutionId) set.add(sub.student.institutionId);
    }

    const verifiedInstitutionsList = await prisma.institution.findMany({
        where: { user: { isVerified: true, isDeleted: false } },
        select: { id: true }
    });
    if (!activeMap.has('trial')) activeMap.set('trial', new Set());
    const trialSet = activeMap.get('trial')!;
    for (const inst of verifiedInstitutionsList) {
        trialSet.add(inst.id);
    }

    const finalPlans = plans.map((plan: any) => {
        const mappedInstIds = new Set(plan.institutionSubscriptions.map((s: any) => s.institutionId));
        const activeInsts = activeMap.get(plan.planType) || new Set();

        for (const instId of activeInsts) {
            if (!mappedInstIds.has(instId)) {
                plan.institutionSubscriptions.push({
                    id: `dynamic_${instId}`,
                    institutionId: instId,
                    subscriptionPlanId: plan.id,
                    isVisible: true,
                    isUsed: true
                });
                mappedInstIds.add(instId);
            }
        }

        return {
            ...plan,
            usedCount: plan._count?.subscriptions ?? 0,
            finalPrice: plan.priceAfterDiscount + (plan.priceAfterDiscount * plan.gstPercentage / 100),
            createdAt: toIST(plan.createdAt),
            updatedAt: toIST(plan.updatedAt),
        };
    });

    res.status(200).json({
        message: "Plans fetched successfully",
        data: finalPlans,
        meta: {
            total: totalPlans,
            page,
            limit,
            totalPages: Math.ceil(totalPlans / limit)
        }
    });
});

export const mapPlanToInstitutions = asyncHandler(async (req: Request, res: Response) => {
    const { id } = req.params;
    const { institutionIds } = req.body;

    if (!Array.isArray(institutionIds)) {
        res.status(400).json({ message: "institutionIds must be an array" });
        return;
    }

    const plan = await prisma.subscriptionPlan.findUnique({
        where: { id, isDeleted: false }
    });

    if (!plan) {
        res.status(404).json({ message: "Plan not found" });
        return;
    }

    if (plan.planType.toLowerCase() === 'trial') {
        res.status(400).json({ message: "Institutions are automatically assigned to the Trial plan and cannot be manually mapped or removed." });
        return;
    }

    const uniqueInstitutionIds = Array.from(new Set(institutionIds.filter(Boolean)));

    await prisma.$transaction(async (tx) => {
        if (uniqueInstitutionIds.length === 0) {
            await tx.institutionSubscription.deleteMany({
                where: { subscriptionPlanId: id }
            });
        } else {
            await tx.institutionSubscription.deleteMany({
                where: {
                    subscriptionPlanId: id,
                    institutionId: { notIn: uniqueInstitutionIds }
                }
            });

            const existing = await tx.institutionSubscription.findMany({
                where: {
                    subscriptionPlanId: id,
                    institutionId: { in: uniqueInstitutionIds }
                }
            });

            const existingInstitutionIds = new Set(existing.map((item) => item.institutionId));
            const newInstitutionIds = uniqueInstitutionIds.filter((institutionId: string) => !existingInstitutionIds.has(institutionId));

            if (newInstitutionIds.length > 0) {
                await tx.institutionSubscription.createMany({
                    data: newInstitutionIds.map((institutionId: string) => ({
                        subscriptionPlanId: id,
                        institutionId,
                        isVisible: true
                    }))
                });
            }
        }
    });

    const updatedPlan = await prisma.subscriptionPlan.findUnique({
        where: { id },
        include: {
            institutionSubscriptions: {
                include: {
                    institution: {
                        select: {
                            id: true,
                            user: {
                                select: { institutionName: true }
                            }
                        }
                    }
                }
            }
        }
    });

    res.status(200).json({
        message: "Plan mapped to institutions successfully",
        data: updatedPlan
    });
});

export const getPlanInstitutionMappings = asyncHandler(async (req: Request, res: Response) => {
    const { id } = req.params;

    const plan = await prisma.subscriptionPlan.findUnique({
        where: { id, isDeleted: false },
        include: { 
            institutionSubscriptions: {
                include: {
                    institution: {
                        select: {
                            id: true,
                            user: {
                                select: { institutionName: true }
                            }
                        }
                    }
                }
            }
        }
    });

    if (!plan) {
        res.status(404).json({ message: "Plan not found" });
        return;
    }

    const subscriptions = await prisma.subscription.findMany({
        where: {
            planId: id
        },
        select: {
            planId: true,
            institutionId: true,
            studentInstitution: { select: { institutionId: true } },
            student: { select: { institutionId: true } }
        }
    });

    const usedSet = new Set<string>();
    for (const sub of subscriptions) {
        if (sub.institutionId) usedSet.add(sub.institutionId);
        if (sub.studentInstitution?.institutionId) usedSet.add(sub.studentInstitution.institutionId);
        if (sub.student?.institutionId) usedSet.add(sub.student.institutionId);
    }

    if (plan.planType.toLowerCase() === 'trial') {
        const verifiedInstitutionsList = await prisma.institution.findMany({
            where: { user: { isVerified: true, isDeleted: false } },
            select: { id: true }
        });
        for (const inst of verifiedInstitutionsList) {
            usedSet.add(inst.id);
        }
    }

    const mappedInstitutionIds = new Set<string>();
    const data = plan.institutionSubscriptions.map((sub: any) => {
        mappedInstitutionIds.add(sub.institutionId);
        return {
            ...sub,
            isUsed: usedSet.has(sub.institutionId)
        };
    });

    const unmappedInstitutionIds = Array.from(usedSet).filter(instId => !mappedInstitutionIds.has(instId));
    
    if (unmappedInstitutionIds.length > 0) {
        const unmappedInstitutions = await prisma.institution.findMany({
            where: { id: { in: unmappedInstitutionIds } },
            select: {
                id: true,
                user: { select: { institutionName: true } }
            }
        });

        for (const inst of unmappedInstitutions) {
            data.push({
                id: `dynamic_${inst.id}`,
                institutionId: inst.id,
                subscriptionPlanId: id,
                isVisible: true,
                isUsed: true,
                institution: inst
            });
        }
    }

    res.status(200).json({
        message: "Institution mappings fetched successfully",
        data,
    });
});

export const updateInstitutionSubscriptionVisibility = asyncHandler(async (req: Request, res: Response) => {
    const { id, institutionId } = req.params;
    const { isVisible } = req.body;

    if (typeof isVisible !== 'boolean') {
        res.status(400).json({ message: "isVisible must be a boolean" });
        return;
    }

    const plan = await prisma.subscriptionPlan.findUnique({
        where: { id, isDeleted: false }
    });

    if (!plan) {
        res.status(404).json({ message: "Plan not found" });
        return;
    }

    const institutionSubscription = await prisma.institutionSubscription.findFirst({
        where: {
            subscriptionPlanId: id,
            institutionId
        }
    });

    let updatedSubscription;
    if (!institutionSubscription) {
        updatedSubscription = await prisma.institutionSubscription.create({
            data: {
                subscriptionPlanId: id,
                institutionId,
                isVisible
            }
        });
    } else {
        updatedSubscription = await prisma.institutionSubscription.update({
            where: { id: institutionSubscription.id },
            data: { isVisible }
        });
    }

    res.status(200).json({
        message: "Institution visibility updated successfully",
        data: updatedSubscription,
    });
});

export const updatePlan = asyncHandler(async (req: Request, res: Response) => {
    const { id } = req.params;

    const {
        planName,
        actualPrice: bodyActualPrice,
        discountPercentage: bodyDiscountPercentage,
        gstPercentage,
        duration,
        features,
        description,
        isVisible,
        institutionIds
    } = req.body;

    const oldPlan = await prisma.subscriptionPlan.findUnique({
        where: { id, isDeleted: false },
        include: { institutionSubscriptions: true }
    });

    if (!oldPlan) {
        res.status(404).json({ message: "Plan not found" });
        return;
    }

    const actualPrice = Number(bodyActualPrice !== undefined ? bodyActualPrice : oldPlan.actualPrice);

    const discountPercentage = Number(bodyDiscountPercentage !== undefined ? bodyDiscountPercentage : oldPlan.discountPercentage);

    const priceAfterDiscount = bodyActualPrice !== undefined || bodyDiscountPercentage !== undefined
        ? Number((actualPrice - (actualPrice * discountPercentage) / 100).toFixed(2))
        : oldPlan.priceAfterDiscount;

    const newPlanData = {
        planName: planName ?? oldPlan.planName,
        planType: oldPlan.planType,
        actualPrice,
        discountPercentage,
        gstPercentage: gstPercentage !== undefined ? Number(gstPercentage) : oldPlan.gstPercentage,
        priceAfterDiscount,
        duration: duration !== undefined ? Number(duration) : oldPlan.duration,
        features: features ?? oldPlan.features,
        description: description ?? oldPlan.description,
        isActive: true,
        isDeleted: false,
    };

    const newPlan = await prisma.$transaction(async (tx) => {
        await tx.subscriptionPlan.updateMany({
            where: { planType: oldPlan.planType },
            data: { isActive: false },
        });

        const instIdsToMap = institutionIds && Array.isArray(institutionIds)
            ? institutionIds
            : oldPlan.institutionSubscriptions.map((sub: any) => sub.institutionId);

        const oldVisibilityMap = new Map();
        oldPlan.institutionSubscriptions.forEach((sub: any) => {
            oldVisibilityMap.set(sub.institutionId, sub.isVisible);
        });

        const createdPlan = await tx.subscriptionPlan.create({
            data: {
                ...newPlanData,
                institutionSubscriptions: {
                    create: instIdsToMap.map((instId: string) => ({
                        institutionId: instId,
                        isVisible: oldVisibilityMap.has(instId) ? oldVisibilityMap.get(instId) : (isVisible !== undefined ? isVisible : true)
                    }))
                }
            },
        });
        return createdPlan;
    });

    res.status(200).json({
        message: "Plan updated successfully",
        data: {
            ...newPlan,
            createdAt: toIST(newPlan.createdAt),
            updatedAt: toIST(newPlan.updatedAt),
        },
    });
});

export const deletePlan = asyncHandler(async (req: Request, res: Response) => {
    const { id } = req.params;
    const deleteAll = req.query.deleteAll === 'true';

    const plan = await prisma.subscriptionPlan.findUnique({
        where: { id, isDeleted: false },
    });

    if (!plan) {
        res.status(404).json({ message: "Plan not found" });
        return;
    }

    const assignedCount = await prisma.subscription.count({
        where: { planId: id }
    });

    if (assignedCount > 0) {
        res.status(400).json({ message: "Plan cannot be deleted as it is assigned to some students." });
        return;
    }

    if (deleteAll) {
        await prisma.subscriptionPlan.updateMany({
            where: { planType: plan.planType },
            data: { isDeleted: true }
        });
        res.status(200).json({
            message: "Plan pack and its entire history deleted successfully",
            deletedId: id
        });
    } else {
        await prisma.subscriptionPlan.update({
            where: { id },
            data: { isDeleted: true }
        });
        res.status(200).json({
            message: "Plan version deleted successfully",
            deletedId: id
        });
    }
});

export const getPlanHistory = asyncHandler(async (req: Request, res: Response) => {
    const planType = req.query.planType?.toString().toLowerCase().trim() || "trial";
    const page = Number(req.query.page || 1);
    const limit = Number(req.query.limit || 10);
    const skip = (page - 1) * limit;

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

    const plans = await prisma.subscriptionPlan.findMany({
        where,
        orderBy: { updatedAt: 'desc' },
        skip,
        take: limit
    });

    const finalPlans = plans.map((plan: any) => ({
        ...plan,
        finalPrice: plan.priceAfterDiscount,
        basicPrice: Number(plan.priceAfterDiscount * 100 / (100 + plan.gstPercentage)).toFixed(2),
        gstAmount: Number(plan.priceAfterDiscount * (plan.gstPercentage / (100 + plan.gstPercentage))).toFixed(2),
        createdAt: toIST(plan.createdAt),
        updatedAt: toIST(plan.updatedAt),
    }));

    const totalPlans = await prisma.subscriptionPlan.count({
        where,
    });

    res.status(200).json({
        message: "Plans fetched successfully",
        data: finalPlans,
        meta: {
            total: totalPlans,
            page,
            limit,
            totalPages: Math.ceil(totalPlans / limit)
        }
    });
});
