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 clientRepo from "./client.repo";
import { prisma } from "../../config/prisma";

const INVITE_EXPIRES_HOURS = 48;

export async function inviteClient(
  organizationId: string,
  data: { name: string; email: string; companyName?: string; gstin?: string; pan?: string; phone?: 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 clientRepo.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 clientRepo.createInvitation({
    organizationId,
    email,
    name: data.name,
    token,
    expiresAt,
    companyName: data.companyName,
    gstin: data.gstin,
    pan: data.pan,
    phone: data.phone,
  });

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

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

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

export async function acceptInvitation(token: string, password: string) {
  const invitation = await clientRepo.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, client } = await clientRepo.acceptInvitation({
    clientId: invitation.id,
    organizationId: invitation.organizationId,
    name: invitation.name,
    email: invitation.email,
    passwordHash,
  });

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

export async function getClients(
  organizationId: string,
  page: number,
  limit: number,
  search?: string,
  status?: string,
) {
  const validStatuses = ["INVITED", "ACTIVE", "SUSPENDED", "REVOKED"];
  const safeStatus = status && validStatuses.includes(status) ? (status as any) : undefined;
  return clientRepo.findClientsByOrg(organizationId, page, limit, search, safeStatus);
}

export async function getClient(id: string, organizationId: string) {
  const client = await clientRepo.findClientById(id, organizationId);
  if (!client) throw new NotFoundError("Client not found");
  return client;
}

export async function updateClient(
  id: string,
  organizationId: string,
  data: { name?: string; companyName?: string; gstin?: string; pan?: string; phone?: string; status?: string },
) {
  const client = await clientRepo.findClientById(id, organizationId);
  if (!client) throw new NotFoundError("Client not found");
  return clientRepo.updateClient(id, data);
}

export async function deleteClient(id: string, organizationId: string) {
  const client = await clientRepo.findClientById(id, organizationId);
  if (!client) throw new NotFoundError("Client not found");
  return clientRepo.deleteClient(id);
}
