import { prisma } from '../config/database.js';
import bcrypt from 'bcryptjs';
import { randomBytes } from 'crypto';
import { getDespachoPlanLimits } from './plan-catalogo.service.js';
import { emailService } from './email/email.service.js';
import type { UserListItem, UserInvite, UserUpdate, Role } from '@horux/shared';

/**
 * Refactor F6.2 (multi-tenant): los users se listan/invitan/borran vía
 * `tenant_memberships`. `User.tenantId` y `User.rolId` se siguen poblando al
 * crear (legacy, F6.4 los borra) pero NO se leen en este servicio.
 *
 * - getUsuarios(tenantId) → memberships activos en ese tenant
 * - inviteUsuario → crea User + Membership (siempre isOwner=false aquí)
 * - updateUsuario → cambia role en la membership de ese tenant; active es global
 * - deleteUsuario → desactiva membership (soft delete por tenant). El user
 *   sigue existiendo si tiene otras memberships.
 */

async function getRolId(nombre: string): Promise<number> {
  const rol = await prisma.rol.findUnique({ where: { nombre } });
  if (!rol) throw new Error(`Rol '${nombre}' no encontrado`);
  return rol.id;
}

/** Mapea una row de membership con user joineado al shape UserListItem. */
function mapMembershipRow(m: any, includeTenant = false): UserListItem {
  return {
    id: m.user.id,
    email: m.user.email,
    nombre: m.user.nombre,
    role: m.rol.nombre as Role,
    active: m.user.active && m.active, // user activo Y membership activa
    lastLogin: m.user.lastLogin?.toISOString() || null,
    createdAt: m.user.createdAt.toISOString(),
    ...(includeTenant && { tenantId: m.tenantId, tenantName: m.tenant.nombre }),
  };
}

const MEMBERSHIP_INCLUDE = {
  user: {
    select: {
      id: true,
      email: true,
      nombre: true,
      active: true,
      lastLogin: true,
      createdAt: true,
    },
  },
  rol: { select: { nombre: true } },
};

export async function getUsuarios(tenantId: string): Promise<UserListItem[]> {
  const memberships = await prisma.tenantMembership.findMany({
    where: { tenantId, active: true },
    include: MEMBERSHIP_INCLUDE,
    orderBy: { joinedAt: 'desc' },
  });

  return memberships.map(m => mapMembershipRow(m));
}

export async function inviteUsuario(tenantId: string, data: UserInvite): Promise<UserListItem> {
  const tenant = await prisma.tenant.findUnique({
    where: { id: tenantId },
    select: { plan: true },
  });

  // Límite del catálogo despacho desde BD (con cache). -1 = ilimitado.
  // Si el plan no existe en BD (shouldn't happen post-seed) cae a 1 como
  // mínimo defensivo para no permitir invitaciones masivas accidentales.
  const planLimits = tenant ? await getDespachoPlanLimits(tenant.plan) : null;
  const maxUsers = planLimits?.maxUsers ?? 1;

  // Cuenta memberships activos del tenant (no users globales) — esto es el
  // límite real ahora que los users pueden estar en varios tenants.
  const currentCount = await prisma.tenantMembership.count({
    where: { tenantId, active: true },
  });

  if (maxUsers !== -1 && currentCount >= maxUsers) {
    throw new Error('Límite de usuarios alcanzado para este plan');
  }

  // Si el email ya existe como user global, agregamos membership en este
  // tenant en vez de crear un user nuevo. Cubre el caso "el contador X ya
  // trabaja en otra empresa, ahora lo invitan a esta también".
  let user = await prisma.user.findUnique({ where: { email: data.email } });
  let tempPassword: string | null = null;

  if (!user) {
    tempPassword = randomBytes(4).toString('hex');
    const passwordHash = await bcrypt.hash(tempPassword, 12);
    user = await prisma.user.create({
      data: {
        email: data.email,
        passwordHash,
        nombre: data.nombre,
        lastTenantId: tenantId,
      },
    });

    // Enviar correo de bienvenida con credenciales (non-blocking)
    emailService.sendWelcome(data.email, {
      nombre: data.nombre,
      email: data.email,
      tempPassword,
    }).catch(err => console.error('[EMAIL] Welcome email failed:', err));
  }

  const rolId = await getRolId(data.role);

  // Membership en este tenant. Si ya existía (caso edge: re-invitación tras
  // delete), reactivar. isOwner siempre false (owners se crean por register
  // o addTenantToOwner).
  await prisma.tenantMembership.upsert({
    where: { userId_tenantId: { userId: user.id, tenantId } },
    update: { rolId, isOwner: false, active: true },
    create: {
      userId: user.id,
      tenantId,
      rolId,
      isOwner: false,
      active: true,
    },
  });

  // Volvemos a leer la membership para devolver el shape correcto
  const membership = await prisma.tenantMembership.findUnique({
    where: { userId_tenantId: { userId: user.id, tenantId } },
    include: MEMBERSHIP_INCLUDE,
  });

  return mapMembershipRow(membership!);
}

export async function updateUsuario(
  tenantId: string,
  userId: string,
  data: UserUpdate
): Promise<UserListItem> {
  // Verifica que la membership existe en este tenant antes de actualizar.
  const membership = await prisma.tenantMembership.findUnique({
    where: { userId_tenantId: { userId, tenantId } },
  });
  if (!membership) {
    throw new Error('El usuario no es miembro de este tenant');
  }

  // El cambio de role es por-tenant (afecta solo la membership)
  if (data.role) {
    const rolId = await getRolId(data.role);
    await prisma.tenantMembership.update({
      where: { userId_tenantId: { userId, tenantId } },
      data: { rolId },
    });
  }

  // active y nombre son globales del user
  const userUpdate: any = {};
  if (data.nombre) userUpdate.nombre = data.nombre;
  if (data.active !== undefined) userUpdate.active = data.active;
  if (Object.keys(userUpdate).length > 0) {
    await prisma.user.update({ where: { id: userId }, data: userUpdate });
  }

  const refreshed = await prisma.tenantMembership.findUnique({
    where: { userId_tenantId: { userId, tenantId } },
    include: MEMBERSHIP_INCLUDE,
  });

  return mapMembershipRow(refreshed!);
}

