import express from "express";
import bcrypt from "bcryptjs";
import crypto from "crypto";

import { validate } from "../../shared/validate.js";
import { httpError } from "../../shared/errors.js";
import { env } from "../../shared/env.js";
import { User } from "../users/user.model.js";
import { signToken, requireAuth } from "../../middleware/auth.js";
import {
  loginSchema,
  registerSchema,
  forgotPasswordSchema,
  resetPasswordSchema,
  seedAdminSchema,
  changePasswordSchema,
} from "./auth.schemas.js";
import { Channel } from "../chat/chat.model.js";
import { sendMail } from "../../shared/mailer.js";

export const authRouter = express.Router();

function sha256Hex(input) {
  return crypto.createHash("sha256").update(String(input)).digest("hex");
}

function buildResetEmail({ token }) {
  return {
    subject: "Password reset code",
    text:
      "You requested a password reset.\n\n" +
      `Your reset code is: ${token}\n\n` +
      "This code expires in 30 minutes. If you did not request this, you can ignore this email.",
  };
}

function setAuthCookie(res, token) {
  // Note: for local/dev on IP addresses, `secure` must be false (http).
  // For production behind https, set NODE_ENV=production to enable `secure`.
  const isProd = env.NODE_ENV === "production";
  res.cookie("token", token, {
    httpOnly: true,
    // Cookies won't reliably work cross-site on http (devices using IPs).
    // Prefer bearer tokens for mobile; cookies are mainly for same-site web.
    sameSite: isProd ? "none" : "lax",
    secure: isProd,
    path: "/",
    maxAge: 7 * 24 * 60 * 60 * 1000,
  });
}

authRouter.post("/logout", (req, res) => {
  res.clearCookie("token", {
    path: "/",
    sameSite: env.NODE_ENV === "production" ? "none" : "lax",
    secure: env.NODE_ENV === "production",
  });
  res.json({ ok: true });
});

authRouter.post("/login", validate(loginSchema), async (req, res, next) => {
  try {
    const { email, id, password, role } = req.validated.body;

    const defaultAdminEnabled =
      env.NODE_ENV !== "production" || env.ENABLE_DEFAULT_ADMIN;

    const isDefaultAdminAttempt =
      defaultAdminEnabled &&
      id &&
      id.toString().trim() === env.DEFAULT_ADMIN_ID &&
      password === env.DEFAULT_ADMIN_PASSWORD;

    if (isDefaultAdminAttempt) {
      const existing = await User.findOne({
        email: env.DEFAULT_ADMIN_EMAIL.toLowerCase().trim(),
      });

      if (existing) {
        if (existing.status !== "active") throw httpError(403, "User disabled");

        const token = signToken(existing._id.toString());
        setAuthCookie(res, token);
        return res.json({
          token,
          user: {
            id: existing._id,
            _id: existing._id,
            fullName: existing.fullName,
            email: existing.email,
            role: existing.role,
            status: existing.status,
            phone: existing.phone,
            category: existing.category,
            avatarUrl: existing.avatarUrl,
            createdAt: existing.createdAt,
            updatedAt: existing.updatedAt,
          },
        });
      }

      const userCount = await User.countDocuments();
      if (userCount > 0) {
        throw httpError(409, "Default admin not available");
      }

      const passwordHash = await bcrypt.hash(env.DEFAULT_ADMIN_PASSWORD, 12);
      const admin = await User.create({
        fullName: env.DEFAULT_ADMIN_FULL_NAME,
        email: env.DEFAULT_ADMIN_EMAIL.toLowerCase().trim(),
        passwordHash,
        role: "admin",
        status: "active",
      });

      await Channel.create({
        name: "general",
        isPrivate: false,
        members: [],
        createdBy: admin._id,
      });

      const token = signToken(admin._id.toString());
      setAuthCookie(res, token);
      return res.json({
        token,
        user: {
          id: admin._id,
          _id: admin._id,
          fullName: admin.fullName,
          email: admin.email,
          role: admin.role,
          status: admin.status,
          phone: admin.phone,
          category: admin.category,
          avatarUrl: admin.avatarUrl,
          createdAt: admin.createdAt,
          updatedAt: admin.updatedAt,
        },
      });
    }

    if (!email) throw httpError(400, "Email is required for this login");

    const user = await User.findOne({
      email: email.toLowerCase().trim(),
    });
    if (!user) throw httpError(401, "Invalid email or password");
    if (user.status === "pending") {
      throw httpError(403, "Account pending admin approval");
    }
    if (user.status !== "active") throw httpError(403, "User disabled");

    if (role && user.role !== role) {
      throw httpError(401, "Invalid account type for this login");
    }

    const ok = await bcrypt.compare(password, user.passwordHash);
    if (!ok) throw httpError(401, "Invalid email or password");

    const token = signToken(user._id.toString());

    setAuthCookie(res, token);

    res.json({
      token,
      user: {
        id: user._id,
        _id: user._id,
        fullName: user.fullName,
        email: user.email,
        role: user.role,
        status: user.status,
        phone: user.phone,
        category: user.category,
        avatarUrl: user.avatarUrl,
        createdAt: user.createdAt,
        updatedAt: user.updatedAt,
      },
    });
  } catch (err) {
    next(err);
  }
});

