import { prisma, tenantDb } from '../config/database.js';
import { DESPACHO_PLANS, type DespachoPlan } from '@horux/shared';
import { emailService } from './email/email.service.js';
import * as metabaseService from './metabase.service.js';
import { randomBytes } from 'crypto';
import bcrypt from 'bcryptjs';

export async function getAllTenants() {
  return prisma.tenant.findMany({
    where: { active: true },
    select: {
      id: true,
      nombre: true,
      rfc: true,
      plan: true,
      databaseName: true,
      createdAt: true,
      verticalProfile: true,
      codigoPostal: true,
      _count: {
        select: { memberships: { where: { active: true } } as any }
      },
      subscriptions: {
        orderBy: { createdAt: 'desc' },
        take: 1,
        select: {
          id: true,
          amount: true,
          currentPeriodEnd: true,
          status: true,
        },
      },
    },
    orderBy: { nombre: 'asc' }
  });
}

export async function getTenantById(id: string) {
  return prisma.tenant.findUnique({
    where: { id },
    select: {
      id: true,
      nombre: true,
      rfc: true,
      plan: true,
      databaseName: true,
      createdAt: true,
      verticalProfile: true,
      codigoPostal: true,
    }
  });
}

export async function createTenant(data: {
  nombre: string;
  rfc: string;
  plan?: DespachoPlan;
  adminEmail: string;
  adminNombre: string;
  amount: number;
  /** Solo plan custom: primera fecha de pago (deadline para que el cliente
   * complete su primer cobro). Se persiste como Subscription.currentPeriodEnd. */
  firstPaymentDueAt?: string | null;
  verticalProfile?: 'CONTABLE' | 'JURIDICO' | 'ARQUITECTURA';
  codigoPostal?: string;
}) {
  const plan = data.plan || 'trial';

  // 1. Provision a dedicated database for this tenant
  const databaseName = await tenantDb.provisionDatabase(data.rfc);

  // 1b. Register tenant database in Metabase (non-blocking, logs errors only)
  metabaseService.registerDatabase({
    nombre: data.nombre,
    dbName: databaseName,
  }).catch(err => console.error('[METABASE] Register failed:', err));

  // 2. Create tenant record
  const isTrial = plan === 'trial';

  const tenant = await prisma.tenant.create({
    data: {
      nombre: data.nombre,
      rfc: data.rfc.toUpperCase(),
      plan,
      databaseName,
      dbMode: 'MANAGED',
      verticalProfile: data.verticalProfile || 'CONTABLE',
      codigoPostal: data.codigoPostal || undefined,
      trialEndsAt: isTrial ? new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) : undefined,
    }
  });

  // 3. Create or reuse admin user
  const ownerRol = await prisma.rol.findUnique({ where: { nombre: 'owner' } });
  if (!ownerRol) throw new Error('Rol owner no encontrado en la base de datos');

  let user = await prisma.user.findUnique({ where: { email: data.adminEmail } });
  let tempPassword: string | null = null;

  if (!user) {
    tempPassword = randomBytes(4).toString('hex'); // 8-char random
    const hashedPassword = await bcrypt.hash(tempPassword, 10);
    user = await prisma.user.create({
      data: {
        email: data.adminEmail,
        passwordHash: hashedPassword,
        nombre: data.adminNombre,
        lastTenantId: tenant.id,
      },
    });
  } else {
    // User ya existe: actualizar lastTenantId y nombre si cambió
    await prisma.user.update({
      where: { id: user.id },
      data: {
        lastTenantId: tenant.id,
        ...(data.adminNombre && data.adminNombre !== user.nombre ? { nombre: data.adminNombre } : {}),
      },
    });
  }

  // Crea membership owner del user en su tenant (fase 4 multi-tenant).
  // Si ya existía (re-invite a otro tenant), reactivar.
  await prisma.tenantMembership.upsert({
    where: { userId_tenantId: { userId: user.id, tenantId: tenant.id } },
    update: { rolId: ownerRol.id, isOwner: true, active: true },
    create: {
      userId: user.id,
      tenantId: tenant.id,
      rolId: ownerRol.id,
      isOwner: true,
      active: true,
    },
  });

  // 4. Create initial subscription. Para plan custom, si admin proveyó
  // firstPaymentDueAt, lo guardamos como currentPeriodEnd — sirve como
  // deadline visible al cliente para realizar su primer pago.
  const firstPayment = plan === 'custom' && data.firstPaymentDueAt
    ? new Date(data.firstPaymentDueAt)
    : null;
  await prisma.subscription.create({
    data: {
      tenantId: tenant.id,
      plan,
      status: 'pending',
      amount: data.amount,
      frequency: 'monthly',
      ...(firstPayment ? { currentPeriodStart: new Date(), currentPeriodEnd: firstPayment } : {}),
    },
  });

  // 5. Send welcome email to client (non-blocking)
  emailService.sendWelcome(data.adminEmail, {
    nombre: data.adminNombre,
    email: data.adminEmail,
    tempPassword,
  }).catch(err => console.error('[EMAIL] Welcome email failed:', err));

  // 6. Send new client notification to admin with DB credentials
  emailService.sendNewClientAdmin({
    clienteNombre: data.nombre,
    clienteRfc: data.rfc.toUpperCase(),
    adminEmail: data.adminEmail,
    adminNombre: data.adminNombre,
    tempPassword,
    databaseName,
    plan,
  }).catch(err => console.error('[EMAIL] New client admin email failed:', err));

  return { tenant, user, tempPassword };
}

