import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import { env } from "../../config/env";
import {
  UnauthorizedError,
  ConflictError,
  NotFoundError,
  BadRequestError,
} from "../../common/errors/http-errors";
import { mailService } from "../../infrastructure/mail/mail.service";
import * as authRepo from "./auth.repository";
import {
  uploadToCloudinary,
  deleteFromCloudinary,
  extractPublicId,
} from "../../common/utils/upload-to-cloudinary";

interface RegisterInput {
  organizationId?: string;
  name: string;
  email: string;
  password: string;
  phone?: string;
}

// interface RegisterInput {
//   name: string;
//   email: string;
//   password: string;
//   phone?: string;
//   role?: string;
// }

interface LoginInput {
  email: string;
  password: string;
}

function parseExpiresIn(expiresIn: string): Date {
  const match = expiresIn.match(/^(\d+)([smhd])$/);
  const now = new Date();
  if (!match) return new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
  const value = parseInt(match[1], 10);
  const unit = match[2];
  const unitMs: Record<string, number> = {
    s: 1000,
    m: 60 * 1000,
    h: 60 * 60 * 1000,
    d: 24 * 60 * 60 * 1000,
  };
  return new Date(now.getTime() + value * (unitMs[unit] ?? unitMs.d));
}

function generateOtp(): string {
  return Math.floor(100000 + Math.random() * 900000).toString();
}

function signTokens(payload: { id: string; role: string; organizationId: string | null }) {
  const accessToken = jwt.sign(payload, env.JWT_ACCESS_SECRET, {
    expiresIn: env.JWT_ACCESS_EXPIRES_IN as any,
  });
  const refreshToken = jwt.sign(payload, env.JWT_REFRESH_SECRET, {
    expiresIn: env.JWT_REFRESH_EXPIRES_IN as any,
  });
  return { accessToken, refreshToken };
}

function toAuthUser(user: {
  id: string;
  name: string;
  email: string;
  role: string;
  phone?: string | null;
  organizationId: string | null;
  avatarUrl?: string | null;
}) {
  return {
    id: user.id,
    name: user.name,
    email: user.email,
    role: user.role,
    phone: user.phone ?? undefined,
    organizationId: user.organizationId,
    avatarUrl: user.avatarUrl ?? undefined,
  };
}

async function issueSessionAndTokens(
  user: { id: string; role: string; organizationId: string | null },
  userAgent?: string,
  ipAddress?: string,
) {
  const tokens = signTokens({
    id: user.id,
    role: user.role,
    organizationId: user.organizationId,
  });
  await authRepo.createSession({
    userId: user.id,
    refreshToken: tokens.refreshToken,
    userAgent,
    ipAddress,
    expiresAt: parseExpiresIn(env.JWT_REFRESH_EXPIRES_IN),
  });
  return tokens;
}

// ---------------------------------------------------------------------------
// REGISTER
// ---------------------------------------------------------------------------
// export async function register(input: RegisterInput) {
//   const email = input.email.toLowerCase();

//   const existing = await authRepo.findUserByEmail(email);

//   if (existing) {
//     // If user exists but is INACTIVE (never verified OTP), allow re-registration:
//     // delete old unverified user and re-create so they get a fresh OTP
//     if (existing.status === "INACTIVE") {
//       await authRepo.deleteOtpsByEmailAndPurpose(email, "EMAIL_VERIFICATION");
//       await authRepo.deleteUserById(existing.id);
//     } else {
//       throw new ConflictError("Email already registered");
//     }
//   }

//   if (input.phone) {
//     const existingPhone = await authRepo.findUserByPhone(input.phone);
//     if (existingPhone) throw new ConflictError("Phone number already registered");
//   }

//   if (input.organizationId) {
//     const org = await authRepo.findOrganizationById(input.organizationId);
//     if (!org) throw new NotFoundError("Organization not found");
//   }

//   const passwordHash = await bcrypt.hash(input.password, 12);
//   await authRepo.createUser({
//     organizationId: input.organizationId,
//     name: input.name,
//     email,
//     passwordHash,
//     phone: input.phone,
//   });

//   const otp = generateOtp();
//   await authRepo.createOtp({
//     email,
//     otp,
//     purpose: "EMAIL_VERIFICATION",
//     expiresAt: new Date(Date.now() + 15 * 60 * 1000),
//   });

//   await mailService.sendOtpEmail(email, otp, "Email Verification");

//   return {
//     message: "Registration successful. Please verify your email with the OTP sent.",
//     email,
//   };
// }

