import bcrypt from "bcryptjs";
import { Role } from "@prisma/client";

import prisma from "../../prismaClient";
import { signJwt } from "../../lib/jwt";
import {
  createPasswordResetToken,
  consumePasswordResetToken,
} from "../../lib/passwordReset";
import { sendResetPasswordEmail } from "../../lib/email";
import { upsertLoginActivity } from "../../lib/loginActivity";

export class AuthService {
  async registerDisabled() {
    const error: any = new Error("Registration is disabled");
    error.code = 403;
    error.messageExtended =
      "Only managers can invite new admins. Please use your invitation link to set your password.";
    throw error;
  }

  async login(email?: string, password?: string) {
    if (!email || !password) {
      if (email) {
        const found = await prisma.user.findUnique({ where: { email } });
        if (found && found.role !== Role.USER) {
          await upsertLoginActivity(
            found.id,
            "Login failed",
            "FAILURE",
            "MISSING_CREDENTIALS"
          );
        }
      }
      const error: any = new Error("Missing email or password");
      error.code = 400;
      throw error;
    }

    const user = await prisma.user.findUnique({ where: { email } });

    if (!user || user.role === Role.USER) {
      if (user && user.role === Role.USER) {
        await upsertLoginActivity(
          user.id,
          "Login failed",
          "FAILURE",
          "NOT_ALLOWED"
        );
      }
      const error: any = new Error("Invalid credentials");
      error.code = 401;
      throw error;
    }

    if (!user.isActivated) {
      await upsertLoginActivity(
        user.id,
        "Login failed",
        "FAILURE",
        "NOT_ACTIVATED"
      );
      const error: any = new Error(
        "Account is not activated. Please use your invitation link to set a password."
      );
      error.code = 403;
      throw error;
    }

    if (user.isLocked) {
      await upsertLoginActivity(
        user.id,
        "Login failed",
        "FAILURE",
        "ACCOUNT_LOCKED"
      );
      const error: any = new Error(
        "Account is locked due to suspicious activity. Please contact an administrator."
      );
      error.code = 403;
      throw error;
    }

    const ok = await bcrypt.compare(password, user.password);
    if (!ok) {
      await upsertLoginActivity(
        user.id,
        "Login failed",
        "FAILURE",
        "WRONG_PASSWORD"
      );
      const error: any = new Error("Invalid credentials");
      error.code = 401;
      throw error;
    }

    const token = signJwt({
      userId: user.id,
      email: user.email,
      role: user.role,
    });

    const now = new Date();
    try {
      await prisma.user.update({
        where: { id: user.id },
        data: { lastLoginAt: now },
      });
    } catch (_e) {}
    await upsertLoginActivity(user.id, "Login success", "SUCCESS", null);

    const updated = await prisma.user.findUnique({ where: { id: user.id } });
    return { token, user: updated! };
  }

  async getCurrentUser(userId: string) {
    const user = await prisma.user.findUnique({ where: { id: userId } });
    if (!user) {
      const error: any = new Error("User not found");
      error.code = 404;
      throw error;
    }
    return user;
  }

  async forgotPassword(email: string) {
    const user = await prisma.user.findUnique({ where: { email } });
    if (user && user.role !== Role.USER) {
      try {
        const { link } = await createPasswordResetToken(user.id);
        await sendResetPasswordEmail(email, link);
      } catch (_e) {
        // swallow to avoid enumeration
      }
    }
    // always resolve without throwing
    return true;
  }

  async resetPassword(token: string, password: string) {
    const rec = await consumePasswordResetToken(token);
    if (!rec) {
      const error: any = new Error("Invalid or expired token");
      error.code = 400;
      throw error;
    }

    const user = await prisma.user.findUnique({ where: { id: rec.userId } });
    if (!user) {
      const error: any = new Error("Invalid or expired token");
      error.code = 400;
      throw error;
    }

    const hash = await bcrypt.hash(password, 10);
    await prisma.$transaction([
      prisma.user.update({
        where: { id: user.id },
        data: { password: hash, isActivated: true },
      }),
      prisma.passwordResetToken.update({
        where: { tokenHash: rec.tokenHash },
        data: { usedAt: new Date() },
      }),
    ]);
    await upsertLoginActivity(user.id, "Password reset", "SUCCESS", null);
    return true;
  }
}

export const authService = new AuthService();
