const { hash, compare } = require("bcryptjs");
const { sign } = require("jsonwebtoken");
const crypto = require("crypto");
const {
  AssignReport,
  Users,
  Companies,
  UserProjects,
  Projects,
  Subscriptions,
} = require("../models");
const { Op } = require("sequelize");
const {
  sendRegistrationEmail,
  sendPasswordResetEmail,
  sendNewUserAddedEmail,
} = require("../utils/emailService");
const {
  logActivity,
  getIpAddress,
  getUserAgent,
} = require("../utils/activityLogger");
const { formatId } = require("../utils/idFormatter");

const getUsers = async (req, res) => {
  const { projectId, list } = req.query;

  try {
    if (projectId && list == undefined) {
      const users = await UserProjects.findAll({
        where: {
          ProjectId: projectId,
        },
        include: [
          {
            model: Users,
            attributes: {
              exclude: ["password"],
            },
          },
        ],
      });
      res.json(users.map(({ User, ...rest }) => ({ ...User, ...rest })));
      return;
    }
    const users = await Users.findAll({
      where: {
        CompanyId: req.user.companyId,
      },
      attributes: {
        exclude: ["password"],
      },
    });
    res.json(users);
  } catch (err) {
    res.status(500).json({ message: "Failed to fetch" });
  }
};

const isEmail = (email) => {
  const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return re.test(email);
};

const createUser = async (req, res) => {
  const permissions = ["super-admin", "admin"];
  if (!permissions.includes(req.user?.role))
    return res.status(403).json({ message: "Forbidden" });

  const { fullname, password, role, email, jobDesc, department, project } =
    req.body;
  if (!fullname)
    return res.status(400).json({ message: "Full name is required" });
  if (!email) return res.status(400).json({ message: "Email is required" });
  if (!isEmail(email))
    return res.status(400).json({ message: "Email is not valid" });
  if (!password)
    return res.status(400).json({ message: "Password is required" });
  if (!jobDesc)
    return res.status(400).json({ message: "Job description is required" });
  if (!department)
    return res.status(400).json({ message: "Department is required" });
  if (!role) return res.status(400).json({ message: "Role is required" });
  if (!project) return res.status(400).json({ message: "Project is required" });

  // Only super-admin can create other super-admins
  if (role === "super-admin" && req.user.role !== "super-admin") {
    return res.status(403).json({
      message: "Only super admin can create other super admins",
    });
  }

  try {
    const emailExists = await Users.findOne({ where: { email } });
    if (emailExists)
      return res.status(404).json({ message: "Email must be unique" });

    const company = await Companies.findByPk(req.user.companyId);
    if (!company)
      return res.status(404).json({ message: "Something went wrong" });

    const subscription = await Subscriptions.findOne({
      where: {
        companyId: req.user.companyId,
      },
    });
    if (!subscription)
      return res.status(404).json({ message: "Something went wrong" });

    if (role === "hr") {
      const existingHr = await Users.findOne({
        where: { CompanyId: company.id, role: "hr" },
      });
      if (existingHr) {
        return res.status(400).json({
          message: "Only one HR user is allowed per company",
        });
      }
    }

    const companyUsersCount = await Users.count({
      where: {
        CompanyId: company.id,
        role: {
          [Op.or]: ["user", "admin", "super-admin", "hr"],
        },
      },
    });
    const subUsersCount = subscription.usersCount;
    if (companyUsersCount >= subUsersCount)
      return res.status(400).json({
        message: `You can only have ${subUsersCount} users`,
      });

    const hashed = await hash(password, 10);

    const createdUser = await Users.create({
      fullname,
      password: hashed,
      role,
      email,
      CompanyId: req.user.companyId,
      jobDesc,
      department,
    });

    await UserProjects.create({
      UserId: createdUser.id,
      ProjectId: project,
    });

    // Send new user added email
    try {
      const projectDetails = await Projects.findByPk(project);
      const company = await Companies.findByPk(req.user.companyId);
      const addedByUser = await Users.findByPk(req.user.id);

      if (projectDetails && company) {
        await sendNewUserAddedEmail({
          to: email,
          userName: fullname,
          projectName: projectDetails.name,
          companyName: company.name,
          userRole: role,
          addedBy: addedByUser ? addedByUser.fullname : "System Administrator",
          addedDate: new Date(),
        });
      }
    } catch (emailError) {
      console.error("Failed to send new user email:", emailError);
      // Don't fail the user creation if email fails
    }

    res.status(201).json({ message: "User created successfully" });
  } catch (err) {
    console.log(err);

    res.status(500).json({ message: "Failed to create" });
  }
};

