const Labour = require('../models/Labour');
const Customer = require('../models/Customer');
const TempUser = require('../models/TempUser');
const bcrypt = require('bcrypt');
const crypto = require('crypto');
require('dotenv').config();
const jwt = require('jsonwebtoken');
const WorkOrder = require('../models/WorkOrder'); 
const axios = require('axios');
const emailService = require('../utils/emailService');

// 1. Register function (No changes needed here)
exports.register = async (req, res) => {
    try {
        const { name, email, phone, password, address, userType, labourType } = req.body;

        // Check if a verified user already exists with this email or phone
        const existingLabour = await Labour.findOne({ $or: [{ email }, { phone }] });
        const existingCustomer = await Customer.findOne({ $or: [{ email }, { phone }] });
        if (existingLabour || existingCustomer) {
            return res.status(409).json({ message: 'An account with this email or phone already exists.' });
        }
        
        // Remove any previous unverified attempts with the same email
        await TempUser.deleteOne({ email });

        const hashedPassword = await bcrypt.hash(password, 10);
        
        // Generate a verification token
        const verificationToken = crypto.randomBytes(32).toString('hex');
        const tokenExpires = Date.now() + 3600000; // 1 hour from now

        const tempUserData = {
            name, email, phone,
            password: hashedPassword,
            userType, verificationToken, tokenExpires,
        };

        if (userType === 'labour') {
            tempUserData.address = address;
            tempUserData.labourType = labourType;
            tempUserData.cnicPic = req.files['cnicPic'][0].path.replace(/\\/g, '/');
            tempUserData.policeCertPic = req.files['policeCertPic'][0].path.replace(/\\/g, '/');
            if (req.files['profilePic']) {
                tempUserData.profilePic = req.files['profilePic'][0].path.replace(/\\/g, '/');
            }
        }

        const newTempUser = new TempUser(tempUserData);
        await newTempUser.save();

        // Send verification email
        const verificationUrl = `${process.env.FRONTEND_URL}/verify/${verificationToken}`;
        
        await emailService.sendEmail({
            to: email,
            subject: 'Verify Your Email Address',
            text: `Hello ${name},\n\nPlease verify your email by clicking this link: ${verificationUrl}\nThis link will expire in 1 hour.\n\nThank you,\nYour LaborPK team`,
            html: `<p>Hello ${name},</p><p>Please verify your email by clicking the link below:</p><a href="${verificationUrl}">Verify My Email</a><p>This link will expire in 1 hour.</p><p>Thank you,<br>Your LaborPK team</p>`
        });

        res.status(201).json({ 
            message: 'Registration successful! Please check your email to verify your account.'
        });
        
    } catch (error) {
        console.error("Registration error:", error);
        res.status(500).json({ message: 'Server error during registration.' });
    }
};


// 2. DEFINITIVELY FIXED verifyEmail function
exports.verifyEmail = async (req, res) => {
    try {
        const { token } = req.body;

        if (!token) {
            return res.status(400).json({ message: 'Verification token is required.' });
        }

        // ** ATOMIC OPERATION: Find and delete the token in one step. **
        // This prevents race conditions where two requests could process the same token.
        // The operation is guaranteed to be safe.
        const tempUser = await TempUser.findOneAndDelete({
            verificationToken: token,
            tokenExpires: { $gt: Date.now() }
        });

        // If no tempUser is returned, the token is invalid, expired, or has ALREADY been used by another request.
        if (!tempUser) {
            // Check if a user now exists, which would mean they just verified successfully.
            const alreadyExists = await Labour.findOne({ email: req.body.email }) || await Customer.findOne({ email: req.body.email });
            if (alreadyExists) {
                 return res.status(200).json({ message: 'Email verified successfully! You can now log in.' });
            }
            return res.status(400).json({ message: 'Invalid or expired verification token. Please try registering again.' });
        }
        
        // Since the above operation is atomic, we are GUARANTEED that this code block
        // will only ever run ONCE for a valid token. We no longer need to check for
        // existing users because it's impossible for one to have been created by a duplicate request.

        // Proceed to create the new permanent user
        if (tempUser.userType === 'labour') {
            const newLabour = new Labour({
                name: tempUser.name,
                email: tempUser.email,
                phone: tempUser.phone,
                address: tempUser.address,
                password: tempUser.password,
                labourType: tempUser.labourType,
                profilePic: tempUser.profilePic,
                cnicPic: tempUser.cnicPic,
                policeCertPic: tempUser.policeCertPic,
                approved: false // Set to false by default, awaiting admin
            });
            await newLabour.save();
            
            // Notify admin about the new labourer needing approval
            if (process.env.ADMIN_EMAIL) {
                await emailService.sendEmail({
                    to: process.env.ADMIN_EMAIL,
                    subject: 'New Labourer Registration Pending Approval',
                    html: `<p>A new labourer, <strong>${tempUser.name}</strong> (Email: ${tempUser.email}), has verified their email and is waiting for account approval.</p>`
                });
            } else {
                console.warn('WARNING: ADMIN_EMAIL not set in environment variables. Skipping admin notification email.');
            }

        } else { // It's a customer
            const newCustomer = new Customer({
                name: tempUser.name,
                email: tempUser.email,
                phone: tempUser.phone,
                password: tempUser.password
            });
            await newCustomer.save();
        }

        res.status(200).json({ message: 'Email verified successfully! You can now log in.' });

    } catch (error) {
        console.error("Email verification error:", error);
        res.status(500).json({ message: 'Server error during email verification.' });
    }
};