export async function register(input: RegisterInput) {
  const email = input.email.toLowerCase();

  const existing = await authRepo.findUserByEmail(email);

  if (existing) {
    if (existing.status === "INACTIVE") {
      await authRepo.deleteOtpsByEmailAndPurpose(email, "EMAIL_VERIFICATION");
      await authRepo.deleteUserById(existing.id);
    } else {
      throw new ConflictError("Email already registered");
    }
  }

  if (input.phone) {
    const existingPhone = await authRepo.findUserByPhone(input.phone);
    if (existingPhone) throw new ConflictError("Phone number already registered");
  }

  const passwordHash = await bcrypt.hash(input.password, 12);
  await authRepo.createUser({
    name: input.name,
    email,
    passwordHash,
    phone: input.phone,
    role: "ADMIN",   
    organizationId: undefined,  // organization created later via onboarding
  });

  const otp = generateOtp();
  await authRepo.createOtp({
    email,
    otp,
    purpose: "EMAIL_VERIFICATION",
    expiresAt: new Date(Date.now() + 15 * 60 * 1000),
  });

  await mailService.sendOtpEmail(email, otp, "Email Verification");

  return {
    message: "Registration successful. Please verify your email with the OTP sent.",
    email,
  };
}

// ---------------------------------------------------------------------------
// VERIFY OTP
// ---------------------------------------------------------------------------
export async function verifyOtp(
  email: string,
  otp: string,
  purpose?: "EMAIL_VERIFICATION" | "FORGOT_PASSWORD",
  userAgent?: string,
  ipAddress?: string,
) {
  const emailLower = email.toLowerCase();
  const verification = await authRepo.findLatestOtp(emailLower, purpose);

  if (!verification) {
    throw new NotFoundError("No active OTP verification request found for this email");
  }
  if (verification.expiresAt < new Date()) {
    throw new BadRequestError("OTP has expired. Please request a new one");
  }
  if (verification.attempts >= 5) {
    throw new BadRequestError("Max verification attempts exceeded. Please request a new OTP");
  }
  if (verification.otp !== otp) {
    await authRepo.incrementOtpAttempts(verification.id);
    throw new UnauthorizedError("Invalid OTP");
  }

  await authRepo.markOtpVerified(verification.id);

  if (purpose === "EMAIL_VERIFICATION") {
    const user = await authRepo.findUserByEmail(emailLower);
    if (!user) throw new NotFoundError("User not found");

    const updatedUser = await authRepo.activateUser(user.id);
    const tokens = await issueSessionAndTokens(updatedUser, userAgent, ipAddress);
    await authRepo.deleteOtpsByEmailAndPurpose(emailLower, "EMAIL_VERIFICATION");

    return {
      verified: true,
      ...tokens,
      user: toAuthUser(updatedUser),
    };
  }

  return { verified: true };
}

// ---------------------------------------------------------------------------
// LOGIN
// ---------------------------------------------------------------------------
export async function login(input: LoginInput, userAgent?: string, ipAddress?: string) {
  const email = input.email.toLowerCase();
  const user = await authRepo.findUserByEmail(email);

  if (!user) {
    throw new UnauthorizedError("No account found with this email address.");
  }

  if (user.status === "INACTIVE") {
    throw new UnauthorizedError("Account not verified. Please complete email verification before logging in.");
  }

  if (user.status !== "ACTIVE") {
    throw new UnauthorizedError("Your account has been suspended. Please contact support.");
  }

  const valid = await bcrypt.compare(input.password, user.passwordHash);
  if (!valid) throw new UnauthorizedError("Incorrect password. Please try again.");

  const tokens = await issueSessionAndTokens(user, userAgent, ipAddress);
  return { ...tokens, user: toAuthUser(user) };
}

// ---------------------------------------------------------------------------
// REFRESH
// ---------------------------------------------------------------------------
export async function refresh(refreshToken: string, userAgent?: string, ipAddress?: string) {
  const session = await authRepo.findSessionByToken(refreshToken);

  if (
    !session ||
    session.expiresAt < new Date() ||
    session.user.status !== "ACTIVE"
  ) {
    if (session) await authRepo.deleteSessionById(session.id);
    throw new UnauthorizedError("Session expired or revoked");
  }

  try {
    const payload = jwt.verify(refreshToken, env.JWT_REFRESH_SECRET) as {
      id: string;
      role: string;
      organizationId: string;
    };

    await authRepo.deleteSessionById(session.id);
    const tokens = await issueSessionAndTokens(payload, userAgent, ipAddress);
    return tokens;
  } catch {
    await authRepo.deleteSessionById(session.id);
    throw new UnauthorizedError("Invalid refresh token");
  }
}

// ---------------------------------------------------------------------------
// LOGOUT
// ---------------------------------------------------------------------------
export async function logout(refreshToken: string) {
  if (refreshToken) await authRepo.deleteSessionByToken(refreshToken);
}

// ---------------------------------------------------------------------------
// FORGOT PASSWORD
// ---------------------------------------------------------------------------
export async function forgotPassword(email: string) {
  const emailLower = email.toLowerCase();
  const user = await authRepo.findActiveUserByEmail(emailLower);

  // Generic message always — prevents user enumeration
  const genericResponse = {
    message: "If an account with this email exists, a password reset OTP has been sent.",
  };
  if (!user || user.status !== "ACTIVE") return genericResponse;

  const otp = generateOtp();
  await authRepo.createOtp({
    email: emailLower,
    otp,
    purpose: "FORGOT_PASSWORD",
    expiresAt: new Date(Date.now() + 15 * 60 * 1000),
  });
  await mailService.sendOtpEmail(emailLower, otp, "Forgot Password");

  return genericResponse;
}