const deleteUser = async (req, res) => {
  const permissions = ["super-admin", "admin"];
  if (!permissions.includes(req.user?.role))
    return res.status(403).json({ message: "Forbidden" });

  const { id } = req.query;
  if (!id) return res.status(400).json({ message: "ID is required" });

  if (id == req.user.id)
    return res.status(400).json({ message: "You cannot delete yourself" });

  try {
    const user = await Users.findByPk(id);
    if (!user) return res.status(404).json({ message: "User not found" });

    // Only super-admin can delete super-admins
    if (user.role === "super-admin" && req.user.role !== "super-admin") {
      return res.status(403).json({
        message: "Only super admin can delete other super admins",
      });
    }

    // Prevent deleting super-admin if they're the only one
    if (user.role === "super-admin") {
      const superAdminCount = await Users.count({
        where: { role: "super-admin" },
      });
      if (superAdminCount <= 1) {
        return res.status(400).json({
          message: "Cannot delete the last super admin",
        });
      }
    }

    const isUserInReport = await AssignReport.findAll({
      where: { UserId: id },
    });
    if (isUserInReport.length > 0)
      return res.status(400).json({
        message: "You can't delete a user assigned to a report",
      });

    await Users.destroy({ where: { id } });

    res.status(200).json({ message: "User deleted successfully" });
  } catch (err) {
    res.status(500).json({ message: "Failed to delete" });
  }
};

const login = async (req, res) => {
  const { email, password } = req.body;
  if (!email) return res.status(400).json({ message: "Email is required" });
  if (!password)
    return res.status(400).json({ message: "Password is required" });

  try {
    const user = await Users.findOne({
      where: { email },
      include: [Companies],
    });
    if (!user) return res.status(404).json({ message: "User not found" });

    const isMatch = await compare(password, user.password);
    if (!isMatch) return res.status(401).json({ message: "Invalid password" });

    const projects = await UserProjects.findAll({
      where: { UserId: user.id },
      include: Projects,
    });

    const token = sign(
      {
        companyName: user.Company.name,
        role: user.role,
        id: user.id,
        email: user.email,
        fullname: user.fullname,
        companyId: user.CompanyId,
        subscriptionEndsAt: user.Company.subscriptionEndsAt,
        subscriptionType: user.Company.subscriptionType,
      },
      process.env.JWT_SECRET,
    );

    // Log login activity
    logActivity({
      userId: user.id,
      companyId: user.CompanyId,
      action: "login",
      entityType: "Authentication",
      description: `${user.fullname} logged in`,
      ipAddress: getIpAddress(req),
      userAgent: getUserAgent(req),
    }).catch((err) => console.error("Failed to log login activity:", err));

    res.json({
      message: "Logged in successfully, Redirecting...",
      token,
      activeProject: projects.length
        ? {
            name: projects[0].Project.name,
            id: projects[0].Project.id,
          }
        : null,
    });
  } catch (err) {
    console.log(err);

    res.status(500).json({ message: "Failed to login" });
  }
};