// ... (The rest of your controller file)

// In your login controller
exports.login = async (req, res) => {
    const { phone, password } = req.body;
    const labour = await Labour.findOne({ phone });
    if (!labour) return res.status(400).json({ error: 'Invalid credentials' });
    if (!labour.approved) return res.status(403).json({ error: 'Not approved by admin yet' });
    const isMatch = await bcrypt.compare(password, labour.password);
    if (!isMatch) return res.status(400).json({ error: 'Invalid credentials' });
    const token = jwt.sign({ id: labour._id }, process.env.JWT_SECRET); // Make sure this matches middleware
    res.json({ token });
};

// Add these two new functions to your labourController.js

// 1. FORGOT PASSWORD: Generates and emails a reset token
exports.forgotPassword = async (req, res) => {
    try {
        const { email } = req.body;

        const labour = await Labour.findOne({ email });

        if (!labour) {
            // Security best practice: Don't reveal if the user exists or not.
            return res.status(200).json({ message: 'If an account with that email exists, a password reset link has been sent.' });
        }

        // Generate a random, secure token
        const resetToken = crypto.randomBytes(32).toString('hex');

        // Set token and expiration date (e.g., 15 minutes) on the user document
        labour.resetPasswordToken = resetToken;
        labour.resetPasswordExpires = Date.now() + 15 * 60 * 1000; // 15 minutes

        await labour.save();

        // Create the reset URL for the frontend
        const resetUrl = `${process.env.FRONTEND_URL}/reset-password/${resetToken}`;

        // Send the email using your stylish template
        const emailInfo = {
            title: 'Password Reset Request',
            name: labour.name,
            message: `You requested a password reset for your laborPK account. Please click the button below to set a new password. This link is only valid for 15 minutes.`,
            buttonUrl: resetUrl,
            buttonText: 'Reset Your Password'
        };
        
        // Assuming you have the generateEmailHTML utility from our previous conversation
        const { generateEmailHTML } = require('../utils/emailTemplates');
        
        await emailService.sendEmail({
            to: labour.email,
            subject: 'Your Password Reset Link',
            html: generateEmailHTML(emailInfo)
        });

        res.status(200).json({ message: 'If an account with that email exists, a password reset link has been sent.' });

    } catch (error) {
        console.error('Forgot Password Error:', error);
        res.status(500).json({ message: 'An error occurred. Please try again later.' });
    }
};

// 2. RESET PASSWORD: Verifies token and updates the password
exports.resetPassword = async (req, res) => {
    try {
        const { token, password } = req.body;

        if (!token || !password) {
            return res.status(400).json({ message: 'Token and new password are required.' });
        }

        // Find the user by the token, ensuring it hasn't expired
        const labour = await Labour.findOne({
            resetPasswordToken: token,
            resetPasswordExpires: { $gt: Date.now() } // $gt means "greater than"
        });

        if (!labour) {
            return res.status(400).json({ message: 'Password reset token is invalid or has expired.' });
        }

        // Hash the new password
        const hashedPassword = await bcrypt.hash(password, 10);

        // Update the password and clear the reset token fields
        labour.password = hashedPassword;
        labour.resetPasswordToken = undefined;
        labour.resetPasswordExpires = undefined;

        await labour.save();
        
        res.status(200).json({ message: 'Your password has been successfully updated.' });

    } catch (error) {
        console.error('Reset Password Error:', error);
        res.status(500).json({ message: 'An error occurred. Please try again later.' });
    }
};

