'use server';

import { z } from 'zod';
import { and, eq } from 'drizzle-orm';
import { redirect } from 'next/navigation';
import { revalidatePath } from 'next/cache';
import { db } from '@/db';
import { leads, leadFotos, leadNotas, leadPipelineEventos } from '@/db/schema';
import type { NewLeadFoto, NewLeadNota } from '@/db/schema';
import { getTenantBySlug } from '@/lib/funnel/public-queries';
import { getTenantPerfilById } from '@/db/tenant-queries';
import { procesarLead } from '@/lib/funnel/orchestrator';
import { señalarPerfilCompleto } from '@/lib/funnel/perfil';
import { iniciarLlamadaSaliente, construirVariablesLlamada } from '@/lib/voice/retell';
import { iniciarConversacionWhatsapp } from '@/lib/webhooks';
import { enviarEnlaceFormulario } from '@/lib/email/mailer';
import { resolveTheme } from '@/lib/funnel/themes';
import { env } from '@/lib/env';

const MAX_ZONAS = 6;
const MAX_FOTOS_ZONA = 6;
const MAX_FOTO_BYTES = 6 * 1024 * 1024; // 6 MB por foto

const crearLeadSchema = z.object({
  nombre: z.string().trim().min(2, 'El nombre es obligatorio'),
  email: z.string().trim().email('Introduce un email válido'),
  telefono: z
    .string()
    .trim()
    .regex(/^[+\d\s\-().]{7,20}$/, 'Introduce un teléfono válido'),
  consentPrivacidad: z.boolean(),
  consentContratacion: z.boolean(),
});

export type CrearLeadInput = z.input<typeof crearLeadSchema>;
export type CrearLeadResult =
  | { ok: true; leadId: string }
  | { ok: false; error: string };

export async function crearLead(slug: string, input: CrearLeadInput): Promise<CrearLeadResult> {
  const parsed = crearLeadSchema.safeParse(input);
  if (!parsed.success) {
    return { ok: false, error: parsed.error.issues[0]?.message ?? 'Datos inválidos.' };
  }
  const data = parsed.data;

  // RF-LEG-01: los dos consentimientos son obligatorios para iniciar el funnel.
  if (!data.consentPrivacidad || !data.consentContratacion) {
    return { ok: false, error: 'Debes aceptar la política de privacidad y las condiciones.' };
  }

  // El lead se atribuye al reformista dueño del funnel (slug de la URL pública).
  const tenant = await getTenantBySlug(slug);
  if (!tenant) {
    return { ok: false, error: 'No hemos podido identificar al reformista. Recarga la página.' };
  }

  const [lead] = await db
    .insert(leads)
    .values({
      tenantId: tenant.id,
      nombre: data.nombre,
      email: data.email,
      telefono: data.telefono,
      consentPrivacidad: data.consentPrivacidad,
      consentContratacion: data.consentContratacion,
      pipelineStage: 'form_completado',
      estado: 'nuevo',
    })
    .returning({ id: leads.id });

  await db.insert(leadPipelineEventos).values({
    leadId: lead.id,
    stage: 'form_completado',
    metadata: { origen: 'landing' },
  });

  return { ok: true, leadId: lead.id };
}

const TIPOS = ['cocina', 'bano', 'salon', 'comedor', 'integral', 'otro'] as const;
const CALIDADES = ['basica', 'media', 'premium'] as const;

async function fileToDataUri(file: File): Promise<string | null> {
  if (file.size === 0 || file.size > MAX_FOTO_BYTES) return null;
  if (!file.type.startsWith('image/')) return null;
  const buffer = Buffer.from(await file.arrayBuffer());
  return `data:${file.type};base64,${buffer.toString('base64')}`;
}

const CALIDAD_RANK: Record<(typeof CALIDADES)[number], number> = { basica: 0, media: 1, premium: 2 };

type ZonaParseada = {
  tipo: (typeof TIPOS)[number];
  m2: number | null;
  calidad: (typeof CALIDADES)[number];
  notas: string | null;
  fotos: string[]; // data URIs
};