const register = async (req, res) => {
  const {
    name,
    password,
    country,
    fullname,
    orgNumber,
    phoneNumber,
    email,
    address1,
    address2,
    city,
    zipCode,
    adminEmail,
    confirmPassword,
    jobDesc,
    department,
  } = req.body;
  if (!name)
    return res.status(400).json({ message: "Company name is required" });
  if (!email)
    return res.status(400).json({ message: "Company email is required" });
  if (!isEmail(email))
    return res.status(400).json({ message: "Company email is not valid" });
  if (!country)
    return res.status(400).json({ message: "Company country is required" });
  if (!orgNumber)
    return res.status(400).json({
      message: "Company organization number is required",
    });
  if (!phoneNumber)
    return res.status(400).json({
      message: "Company phone number is required",
    });
  if (!address1)
    return res.status(400).json({ message: "Address 1 is required" });
  if (!city) return res.status(400).json({ message: "City is required" });
  if (!zipCode)
    return res.status(400).json({ message: "Zip code is required" });
  if (!fullname)
    return res.status(400).json({ message: "Admin full name is required" });
  if (!adminEmail)
    return res.status(400).json({ message: "Admin email is required" });
  if (!isEmail(adminEmail))
    return res.status(400).json({ message: "Admin email is not valid" });
  if (!password)
    return res.status(400).json({ message: "Password is required" });
  if (!confirmPassword)
    return res.status(400).json({ message: "Confirm password is required" });
  if (password !== confirmPassword)
    return res.status(400).json({ message: "Passwords do not match" });
  if (!jobDesc)
    return res.status(400).json({ message: "Job description is required" });
  if (!department)
    return res.status(400).json({ message: "Department is required" });

  try {
    const adminEmailExists = await Users.findOne({
      where: { email: adminEmail },
    });
    if (adminEmailExists)
      return res.status(404).json({ message: "Admin email must be unique" });
    const companyNameExists = await Companies.findOne({ where: { name } });
    if (companyNameExists)
      return res.status(404).json({ message: "Company name must be unique" });
    const companyEmailExists = await Companies.findOne({ where: { email } });
    if (companyEmailExists)
      return res.status(404).json({ message: "Company email must be unique" });

    const createdCompany = await Companies.create({
      name,
      country,
      orgNumber,
      phoneNumber,
      email,
      address1,
      address2,
      city,
      zipCode,
    });
    await Subscriptions.create({
      CompanyId: createdCompany.id,
    });

    const hashed = await hash(password, 10);

    await Users.create({
      fullname,
      password: hashed,
      role: "super-admin",
      CompanyId: createdCompany.id,
      email: adminEmail,
      jobDesc,
      department,
    });

    // Send registration confirmation email
    try {
      const companyAddress = `${address1}${address2 ? ", " + address2 : ""}, ${city}, ${zipCode}, ${country}`;
      const formattedCompanyId = formatId("companies", createdCompany.id);

      await sendRegistrationEmail({
        to: adminEmail,
        name: fullname,
        companyName: name,
        companyEmail: email,
        companyAddress: companyAddress,
        companyId: formattedCompanyId,
        expiryDate: createdCompany.subscriptionEndsAt,
      });
    } catch (emailError) {
      console.error("Failed to send registration email:", emailError);
      // Don't fail the registration if email fails
    }

    res.json({
      message: "Registered successfully, Redirecting...",
    });
  } catch (err) {
    console.log(err);

    res.status(500).json({ message: "Failed to login" });
  }
};

const requestPasswordReset = async (req, res) => {
  const { email } = req.body;

  if (!email) return res.status(400).json({ message: "Email is required" });
  if (!isEmail(email))
    return res.status(400).json({ message: "Email is not valid" });

  try {
    const user = await Users.findOne({ where: { email } });

    if (!user) {
      // Don't reveal if user exists or not for security
      return res.json({
        message:
          "If an account with that email exists, a password reset link has been sent.",
      });
    }

    // Generate reset token
    const resetToken = crypto.randomBytes(32).toString("hex");
    const resetTokenHash = crypto
      .createHash("sha256")
      .update(resetToken)
      .digest("hex");

    // Set token expiry (1 hour from now)
    const resetExpires = new Date();
    resetExpires.setHours(resetExpires.getHours() + 1);

    // Save token to database
    await Users.update(
      {
        resetPasswordToken: resetTokenHash,
        resetPasswordExpires: resetExpires,
      },
      {
        where: { id: user.id },
      },
    );

    // Create reset link
    const resetLink = `${process.env.FRONTEND_URL || "http://localhost:5174/"}auth/reset-password/${resetToken}`;

    // Send email
    try {
      await sendPasswordResetEmail({
        to: user.email,
        customerName: user.fullname,
        resetLink: resetLink,
      });
    } catch (emailError) {
      console.error("Failed to send password reset email:", emailError);
      return res.status(500).json({
        message: "Failed to send password reset email. Please try again later.",
      });
    }

    res.json({
      message:
        "If an account with that email exists, a password reset link has been sent",
    });
  } catch (err) {
    console.log(err);
    res.status(500).json({ message: "Failed to process request" });
  }
};