exports.getlabours = async (req, res) => {
    try {
        const { labourType } = req.query; // Get labourType from query parameters

        let query = {};

        // If labourType is provided in the query, add it to the filter
        if (labourType) {
            // Since labourType in your Mongoose model is an array of strings,
            // we use the $in operator to find documents where the labourType array
            // contains the requested single labourType.
            query.labourType = { $in: [labourType] };
        }

        // Fetch labours based on the constructed query
        const labours = await Labour.find(query);

        // --- REAL-TIME ADDITION: Emit initial labour list via Socket.IO ---
        const io = req.app.get('io'); // Get the Socket.IO instance
        // Assuming the client making this request is a customer,
        // you might want to emit to their specific room if req.user._id is available.
        // For simplicity, let's assume a general 'customer' room or broadcast if not specific.
        // If this endpoint is called by an authenticated customer, use req.user._id.
        if (req.user && req.user._id) {
            io.to(`customer_${req.user._id}`).emit('initialLabourList', {
                labours: labours,
                message: 'Your initial list of labourers has been loaded.'
            });
            console.log(`Emitted 'initialLabourList' to customer_${req.user._id}`);
        } else {
            // Fallback or handle cases where no specific customer ID is available
            // You might broadcast to a general 'public' room or log a warning.
            // For now, it will just send the HTTP response.
            console.warn("No customer ID found for emitting 'initialLabourList'.");
        }
        // --- END REAL-TIME ADDITION ---

        res.json(labours);
    } catch (err) {
        console.error("Error fetching labours:", err); // Log the full error
        res.status(500).json({ error: err.message });
    }
};
// FIX STARTS HERE
exports.updateLocation = async (req, res) => {
    // Destructure only lat and lng from req.body, as 'id' comes from the authenticated user
    const { lat, lng } = req.body;

    console.log(lat, lng)

    // Get the labourer's ID from the authenticated request object (set by protectLabour middleware)
    const labourerId = req.labour._id; // This is the CORRECT way to get the ID

    //console.log(`Attempting to update location for labourer ID: ${labourerId} to lat: ${lat}, lng: ${lng}`);

    try {
        const labour = await Labour.findByIdAndUpdate(
            labourerId, // Use the ID from the authenticated labourer
            { location: { lat, lng } },
            { new: true, runValidators: true } // { new: true } returns the updated document
        );

        if (!labour) {
            // This should ideally not happen if the token is valid and middleware works
            console.warn(`Labourer with ID ${labourerId} not found during location update, despite authentication.`);
            return res.status(404).json({ error: 'Authenticated labourer not found in database.' });
        }

        // --- REAL-TIME ADDITION: Notify customers about labourer's updated location ---
        const io = req.app.get('io'); // Get the Socket.IO instance
        // Emit to a general room that customers interested in nearby labourers might join
        // Or, if you have more sophisticated location-based rooms, emit to those.
        io.emit('labourerLocationUpdate', {
            labourerId: labour._id,
            location: labour.location,
            labourType: labour.labourType, // Include labour type for filtering on frontend
            message: `Labourer ${labour.name} updated their location.`
        });
        console.log(`Emitted 'labourerLocationUpdate' for labourer ${labour._id}`);
        // --- END REAL-TIME ADDITION ---

        //console.log('Location updated successfully for:', labourerId);
        res.json({ message: 'Location updated successfully!', labour });
    } catch (err) {
        console.error('Error updating labour location:', err); // Detailed logging of the actual error
        res.status(500).json({ error: 'Failed to update location. Please check server logs for details.' });
    }
};
// FIX ENDS HERE

// In labourController.js
exports.getProfile = async (req, res) => {
    try {
        // FIX: Changed from req.user._id to req.labour._id
        const labour = await Labour.findById(req.labour._id).select('-password');
        if (!labour) {
            return res.status(404).json({ message: 'Labour not found' });
        }
        res.json(labour);
    } catch (err) {
        console.error('Error in getProfile:', err);
        res.status(500).json({ message: 'Failed to fetch profile', error: err.message });
    }
};