// Lee las zonas del FormData (campos zona-<i>-tipo / -m2 / -calidad / -notas / -fotos).
async function parsearZonas(formData: FormData): Promise<ZonaParseada[]> {
  const count = Math.min(Number(formData.get('zonasCount')) || 0, MAX_ZONAS);
  const zonas: ZonaParseada[] = [];
  for (let i = 0; i < count; i++) {
    const tipoRaw = String(formData.get(`zona-${i}-tipo`) ?? '');
    const calidadRaw = String(formData.get(`zona-${i}-calidad`) ?? '');
    const m2Raw = Number(formData.get(`zona-${i}-m2`));
    const tipo = (TIPOS as readonly string[]).includes(tipoRaw)
      ? (tipoRaw as (typeof TIPOS)[number])
      : 'otro';
    const calidad = (CALIDADES as readonly string[]).includes(calidadRaw)
      ? (calidadRaw as (typeof CALIDADES)[number])
      : 'media';
    const m2 = Number.isFinite(m2Raw) && m2Raw > 0 ? m2Raw : null;
    const notas = String(formData.get(`zona-${i}-notas`) ?? '').trim() || null;

    const archivos = formData
      .getAll(`zona-${i}-fotos`)
      .filter((f): f is File => f instanceof File)
      .slice(0, MAX_FOTOS_ZONA);
    const fotos: string[] = [];
    for (const file of archivos) {
      const uri = await fileToDataUri(file);
      if (uri) fotos.push(uri);
    }
    zonas.push({ tipo, m2, calidad, notas, fotos });
  }
  return zonas;
}

// Paso 2 (canal formulario): el cliente describe la reforma zona por zona y sube fotos.
// Guardamos fotos (momento 'antes', etiquetadas por zona) y notas como data en lead_notas;
// agregamos los campos del lead para calcular el presupuesto orientativo al instante con el motor
// actual, y señalamos "perfil completo" al flujo externo para que genere los renders "después".
export async function guardarDetallesYFotos(leadId: string, formData: FormData): Promise<void> {
  const [lead] = await db.select().from(leads).where(eq(leads.id, leadId)).limit(1);
  if (!lead) throw new Error('Solicitud no encontrada.');
  const tenantId = lead.tenantId;

  const provincia = String(formData.get('provincia') ?? '').trim() || null;
  const urgenciaRaw = String(formData.get('urgencia') ?? '');
  const urgencia = (['alta', 'media', 'baja'] as const).includes(urgenciaRaw as 'alta')
    ? (urgenciaRaw as 'alta' | 'media' | 'baja')
    : null;
  const targetEuros = Number(formData.get('presupuestoTarget'));
  const presupuestoTarget =
    Number.isFinite(targetEuros) && targetEuros > 0 ? Math.round(targetEuros * 100) : null;
  const estructural = formData.get('estructural') === 'on';
  const anteriorA2000 = formData.get('anteriorA2000') === 'on';
  const cambioDistribucion = formData.get('cambioDistribucion') === 'on';

  let zonas = await parsearZonas(formData);
  if (zonas.length === 0) {
    zonas = [{ tipo: 'otro', m2: null, calidad: 'media', notas: null, fotos: [] }];
  }

  // Inserta fotos (antes, por zona) y notas (por zona) en la estructura del lead.
  const fotoRows: NewLeadFoto[] = [];
  const notaRows: NewLeadNota[] = [];
  let orden = 0;
  for (const z of zonas) {
    for (const url of z.fotos) {
      fotoRows.push({ leadId, url, momento: 'antes', zona: z.tipo, orden: orden++ });
    }
    if (z.notas) notaRows.push({ leadId, texto: z.notas, zona: z.tipo, origen: 'funnel' });
  }
  if (fotoRows.length > 0) await db.insert(leadFotos).values(fotoRows);
  if (notaRows.length > 0) await db.insert(leadNotas).values(notaRows);

  // Agregado para el motor de presupuesto (multi-zona "de verdad" = F1.5): m² suma, tipo único
  // o 'integral' si hay varias zonas, calidad la más alta, y tasteText con las notas concatenadas.
  const tiposUnicos = Array.from(new Set(zonas.map((z) => z.tipo)));
  const tipoReforma = tiposUnicos.length === 1 ? tiposUnicos[0] : 'integral';
  const m2Total = zonas.reduce((s, z) => s + (z.m2 ?? 0), 0);
  const m2Suelo = m2Total > 0 ? m2Total : null;
  const calidadGlobal = zonas.reduce<(typeof CALIDADES)[number]>(
    (best, z) => (CALIDAD_RANK[z.calidad] > CALIDAD_RANK[best] ? z.calidad : best),
    'basica',
  );
  const tasteText =
    zonas
      .filter((z) => z.notas)
      .map((z) => `${z.tipo}: ${z.notas}`)
      .join('\n') || null;

  await db
    .update(leads)
    .set({
      tipoReforma,
      calidadGlobal,
      m2Suelo,
      provincia,
      urgencia,
      presupuestoTarget,
      estructural,
      anteriorA2000,
      cambioDistribucion,
      tasteText,
      pipelineStage: 'fotos_subidas',
      updatedAt: new Date(),
    })
    .where(and(eq(leads.id, leadId), eq(leads.tenantId, tenantId)));

  await db.insert(leadPipelineEventos).values({
    leadId,
    stage: 'fotos_subidas',
    metadata: { fotos: fotoRows.length, notas: notaRows.length, zonas: zonas.length },
  });

  // Presupuesto orientativo inmediato (motor actual). La rama de WhatsApp queda simulada.
  await procesarLead(leadId);

  // Señala al flujo externo que el perfil está listo para generar los renders "después".
  await señalarPerfilCompleto(leadId);

  revalidatePath('/panel');
  redirect(`/solicitud/${leadId}/estado`);
}