const resetPassword = async (req, res) => {
  const { token } = req.params;
  const { password, confirmPassword } = req.body;

  if (!token)
    return res.status(400).json({ message: "Reset token is required" });
  if (!password)
    return res.status(400).json({ message: "Password is required" });
  if (!confirmPassword)
    return res.status(400).json({ message: "Confirm password is required" });
  if (password !== confirmPassword)
    return res.status(400).json({ message: "Passwords do not match" });
  if (password.length < 6)
    return res.status(400).json({
      message: "Password must be at least 6 characters",
    });

  try {
    // Hash the token to compare with database
    const resetTokenHash = crypto
      .createHash("sha256")
      .update(token)
      .digest("hex");

    // Find user with valid token
    const user = await Users.findOne({
      where: {
        resetPasswordToken: resetTokenHash,
        resetPasswordExpires: {
          [Op.gt]: new Date(), // Token not expired
        },
      },
    });

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

    // Hash new password
    const hashed = await hash(password, 10);

    // Update password and clear reset token
    await Users.update(
      {
        password: hashed,
        resetPasswordToken: null,
        resetPasswordExpires: null,
      },
      {
        where: { id: user.id },
      },
    );

    res.json({
      message:
        "Password has been reset successfully. You can now log in with your new password.",
    });
  } catch (err) {
    console.log(err);
    res.status(500).json({ message: "Failed to reset password" });
  }
};

/**
 * Google OAuth Login/Register
 * POST /api/users/google-auth
 */
const googleAuth = async (req, res) => {
  const { credential } = req.body;

  if (!credential) {
    return res.status(400).json({ message: "Google credential is required" });
  }

  try {
    const { OAuth2Client } = require("google-auth-library");
    const client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID);

    // Verify the Google token
    const ticket = await client.verifyIdToken({
      idToken: credential,
      audience: process.env.GOOGLE_CLIENT_ID,
    });

    const payload = ticket.getPayload();
    const { email, name, picture, sub: googleId } = payload;

    if (!email) {
      return res.status(400).json({ message: "Email not provided by Google" });
    }

    // Check if user exists
    let user = await Users.findOne({
      where: { email },
      include: [Companies],
    });

    // If user doesn't exist, create a new one
    if (!user) {
      // For new Google users, we need to assign them to a company
      // You can modify this logic based on your requirements
      
      // Option 1: Create a default company for the user
      const company = await Companies.create({
        name: `${name}'s Company`,
        subscriptionType: "trial",
        subscriptionEndsAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days trial
      });

      // Create the user
      user = await Users.create({
        email,
        fullname: name,
        password: crypto.randomBytes(32).toString("hex"), // Random password since they use Google
        role: "admin", // First user is admin of their company
        CompanyId: company.id,
        googleId,
        profilePicture: picture,
      });

      // Reload with company
      user = await Users.findByPk(user.id, {
        include: [Companies],
      });

      // Log registration activity
      logActivity({
        userId: user.id,
        companyId: user.CompanyId,
        action: "register",
        entityType: "Authentication",
        description: `${user.fullname} registered via Google`,
        ipAddress: getIpAddress(req),
        userAgent: getUserAgent(req),
      }).catch((err) => console.error("Failed to log registration activity:", err));
    } else {
      // Update Google ID and picture if not set
      if (!user.googleId) {
        await Users.update(
          { googleId, profilePicture: picture },
          { where: { id: user.id } }
        );
      }

      // Log login activity
      logActivity({
        userId: user.id,
        companyId: user.CompanyId,
        action: "login",
        entityType: "Authentication",
        description: `${user.fullname} logged in via Google`,
        ipAddress: getIpAddress(req),
        userAgent: getUserAgent(req),
      }).catch((err) => console.error("Failed to log login activity:", err));
    }

    // Get user's projects
    const projects = await UserProjects.findAll({
      where: { UserId: user.id },
      include: Projects,
    });

    // Generate JWT token
    const token = sign(
      {
        companyName: user.Company.name,
        role: user.role,
        id: user.id,
        email: user.email,
        fullname: user.fullname,
        companyId: user.CompanyId,
        subscriptionEndsAt: user.Company.subscriptionEndsAt,
        subscriptionType: user.Company.subscriptionType,
      },
      process.env.JWT_SECRET
    );

    res.json({
      message: "Authenticated successfully with Google",
      token,
      activeProject: projects.length
        ? {
            name: projects[0].Project.name,
            id: projects[0].Project.id,
          }
        : null,
    });
  } catch (err) {
    console.error("Google auth error:", err);
    res.status(500).json({ 
      message: "Failed to authenticate with Google",
      error: err.message 
    });
  }
};