exports.getNearbyLabours = async (req, res) => {
    try {
        const { latitude, longitude, labourType } = req.query;
        const MAPBOX_TOKEN = process.env.MAPBOX_TOKEN;

        if (!MAPBOX_TOKEN) {
            return res.status(500).json({ error: "Mapbox token is not configured." });
        }

        if (!latitude || !longitude) {
            return res.status(400).json({ error: "User location coordinates are required." });
        }

        // Build query for labours - only approved and with location
        const query = { 
            approved: true, 
            'location.lat': { $exists: true }, 
            'location.lng': { $exists: true } 
        };

        if (labourType) {
            query.labourType = { $in: [labourType] };
        }

        // First get all approved labours with locations
        const approvedLabours = await Labour.find(query);

        if (approvedLabours.length === 0) {
            return res.json([]);
        }

        // Get all work orders for these labours in a separate query
        const labourIds = approvedLabours.map(labour => labour._id);
        const completedWorkOrders = await WorkOrder.find({
            labourerId: { $in: labourIds },
            status: 'completed'
        });

        // Create a map of labourer ID to their ratings
        const labourRatingsMap = completedWorkOrders.reduce((map, order) => {
            if (!map[order.labourerId]) {
                map[order.labourerId] = { total: 0, count: 0 };
            }
            if (order.rating) {
                map[order.labourerId].total += order.rating;
                map[order.labourerId].count += 1;
            }
            return map;
        }, {});

        const userCoord = `${longitude},${latitude}`;
        const labourCoords = approvedLabours.map(labour => 
            `${labour.location.lng},${labour.location.lat}`
        );

        const allCoords = [userCoord, ...labourCoords].join(';');

        // Call Mapbox API
        const mapboxRes = await axios.get(
            `https://api.mapbox.com/directions-matrix/v1/mapbox/driving/${allCoords}`,
            {
                params: {
                    sources: 0,
                    annotations: 'distance,duration',
                    access_token: MAPBOX_TOKEN
                }
            }
        );

        const distances = mapboxRes.data.distances[0].slice(1);
        const durations = mapboxRes.data.durations[0].slice(1);

        // Process labours with ratings
        let nearbyLabours = approvedLabours
            .map((labour, index) => {
                // Get rating info from our map
                const ratingInfo = labourRatingsMap[labour._id] || { total: 0, count: 0 };
                const averageRating = ratingInfo.count > 0 ? (ratingInfo.total / ratingInfo.count) : 0;

                return {
                    ...labour._doc,
                    distanceInMeters: distances[index],
                    durationInSeconds: durations[index],
                    rating: averageRating,
                    reviewCount: ratingInfo.count,
                    // Add a random factor for better shuffling
                    randomFactor: Math.random()
                };
            })
            .filter(labour => labour.distanceInMeters <= 20000); // 20km in meters

        // Enhanced shuffling with weighted randomization
        nearbyLabours.sort((a, b) => {
            // Create a composite score that considers multiple factors
            const scoreA = calculateCompositeScore(a);
            const scoreB = calculateCompositeScore(b);
            
            // Sort by composite score (higher first)
            return scoreB - scoreA;
        });

        // Helper function to calculate composite score
        function calculateCompositeScore(labour) {
            // Normalize distance (closer is better)
            const distanceScore = 1 - (labour.distanceInMeters / 20000);
            
            // Rating score (0-5 scale)
            const ratingScore = labour.rating / 5;
            
            // Review count score (more reviews = more reliable)
            const reviewScore = Math.min(labour.reviewCount / 10, 1); // Cap at 10 reviews
            
            // Random factor to ensure different order each time
            const randomFactor = labour.randomFactor * 0.3; // 30% weight to randomness
            
            // Weighted composite score (adjust weights as needed)
            return (
                (distanceScore * 0.4) +      // 40% distance
                (ratingScore * 0.3) +        // 30% rating
                (reviewScore * 0.2) +        // 20% review count
                randomFactor                 // 10% randomness
            );
        }
        console.log(nearbyLabours)
        // Return multiple labourers in the new shuffled order
        res.json(nearbyLabours);

    } catch (err) {
        console.error('Error in getNearbyLabours:', err);
        res.status(500).json({ 
            error: err.response?.data?.message || 'Error calculating distances.' 
        });
    }
};
