import crypto from 'crypto';
import { prisma, tenantDb } from '../config/database.js';
import { hashPassword } from '../auth/passwords.js';
import { generateAccessToken, generateRefreshToken } from '../auth/tokens.js';
import { emailService } from './email/email.service.js';
import type { JWTPayload, Role } from '@horux/shared';

const INVITATION_EXPIRY_DAYS = 7;

export async function createInvitation(data: {
  email: string;
  invitedBy: string;
  invitedByName: string;
  nombreDespacho?: string;
  rfc?: string;
}) {
  const { email, invitedBy, invitedByName, nombreDespacho, rfc } = data;

  const normalizedEmail = email.toLowerCase().trim();

  // Verificar que no exista un usuario con este email
  const existingUser = await prisma.user.findUnique({ where: { email: normalizedEmail } });
  if (existingUser) {
    throw new Error('Ya existe un usuario registrado con este email');
  }

  // Verificar que no haya una invitación pendiente para este email
  const existingPending = await prisma.clientInvitation.findFirst({
    where: { email: normalizedEmail, status: 'pending' },
  });
  if (existingPending) {
    throw new Error('Ya existe una invitación pendiente para este email');
  }

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

  const invitation = await prisma.clientInvitation.create({
    data: {
      email: normalizedEmail,
      invitedBy,
      nombreDespacho: nombreDespacho || null,
      rfc: rfc?.toUpperCase() || null,
      token,
      expiresAt,
    },
  });

  const baseUrl = process.env.WEB_URL || 'https://horuxfin.com';
  const registerUrl = `${baseUrl}/invitacion/registro/${token}`;

  await emailService.sendClientInvitation(normalizedEmail, {
    invitedByName,
    registerUrl,
    expiresAt: expiresAt.toLocaleDateString('es-MX', {
      day: 'numeric',
      month: 'long',
      year: 'numeric',
    }),
    nombreDespacho: nombreDespacho || null,
  });

  return invitation;
}

export async function validateInvitationToken(token: string) {
  const invitation = await prisma.clientInvitation.findUnique({ where: { token } });
  if (!invitation) {
    throw new Error('Invitación no encontrada');
  }
  if (invitation.status !== 'pending') {
    throw new Error(`Invitación ya ${invitation.status === 'accepted' ? 'aceptada' : 'expirada'}`);
  }
  if (invitation.expiresAt < new Date()) {
    await prisma.clientInvitation.update({
      where: { id: invitation.id },
      data: { status: 'expired' },
    });
    throw new Error('Invitación expirada');
  }
  return invitation;
}

export async function registerFromInvitation(
  token: string,
  data: {
    nombre: string;
    password: string;
    nombreDespacho: string;
    rfc: string;
    verticalProfile: 'CONTABLE' | 'JURIDICO' | 'ARQUITECTURA';
    codigoPostal?: string;
  }
) {
  const invitation = await validateInvitationToken(token);

  // Verificar nuevamente que no exista el usuario
  const existingUser = await prisma.user.findUnique({ where: { email: invitation.email } });
  if (existingUser) {
    throw new Error('Ya existe un usuario registrado con este email');
  }

  const passwordHash = await hashPassword(data.password);

  const tenantSlug = `despacho_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
  const databaseName = `horux_${tenantSlug}`;

  const result = await prisma.$transaction(async (tx) => {
    const tenant = await tx.tenant.create({
      data: {
        nombre: data.nombreDespacho,
        rfc: data.rfc.toUpperCase(),
        plan: 'trial',
        databaseName,
        verticalProfile: data.verticalProfile as any,
        dbMode: 'MANAGED',
        dbSchemaVersion: 0,
        trialEndsAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
        codigoPostal: data.codigoPostal,
      },
    });

    const user = await tx.user.create({
      data: {
        email: invitation.email,
        passwordHash,
        nombre: data.nombre,
        lastTenantId: tenant.id,
      },
    });

    const ownerRole = await tx.rol.findUnique({ where: { nombre: 'owner' } });
    if (!ownerRole) throw new Error('Rol owner no encontrado en BD');

    await tx.tenantMembership.create({
      data: {
        userId: user.id,
        tenantId: tenant.id,
        rolId: ownerRole.id,
        isOwner: true,
      },
    });

    await tx.clientInvitation.update({
      where: { id: invitation.id },
      data: { status: 'accepted', acceptedAt: new Date() },
    });

    return { tenant, user };
  });

  try {
    await tenantDb.provisionDatabase(tenantSlug, databaseName);
  } catch (err: any) {
    await prisma.tenant.delete({ where: { id: result.tenant.id } }).catch(() => {});
    await prisma.user.delete({ where: { id: result.user.id } }).catch(() => {});
    throw new Error(`Error al crear base de datos del despacho: ${err.message}`);
  }

  const payload: Omit<JWTPayload, 'iat' | 'exp'> = {
    userId: result.user.id,
    email: result.user.email,
    role: 'owner' as Role,
    tenantId: result.tenant.id,
    tokenVersion: 0,
  };

  const accessToken = generateAccessToken(payload);
  const refreshToken = generateRefreshToken(payload);

  await prisma.refreshToken.create({
    data: {
      userId: result.user.id,
      token: refreshToken,
      expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
    },
  });

  // Send welcome email (fire-and-forget)
  emailService.sendDespachoWelcome(result.user.email, {
    nombre: result.user.nombre,
    despachoNombre: result.tenant.nombre,
    email: result.user.email,
  }).catch(err => console.error('[Invitation] Welcome email failed:', err));

  return {
    accessToken,
    refreshToken,
    user: {
      id: result.user.id,
      email: result.user.email,
      nombre: result.user.nombre,
      role: 'owner' as Role,
      tenantId: result.tenant.id,
      tenantName: result.tenant.nombre,
      tenantRfc: result.tenant.rfc,
      plan: result.tenant.plan,
      tenants: [{
        id: result.tenant.id,
        nombre: result.tenant.nombre,
        rfc: result.tenant.rfc,
        plan: result.tenant.plan,
        role: 'owner' as Role,
        isOwner: true,
      }],
    },
  };
}

export async function resendInvitation(invitationId: string, invitedByName: string) {
  const invitation = await prisma.clientInvitation.findUnique({
    where: { id: invitationId },
  });
  if (!invitation) {
    throw new Error('Invitación no encontrada');
  }
  if (invitation.status !== 'pending') {
    throw new Error('Solo se pueden reenviar invitaciones pendientes');
  }

  const newToken = crypto.randomBytes(32).toString('hex');
  const newExpiresAt = new Date(Date.now() + INVITATION_EXPIRY_DAYS * 24 * 60 * 60 * 1000);

  await prisma.clientInvitation.update({
    where: { id: invitationId },
    data: {
      token: newToken,
      expiresAt: newExpiresAt,
      sentAt: new Date(),
    },
  });

  const baseUrl = process.env.WEB_URL || 'https://horuxfin.com';
  const registerUrl = `${baseUrl}/invitacion/registro/${newToken}`;

  await emailService.sendClientInvitation(invitation.email, {
    invitedByName,
    registerUrl,
    expiresAt: newExpiresAt.toLocaleDateString('es-MX', {
      day: 'numeric',
      month: 'long',
      year: 'numeric',
    }),
    nombreDespacho: invitation.nombreDespacho,
  });

  return { message: 'Invitación reenviada' };
}

export async function listInvitations() {
  return prisma.clientInvitation.findMany({
    orderBy: { createdAt: 'desc' },
  });
}