/**
 * Microsoft OAuth Login/Register
 * POST /api/users/microsoft-auth
 */
const microsoftAuth = async (req, res) => {
  const { accessToken, account } = req.body;

  if (!accessToken || !account) {
    return res.status(400).json({ message: "Access token and account info are required" });
  }

  try {
    const { username: email, name } = account;
    const microsoftId = account.homeAccountId || account.localAccountId;

    if (!email) {
      return res.status(400).json({ message: "Email not provided by Microsoft" });
    }

    // Check if user exists
    let user = await Users.findOne({
      where: { email },
      include: [Companies],
    });

    // If user doesn't exist, create a new one
    if (!user) {
      // Create a default company for the user
      const company = await Companies.create({
        name: `${name}'s Company`,
        subscriptionType: "trial",
        subscriptionEndsAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days trial
      });

      // Create the user
      user = await Users.create({
        email,
        fullname: name,
        password: crypto.randomBytes(32).toString("hex"), // Random password since they use Microsoft
        role: "admin", // First user is admin of their company
        CompanyId: company.id,
        microsoftId,
      });

      // Reload with company
      user = await Users.findByPk(user.id, {
        include: [Companies],
      });

      // Log registration activity
      logActivity({
        userId: user.id,
        companyId: user.CompanyId,
        action: "register",
        entityType: "Authentication",
        description: `${user.fullname} registered via Microsoft`,
        ipAddress: getIpAddress(req),
        userAgent: getUserAgent(req),
      }).catch((err) => console.error("Failed to log registration activity:", err));
    } else {
      // Update Microsoft ID if not set
      if (!user.microsoftId) {
        await Users.update(
          { microsoftId },
          { where: { id: user.id } }
        );
      }

      // Log login activity
      logActivity({
        userId: user.id,
        companyId: user.CompanyId,
        action: "login",
        entityType: "Authentication",
        description: `${user.fullname} logged in via Microsoft`,
        ipAddress: getIpAddress(req),
        userAgent: getUserAgent(req),
      }).catch((err) => console.error("Failed to log login activity:", err));
    }

    // Get user's projects
    const projects = await UserProjects.findAll({
      where: { UserId: user.id },
      include: Projects,
    });

    // Generate JWT token
    const token = sign(
      {
        companyName: user.Company.name,
        role: user.role,
        id: user.id,
        email: user.email,
        fullname: user.fullname,
        companyId: user.CompanyId,
        subscriptionEndsAt: user.Company.subscriptionEndsAt,
        subscriptionType: user.Company.subscriptionType,
      },
      process.env.JWT_SECRET
    );

    res.json({
      message: "Authenticated successfully with Microsoft",
      token,
      activeProject: projects.length
        ? {
            name: projects[0].Project.name,
            id: projects[0].Project.id,
          }
        : null,
    });
  } catch (err) {
    console.error("Microsoft auth error:", err);
    res.status(500).json({ 
      message: "Failed to authenticate with Microsoft",
      error: err.message 
    });
  }
};

