import crypto from "crypto";
import bcrypt from "bcryptjs";
import { BadRequestError, NotFoundError, ConflictError } from "../../common/errors/http-errors";
import { mailService } from "../../infrastructure/mail/mail.service";
import * as empRepo from "./employee.repo";
import { prisma } from "../../config/prisma";
import { Role } from "@prisma/client";

const INVITE_EXPIRES_HOURS = 48;

export async function inviteEmployee(
  organizationId: string,
  data: { name: string; email: string; phone?: string; gender?: string; role: Role; departmentId?: string }
) {
  const email = data.email.toLowerCase();

  const existingUser = await prisma.user.findUnique({ where: { email } });
  if (existingUser) throw new ConflictError("User with this email already exists");

  const existingInvite = await empRepo.findInvitationByEmail(organizationId, email);
  if (existingInvite) throw new ConflictError("Invitation already sent to this email");

  const token = crypto.randomBytes(32).toString("hex");
  const expiresAt = new Date(Date.now() + INVITE_EXPIRES_HOURS * 60 * 60 * 1000);

  await empRepo.createInvitation({
    organizationId,
    email,
    name: data.name,
    phone: data.phone,
    gender: data.gender,
    role: data.role,
    departmentId: data.departmentId,
    token,
    expiresAt,
  });

  const inviteLink = `${process.env.FRONTEND_URL ?? "http://localhost:3000"}/accept-invite?token=${token}`;

  let departmentName: string | undefined;
  if (data.departmentId) {
    const dept = await prisma.department.findUnique({ where: { id: data.departmentId }, select: { name: true } });
    departmentName = dept?.name;
  }

  await mailService.sendInvitationEmail({
    to: email,
    name: data.name,
    email,
    role: data.role,
    department: departmentName,
    inviteLink,
    expiresInHours: INVITE_EXPIRES_HOURS,
  });

  return { message: "Invitation sent successfully", email };
}

export async function acceptInvitation(token: string, password: string) {
  const invitation = await empRepo.findInvitationByToken(token);

  if (!invitation) throw new NotFoundError("Invalid invitation token");
  if (invitation.status !== "INVITED") throw new BadRequestError("Invitation already accepted");
  if (!invitation.expiresAt || invitation.expiresAt < new Date()) throw new BadRequestError("Invitation has expired");

  const existingUser = await prisma.user.findUnique({ where: { email: invitation.email } });
  if (existingUser) throw new ConflictError("User already registered");

  const passwordHash = await bcrypt.hash(password, 12);

  const { user, employee } = await empRepo.acceptInvitation({
    employeeId: invitation.id,
    organizationId: invitation.organizationId,
    name: invitation.name,
    email: invitation.email,
    passwordHash,
    role: invitation.role,
  });

  return {
    message: "Account created successfully. You can now login.",
    user: { id: user.id, name: user.name, email: user.email, role: user.role },
    employeeId: employee.id,
  };
}

export async function getEmployees(
  organizationId: string,
  page: number,
  limit: number,
  search?: string,
  status?: string,
  role?: string,
) {
  const validStatuses = ["INVITED", "ACTIVE", "INACTIVE"];
  const validRoles = ["EMPLOYEE", "MANAGER"];
  const safeStatus = status && validStatuses.includes(status) ? status as any : undefined;
  const safeRole = role && validRoles.includes(role) ? role as any : undefined;
  return empRepo.findEmployeesByOrg(organizationId, page, limit, search, safeStatus, safeRole);
}

export async function getEmployee(id: string, organizationId: string) {
  const emp = await empRepo.findEmployeeById(id, organizationId);
  if (!emp) throw new NotFoundError("Employee not found");
  return emp;
}

export async function updateEmployee(
  id: string,
  organizationId: string,
  data: { name?: string; role?: Role; departmentId?: string; phone?: string; gender?: string; status?: string }
) {
  const emp = await empRepo.findEmployeeById(id, organizationId);
  if (!emp) throw new NotFoundError("Employee not found");
  return empRepo.updateEmployee(id, data);
}

export async function deleteEmployee(id: string, organizationId: string) {
  const emp = await empRepo.findEmployeeById(id, organizationId);
  if (!emp) throw new NotFoundError("Employee not found");
  return empRepo.deleteEmployee(id);
}