// Canal llamada: el cliente pide que le llamen ahora o programa la llamada. "Ahora" dispara la
// llamada saliente de Retell; "programar" registra la fecha y la señala (el dialing en hora lo
// hace el flujo externo, la app no monta cron). Best-effort.
export async function pedirLlamada(
  leadId: string,
  cuando: 'ahora' | string,
): Promise<{ ok: boolean; programada?: string }> {
  const [lead] = await db.select().from(leads).where(eq(leads.id, leadId)).limit(1);
  if (!lead) return { ok: false };
  const tenant = await getTenantPerfilById(lead.tenantId);

  // Mandamos el email con el enlace para subir fotos: el agente se lo recuerda en la llamada
  // ("te enviamos un email con un enlace"). Best-effort, no bloquea la llamada.
  await enviarEnlaceFormularioEmail(leadId);

  if (cuando === 'ahora') {
    const llamada = await iniciarLlamadaSaliente({
      telefono: lead.telefono,
      variables: construirVariablesLlamada({ nombreEmpresa: tenant.nombreEmpresa }, lead),
      leadId,
    });
    await db.insert(leadPipelineEventos).values({
      leadId,
      stage: 'prellamada_enviada',
      metadata: { via: 'llamada', cuando: 'ahora', real: Boolean(llamada), simulado: !llamada },
    });
    return { ok: true };
  }

  const fecha = new Date(cuando);
  const programadaAt = Number.isNaN(fecha.getTime()) ? null : fecha.toISOString();
  await db.insert(leadPipelineEventos).values({
    leadId,
    stage: 'prellamada_enviada',
    metadata: { via: 'llamada', cuando: 'programada', programadaAt },
  });
  return { ok: true, programada: programadaAt ?? undefined };
}

// Canal llamada: envía al cliente un email con el enlace a su formulario para subir las imágenes.
export async function enviarEnlaceFormularioEmail(leadId: string): Promise<boolean> {
  const [lead] = await db.select().from(leads).where(eq(leads.id, leadId)).limit(1);
  if (!lead) return false;
  const tenant = await getTenantPerfilById(lead.tenantId);
  const theme = resolveTheme(tenant.themePreset, tenant.themeColor);
  const url = `${env.APP_URL ?? ''}/solicitud/${leadId}/formulario`;
  return enviarEnlaceFormulario({
    to: lead.email,
    nombre: lead.nombre,
    empresa: tenant.nombreEmpresa,
    url,
    brand: {
      primary: theme.primary,
      primaryDark: theme.primaryDark,
      contrast: theme.contrast,
      logoUrl: tenant.logoUrl,
    },
  });
}

// Canal WhatsApp: arranca la conversación con el lead a través del flujo externo (que manda el
// primer mensaje a su teléfono) y deja traza. El cliente confirma luego en la UI.
export async function iniciarWhatsapp(leadId: string): Promise<{ ok: boolean; telefono: string }> {
  const [lead] = await db.select().from(leads).where(eq(leads.id, leadId)).limit(1);
  if (!lead) return { ok: false, telefono: '' };
  const tenant = await getTenantPerfilById(lead.tenantId);
  const ok = await iniciarConversacionWhatsapp({
    leadId,
    telefono: lead.telefono,
    nombre: lead.nombre,
    empresa: tenant.nombreEmpresa,
  });
  return { ok, telefono: lead.telefono };
}

// Canal WhatsApp: el cliente confirma que ha recibido el mensaje; seguimos por WhatsApp.
export async function confirmarWhatsapp(leadId: string): Promise<{ ok: boolean }> {
  const [lead] = await db.select({ id: leads.id }).from(leads).where(eq(leads.id, leadId)).limit(1);
  if (!lead) return { ok: false };
  await db.insert(leadPipelineEventos).values({
    leadId,
    stage: 'prellamada_enviada',
    metadata: { via: 'whatsapp', confirmado: true },
  });
  return { ok: true };
}