// ---------------------------------------------------------------------------
// RESET PASSWORD
// ---------------------------------------------------------------------------
export async function resetPassword(input: { email: string; otp: string; newPassword: string }) {
  const emailLower = input.email.toLowerCase();

  // Verify OTP first — throws if invalid/expired/wrong
  const verification = await authRepo.findLatestOtp(emailLower, "FORGOT_PASSWORD");

  if (!verification) {
    throw new NotFoundError("No active OTP found. Please request a new reset code.");
  }
  if (verification.expiresAt < new Date()) {
    throw new BadRequestError("OTP has expired. Please request a new reset code.");
  }
  if (verification.attempts >= 5) {
    throw new BadRequestError("Max attempts exceeded. Please request a new OTP.");
  }
  if (verification.otp !== input.otp) {
    await authRepo.incrementOtpAttempts(verification.id);
    throw new UnauthorizedError("Invalid OTP. Please check and try again.");
  }

  // OTP matched — mark verified
  await authRepo.markOtpVerified(verification.id);

  const user = await authRepo.findActiveUserByEmail(emailLower);
  if (!user) throw new NotFoundError("User not found");

  const passwordHash = await bcrypt.hash(input.newPassword, 12);
  await authRepo.updateUserPassword(user.id, passwordHash);
  await authRepo.deleteAllSessionsForUser(user.id);
  await authRepo.deleteOtpsByEmailAndPurpose(emailLower, "FORGOT_PASSWORD");

  return { message: "Password reset successfully" };
}

// ---------------------------------------------------------------------------
// CHANGE PASSWORD
// ---------------------------------------------------------------------------
export async function changePassword(userId: string, currentPassword: string, newPassword: string) {
  const user = await authRepo.findUserById(userId);
  if (!user) throw new NotFoundError("User not found");

  const valid = await bcrypt.compare(currentPassword, user.passwordHash);
  if (!valid) throw new UnauthorizedError("Incorrect current password");

  const passwordHash = await bcrypt.hash(newPassword, 12);
  await authRepo.updateUserPassword(userId, passwordHash);

  return { message: "Password changed successfully" };
}

// ---------------------------------------------------------------------------
// SESSIONS
// ---------------------------------------------------------------------------
export async function getSessions(userId: string) {
  return authRepo.findSessionsByUser(userId);
}

export async function revokeSession(userId: string, sessionId: string) {
  const session = await authRepo.findSessionByIdAndUser(sessionId, userId);
  if (!session) throw new NotFoundError("Session not found");
  await authRepo.deleteSessionById(sessionId);
  return { message: "Session revoked successfully" };
}

export async function revokeAllSessions(userId: string, currentRefreshToken?: string) {
  await authRepo.deleteAllSessionsForUser(userId, currentRefreshToken);
  return { message: "Sessions revoked successfully" };
}

// ---------------------------------------------------------------------------
// UPDATE PROFILE
// ---------------------------------------------------------------------------
export async function updateProfile(userId: string, input: { name?: string; phone?: string }) {
  if (input.phone) {
    const existing = await authRepo.findUserByPhone(input.phone);
    if (existing && existing.id !== userId) throw new ConflictError("Phone number already in use");
  }
  const user = await authRepo.updateUserProfile(userId, input);
  return toAuthUser(user);
}

// ---------------------------------------------------------------------------
// UPLOAD AVATAR
// ---------------------------------------------------------------------------
export async function uploadAvatar(userId: string, file: Express.Multer.File) {
  const user = await authRepo.findUserById(userId);
  if (!user) throw new NotFoundError("User not found");

  // Delete old avatar from cloudinary if exists
  if (user.avatarUrl) {
    const publicId = extractPublicId(user.avatarUrl);
    if (publicId) await deleteFromCloudinary(publicId).catch(() => null);
  }

  const avatarUrl = await uploadToCloudinary(file, "user-avatars");
  const updated = await authRepo.updateUserProfile(userId, { avatarUrl });
  return toAuthUser(updated);
}

// ---------------------------------------------------------------------------
// REMOVE AVATAR
// ---------------------------------------------------------------------------
export async function removeAvatar(userId: string) {
  const user = await authRepo.findUserById(userId);
  if (!user) throw new NotFoundError("User not found");

  if (user.avatarUrl) {
    const publicId = extractPublicId(user.avatarUrl);
    if (publicId) await deleteFromCloudinary(publicId).catch(() => null);
  }

  const updated = await authRepo.updateUserProfile(userId, { avatarUrl: null });
  return toAuthUser(updated);
}

// ---------------------------------------------------------------------------
// USER DETAILS
// ---------------------------------------------------------------------------
export async function userDetails(id: string) {
  const user = await authRepo.findUserById(id);
  if (!user) throw new NotFoundError("User not found");
  return toAuthUser(user);
}