const patchProfile = async (req, res) => {
  try {
    const { fullname, department, jobDesc, phone } = req.body;
    const payload = {};
    if (fullname != null) payload.fullname = String(fullname).trim();
    if (department !== undefined) payload.department = department;
    if (jobDesc !== undefined) payload.jobDesc = jobDesc;
    if (phone !== undefined) payload.phone = phone || null;
    if (!Object.keys(payload).length)
      return res.status(400).json({ message: "No fields to update" });

    await Users.update(payload, { where: { id: req.user.id } });
    const u = await Users.findByPk(req.user.id, {
      attributes: { exclude: ["password"] },
    });
    res.json(u);
  } catch (e) {
    res.status(500).json({ message: "Failed to update profile" });
  }
};

const assertSingleHr = async (companyId, targetUserId) => {
  const existing = await Users.findOne({
    where: { CompanyId: companyId, role: "hr" },
  });
  if (existing && Number(existing.id) !== Number(targetUserId)) {
    return "Only one HR user is allowed per company";
  }
  return null;
};

const getHrStatus = async (req, res) => {
  try {
    const hr = await Users.findOne({
      where: { CompanyId: req.user.companyId, role: "hr" },
      attributes: ["id", "fullname", "email"],
    });
    res.json({ hasHr: !!hr, hrUser: hr });
  } catch (e) {
    res.status(500).json({ message: "Failed" });
  }
};

const updateUserById = async (req, res) => {
  const { id } = req.params;
  const { fullname, department, jobDesc, role, phone, email } = req.body;
  try {
    const target = await Users.findByPk(id);
    if (!target || target.CompanyId !== req.user.companyId) {
      return res.status(404).json({ message: "User not found" });
    }

    const isAdmin = ["admin", "super-admin"].includes(req.user.role);
    const isHr = req.user.role === "hr";
    if (!isAdmin && !isHr) return res.status(403).json({ message: "Forbidden" });

    if (isHr && ["super-admin", "admin"].includes(target.role)) {
      return res.status(403).json({ message: "HR cannot modify administrator accounts" });
    }

    if (email !== undefined && !isAdmin) {
      return res.status(403).json({ message: "Only administrators can change email" });
    }

    const payload = {};
    if (fullname != null) payload.fullname = String(fullname).trim();
    if (department !== undefined) payload.department = department;
    if (jobDesc !== undefined) payload.jobDesc = jobDesc;
    if (phone !== undefined) payload.phone = phone || null;
    if (email !== undefined && isAdmin) payload.email = email;

    if (role !== undefined) {
      if (role === "super-admin" && req.user.role !== "super-admin") {
        return res.status(403).json({
          message: "Only super admin can assign super-admin role",
        });
      }
      if (role === "hr") {
        const err = await assertSingleHr(target.CompanyId, target.id);
        if (err) return res.status(400).json({ message: err });
      }
      payload.role = role;
    }

    if (!Object.keys(payload).length)
      return res.status(400).json({ message: "No fields to update" });

    await Users.update(payload, { where: { id } });
    const u = await Users.findByPk(id, { attributes: { exclude: ["password"] } });
    res.json(u);
  } catch (e) {
    console.error(e);
    res.status(500).json({ message: "Failed to update user" });
  }
};

module.exports = {
  getUsers,
  createUser,
  deleteUser,
  login,
  register,
  googleAuth,
  microsoftAuth,
  requestPasswordReset,
  resetPassword,
  patchProfile,
  updateUserById,
  getHrStatus,
};