/**
 * Flow "Agregar empresa" — un user existente (típicamente owner) agrega un
 * segundo RFC bajo su cuenta. A diferencia de `createTenant()`, NO crea un user
 * nuevo: el caller se vuelve owner de la empresa nueva vía TenantMembership.
 *
 * Sin trial por default — el check de trial por owner (fase 5) bloquearía
 * cualquier intento de re-activarlo. El owner contrata el plan desde la página
 * de suscripción del nuevo tenant tras esta llamada.
 */
export async function addTenantToOwner(data: {
  userId: string;
  nombre: string;
  rfc: string;
  plan?: DespachoPlan;
}) {
  const plan = data.plan || 'trial';
  const rfcUpper = data.rfc.toUpperCase();

  // Valida que el RFC no exista en el sistema
  const existingTenant = await prisma.tenant.findUnique({ where: { rfc: rfcUpper } });
  if (existingTenant) {
    throw new Error('Ya existe una empresa con ese RFC en el sistema');
  }

  // Valida que el user exista y esté activo
  const user = await prisma.user.findUnique({ where: { id: data.userId } });
  if (!user || !user.active) throw new Error('Usuario no encontrado');

  // 1. Provision BD dedicada
  const databaseName = await tenantDb.provisionDatabase(rfcUpper);

  // 1b. Register tenant database in Metabase (non-blocking, logs errors only)
  metabaseService.registerDatabase({
    nombre: data.nombre,
    dbName: databaseName,
  }).catch(err => console.error('[METABASE] Register failed:', err));

  // 2. Crea el tenant
  const tenant = await prisma.tenant.create({
    data: {
      nombre: data.nombre,
      rfc: rfcUpper,
      plan,
      databaseName,
    },
  });

  // 3. Crea membership del caller como owner
  const ownerRol = await prisma.rol.findUnique({ where: { nombre: 'owner' } });
  if (!ownerRol) throw new Error('Rol owner no encontrado');

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

  // 4. Subscripción pending (se activa al contratar un plan)
  await prisma.subscription.create({
    data: {
      tenantId: tenant.id,
      plan,
      status: 'pending',
      amount: 0, // el precio real se setea al contratar
      frequency: 'monthly',
    },
  });

  return { tenant };
}

/**
 * Lista detallada de tenants del user actual con estado de suscripción. Usado
 * por la página `/mis-empresas` — incluye plan, status, currentPeriodEnd,
 * pendingPlan si aplica.
 *
 * @param onlyOwner filtra a solo memberships donde isOwner=true. Default true:
 * un user contador que trabaja en empresa ajena NO la ve aquí, pero sí sus
 * propias empresas donde es owner. El header switcher usa un endpoint distinto.
 */
export async function getMyTenantsDetailed(userId: string, onlyOwner = true) {
  const memberships = await prisma.tenantMembership.findMany({
    where: { userId, active: true, ...(onlyOwner ? { isOwner: true } : {}) },
    include: {
      tenant: {
        include: {
          subscriptions: {
            orderBy: { createdAt: 'desc' },
            take: 1,
          },
        },
      },
      rol: { select: { nombre: true } },
    },
    orderBy: { joinedAt: 'asc' },
  });

  return memberships
    .filter(m => m.tenant.active)
    .map(m => {
      const sub = m.tenant.subscriptions[0] || null;
      return {
        tenantId: m.tenant.id,
        nombre: m.tenant.nombre,
        rfc: m.tenant.rfc,
        plan: m.tenant.plan,
        role: m.rol.nombre,
        isOwner: m.isOwner,
        trialEndsAt: m.tenant.trialEndsAt,
        subscription: sub ? {
          status: sub.status,
          plan: sub.plan,
          amount: sub.amount ? Number(sub.amount) : 0,
          frequency: sub.frequency,
          currentPeriodEnd: sub.currentPeriodEnd,
          pendingPlan: sub.pendingPlan,
          pendingEffectiveAt: sub.pendingEffectiveAt,
        } : null,
      };
    });
}