export async function deleteUsuario(tenantId: string, userId: string): Promise<void> {
  // Soft-delete: desactiva la membership. El user sigue existiendo (puede
  // tener acceso a otros tenants). Si era su única membership activa, queda
  // sin acceso pero no se borra el record.
  const result = await prisma.tenantMembership.deleteMany({
    where: { userId, tenantId },
  });
  if (result.count === 0) {
    throw new Error('El usuario no es miembro de este tenant');
  }
}

// ============================================================================
// Admin global (cross-tenant)
// ============================================================================

/**
 * Lista todos los memberships del sistema. Cada row es una combinación
 * (user, tenant) — un mismo user con N memberships aparece N veces. La UI
 * admin global lo presenta así para ser explícita sobre quién tiene acceso
 * dónde.
 */
export async function getAllUsuarios(): Promise<UserListItem[]> {
  const memberships = await prisma.tenantMembership.findMany({
    where: { active: true },
    include: {
      ...MEMBERSHIP_INCLUDE,
      tenant: { select: { id: true, nombre: true } },
    },
    orderBy: [{ tenant: { nombre: 'asc' } }, { joinedAt: 'desc' }],
  });

  return memberships.map(m => mapMembershipRow(m, true));
}

export async function createUsuarioGlobal(
  tenantId: string,
  data: UserInvite
): Promise<UserListItem> {
  const tenant = await prisma.tenant.findUnique({
    where: { id: tenantId },
    select: { plan: true },
  });

  // Límite del catálogo despacho desde BD (con cache). -1 = ilimitado.
  const planLimits = tenant ? await getDespachoPlanLimits(tenant.plan) : null;
  const maxUsers = planLimits?.maxUsers ?? 1;

  const currentCount = await prisma.tenantMembership.count({
    where: { tenantId, active: true },
  });

  if (maxUsers !== -1 && currentCount >= maxUsers) {
    throw new Error('Límite de usuarios alcanzado para este plan');
  }

  // Si el email ya existe como user global, agregamos membership en este tenant
  let user = await prisma.user.findUnique({ where: { email: data.email } });

  let tempPassword: string | null = null;
  if (!user) {
    tempPassword = randomBytes(4).toString('hex');
    const passwordHash = await bcrypt.hash(tempPassword, 12);
    user = await prisma.user.create({
      data: {
        email: data.email,
        passwordHash,
        nombre: data.nombre,
        lastTenantId: tenantId,
      },
    });

    // Enviar correo de bienvenida con credenciales (non-blocking)
    emailService.sendWelcome(data.email, {
      nombre: data.nombre,
      email: data.email,
      tempPassword,
    }).catch(err => console.error('[EMAIL] Welcome email failed:', err));
  }

  const rolId = await getRolId(data.role);

  await prisma.tenantMembership.upsert({
    where: { userId_tenantId: { userId: user.id, tenantId } },
    update: { rolId, isOwner: false, active: true },
    create: {
      userId: user.id,
      tenantId,
      rolId,
      isOwner: false,
      active: true,
    },
  });

  const membership = await prisma.tenantMembership.findUnique({
    where: { userId_tenantId: { userId: user.id, tenantId } },
    include: MEMBERSHIP_INCLUDE,
  });

  return mapMembershipRow(membership!);
}

export async function updateUsuarioGlobal(
  userId: string,
  data: UserUpdate & { tenantId?: string }
): Promise<UserListItem> {
  // Si data.tenantId está presente: actualiza el role en esa membership.
  // Si data.active está: actualiza User.active globalmente.
  const targetTenantId = data.tenantId;

  if (data.role && targetTenantId) {
    const rolId = await getRolId(data.role);
    await prisma.tenantMembership.update({
      where: { userId_tenantId: { userId, tenantId: targetTenantId } },
      data: { rolId },
    });
  }

  const userUpdate: any = {};
  if (data.nombre) userUpdate.nombre = data.nombre;
  if (data.active !== undefined) userUpdate.active = data.active;
  if (Object.keys(userUpdate).length > 0) {
    await prisma.user.update({ where: { id: userId }, data: userUpdate });
  }

  // Devuelve la primera membership activa del user (o la del tenant target si
  // se especificó) para mantener el shape esperado por el caller.
  const where = targetTenantId
    ? { userId_tenantId: { userId, tenantId: targetTenantId } }
    : undefined;

  const m = where
    ? await prisma.tenantMembership.findUnique({
        where,
        include: { ...MEMBERSHIP_INCLUDE, tenant: { select: { id: true, nombre: true } } },
      })
    : await prisma.tenantMembership.findFirst({
        where: { userId, active: true },
        include: { ...MEMBERSHIP_INCLUDE, tenant: { select: { id: true, nombre: true } } },
        orderBy: { joinedAt: 'asc' },
      });

  if (!m) throw new Error('Usuario sin memberships activas');
  return mapMembershipRow(m, true);
}

export async function deleteUsuarioGlobal(userId: string): Promise<void> {
  // Hard delete del user — cascade borra todas las memberships, refresh
  // tokens, password reset tokens, platform roles, etc.
  await prisma.user.delete({ where: { id: userId } });
}