authRouter.post(
  "/register",
  validate(registerSchema),
  async (req, res, next) => {
    try {
      const { fullName, email, password, role, phone, category } =
        req.validated.body;
      const normalizedEmail = email.toLowerCase().trim();

      const exists = await User.findOne({ email: normalizedEmail });
      if (exists) throw httpError(409, "Email already exists");

      const passwordHash = await bcrypt.hash(password, 12);
      const user = await User.create({
        fullName: fullName.trim(),
        email: normalizedEmail,
        phone: phone.trim(),
        passwordHash,
        role: role ?? "employee",
        category: category ?? "general",
        status: "pending",
      });

      // Do NOT log in immediately: requires admin approval.
      res.status(201).json({
        message:
          "Account created. Please wait for admin approval before signing in.",
        user: {
          id: user._id,
          _id: user._id,
          fullName: user.fullName,
          email: user.email,
          phone: user.phone,
          role: user.role,
          category: user.category,
          status: user.status,
          avatarUrl: user.avatarUrl,
          createdAt: user.createdAt,
          updatedAt: user.updatedAt,
        },
      });
    } catch (err) {
      next(err);
    }
  }
);

// One-time bootstrap: allowed ONLY when no users exist yet.
authRouter.post(
  "/seed-admin",
  validate(seedAdminSchema),
  async (req, res, next) => {
    try {
      const userCount = await User.countDocuments();
      if (userCount > 0) throw httpError(409, "Admin already seeded");

      const { fullName, email, password } = req.validated.body;
      const passwordHash = await bcrypt.hash(password, 12);

      const admin = await User.create({
        fullName,
        email: email.toLowerCase().trim(),
        passwordHash,
        role: "admin",
        status: "active",
      });

      await Channel.create({
        name: "general",
        isPrivate: false,
        members: [],
        createdBy: admin._id,
      });

      const token = signToken(admin._id.toString());
      setAuthCookie(res, token);

      res.status(201).json({
        token,
        user: {
          id: admin._id,
          _id: admin._id,
          fullName: admin.fullName,
          email: admin.email,
          role: admin.role,
          status: admin.status,
          phone: admin.phone,
          category: admin.category,
          avatarUrl: admin.avatarUrl,
          createdAt: admin.createdAt,
          updatedAt: admin.updatedAt,
        },
      });
    } catch (err) {
      next(err);
    }
  }
);

authRouter.post(
  "/forgot-password",
  validate(forgotPasswordSchema),
  async (req, res, next) => {
    try {
      const { email } = req.validated.body;
      const normalizedEmail = email.toLowerCase().trim();

      const user = await User.findOne({ email: normalizedEmail });

      // Always return success to avoid account enumeration.
      const okResponse = {
        ok: true,
        message:
          "If an account exists for that email, a password reset code has been sent.",
      };

      if (!user) return res.json(okResponse);

      const token = String(crypto.randomInt(0, 1000000)).padStart(6, "0");
      const tokenHash = sha256Hex(token);
      const expiresAt = new Date(Date.now() + 30 * 60 * 1000);

      await User.updateOne(
        { _id: user._id },
        {
          $set: {
            passwordResetTokenHash: tokenHash,
            passwordResetTokenExpiresAt: expiresAt,
          },
        }
      );

      const mail = buildResetEmail({ token });
      try {
        await sendMail({
          to: normalizedEmail,
          subject: mail.subject,
          text: mail.text,
        });
      } catch (mailErr) {
        if (env.NODE_ENV !== "production" && env.RESET_ECHO_CODE) {
          return res.json({
            ...okResponse,
            debugCode: token,
            debugMailError: true,
          });
        }
        throw mailErr;
      }

      if (env.NODE_ENV !== "production" && env.RESET_ECHO_CODE) {
        return res.json({ ...okResponse, debugCode: token });
      }

      return res.json(okResponse);
    } catch (err) {
      next(err);
    }
  }
);

authRouter.post(
  "/reset-password",
  validate(resetPasswordSchema),
  async (req, res, next) => {
    try {
      const { email, token, newPassword } = req.validated.body;
      const normalizedEmail = email.toLowerCase().trim();

      const tokenDigits = String(token || "").replace(/\D+/g, "");
      const tokenNormalized = tokenDigits.slice(0, 6).padStart(6, "0");
      if (!tokenNormalized || tokenNormalized.length !== 6) {
        throw httpError(400, "Invalid reset token");
      }

      const user = await User.findOne({ email: normalizedEmail });
      if (!user) throw httpError(400, "Invalid reset token");

      const now = new Date();
      if (!user.passwordResetTokenHash || !user.passwordResetTokenExpiresAt) {
        throw httpError(400, "Invalid reset token");
      }
      if (user.passwordResetTokenExpiresAt <= now) {
        throw httpError(400, "Reset token expired");
      }

      const tokenHash = sha256Hex(tokenNormalized);
      if (tokenHash !== user.passwordResetTokenHash) {
        throw httpError(400, "Invalid reset token");
      }

      const passwordHash = await bcrypt.hash(newPassword, 12);
      user.passwordHash = passwordHash;
      user.passwordResetTokenHash = undefined;
      user.passwordResetTokenExpiresAt = undefined;
      await user.save();

      res.json({ ok: true, message: "Password reset successfully" });
    } catch (err) {
      next(err);
    }
  }
);

authRouter.post(
  "/change-password",
  requireAuth,
  validate(changePasswordSchema),
  async (req, res, next) => {
    try {
      const { currentPassword, newPassword } = req.validated.body;
      const userId = req.user._id;

      const user = await User.findById(userId);
      if (!user) throw httpError(404, "User not found");

      const isPasswordValid = await bcrypt.compare(
        currentPassword,
        user.passwordHash
      );
      if (!isPasswordValid) {
        throw httpError(401, "Current password is incorrect");
      }

      const passwordHash = await bcrypt.hash(newPassword, 12);
      user.passwordHash = passwordHash;
      await user.save();

      res.json({ ok: true, message: "Password changed successfully" });
    } catch (err) {
      next(err);
    }
  }
);