export async function updateTenant(id: string, data: {
  nombre?: string;
  rfc?: string;
  plan?: DespachoPlan;
  active?: boolean;
  amount?: number;
  firstPaymentDueAt?: string | null;
}) {
  const tenant = await prisma.tenant.update({
    where: { id },
    data: {
      ...(data.nombre && { nombre: data.nombre }),
      ...(data.rfc && { rfc: data.rfc.toUpperCase() }),
      ...(data.plan && { plan: data.plan }),
      ...(data.active !== undefined && { active: data.active }),
    },
    select: {
      id: true,
      nombre: true,
      rfc: true,
      plan: true,
      databaseName: true,
      active: true,
      createdAt: true,
    }
  });

  // Actualizar subscription del tenant (plan custom o cualquier plan con amount)
  if (data.amount !== undefined || data.firstPaymentDueAt !== undefined) {
    const subscription = await prisma.subscription.findFirst({
      where: { tenantId: id },
      orderBy: { createdAt: 'desc' },
    });
    if (subscription) {
      const updateData: any = {};
      if (data.amount !== undefined) {
        updateData.amount = data.amount;
      }
      if (data.firstPaymentDueAt !== undefined) {
        updateData.currentPeriodEnd = data.firstPaymentDueAt ? new Date(data.firstPaymentDueAt) : null;
      }
      await prisma.subscription.update({
        where: { id: subscription.id },
        data: updateData,
      });
    }
  }

  return tenant;
}

export async function getDatosFiscales(id: string) {
  return prisma.tenant.findUnique({
    where: { id },
    select: {
      codigoPostal: true,
      calle: true,
      numExterior: true,
      numInterior: true,
      colonia: true,
      ciudad: true,
      municipio: true,
      estado: true,
      telefono: true,
    },
  });
}

export async function updateDatosFiscales(id: string, data: {
  codigoPostal?: string;
  calle?: string;
  numExterior?: string;
  numInterior?: string;
  colonia?: string;
  ciudad?: string;
  municipio?: string;
  estado?: string;
  telefono?: string;
}) {
  return prisma.tenant.update({
    where: { id },
    data,
    select: {
      codigoPostal: true,
      calle: true,
      numExterior: true,
      numInterior: true,
      colonia: true,
      ciudad: true,
      municipio: true,
      estado: true,
      telefono: true,
    },
  });
}

/**
 * Preferencias de auto-facturación de pagos de suscripción.
 * Lee también los regímenes activos del tenant — útil para que la UI muestre
 * las opciones del dropdown "Régimen preferido" sin queries adicionales.
 */
export async function getPreferenciasFacturacion(id: string) {
  const tenant = await prisma.tenant.findUnique({
    where: { id },
    select: {
      factPreferencia: true,
      factUsoCfdi: true,
      factRegimenPreferido: true,
      regimenesActivos: {
        select: { regimen: { select: { clave: true, descripcion: true } } },
        orderBy: { createdAt: 'asc' },
      },
    },
  });
  if (!tenant) return null;
  return {
    factPreferencia: tenant.factPreferencia,
    factUsoCfdi: tenant.factUsoCfdi,
    factRegimenPreferido: tenant.factRegimenPreferido,
    regimenesActivos: tenant.regimenesActivos.map(ra => ra.regimen),
  };
}

export async function updatePreferenciasFacturacion(id: string, data: {
  factPreferencia?: 'publico_general' | 'mis_datos';
  factUsoCfdi?: string;
  factRegimenPreferido?: string | null;
}) {
  return prisma.tenant.update({
    where: { id },
    data,
    select: {
      factPreferencia: true,
      factUsoCfdi: true,
      factRegimenPreferido: true,
    },
  });
}

export async function deleteTenant(id: string) {
  const tenant = await prisma.tenant.findUnique({
    where: { id },
    select: { databaseName: true },
  });

  // Soft-delete the tenant record
  await prisma.tenant.update({
    where: { id },
    data: { active: false }
  });

  // Soft-delete the database (rename with _deleted_ suffix)
  if (tenant) {
    await tenantDb.deprovisionDatabase(tenant.databaseName);
    tenantDb.invalidatePool(id);
    // Remove from Metabase (non-blocking)
    metabaseService.deleteDatabase(tenant.databaseName).catch(err =>
      console.error('[METABASE] Delete failed:', err)
    );
  }
}
