<?php
if (count(get_included_files()) === 1) { http_response_code(403); exit('Forbidden'); }

/**
 * ValeMemoryService — memoria persistente y contexto del agente Vale.
 *
 * Tres responsabilidades:
 *
 * 1. MEMORIA (vale_memory)
 *    Vale escribe aquí lo que aprende de cada negocio. Key-value con JSON.
 *    Persiste entre sesiones — es la "mente" de Vale sobre cada cliente.
 *
 * 2. HALLAZGOS Y RECOMENDACIONES (vale_findings, vale_recommendations, vale_actions)
 *    Lo que Vale detectó, lo que propuso hacer, lo que ejecutó.
 *
 * 3. CONTEXTO PARA PROMPTS
 *    buildContext() produce el bloque de texto que se inyecta en TODOS
 *    los system prompts. Así Vale "sabe" sobre el negocio antes de hablar.
 *
 * 4. CONSTITUCIÓN
 *    getConstitution() carga la versión activa de la DB; si falla, usa el
 *    fallback en config/vale_constitution.php.
 */
class ValeMemoryService {

    // ── Constitución ──────────────────────────────────────────────────────────

    /**
     * Carga la constitución activa de la DB.
     * Fallback al archivo hardcodeado si no hay versión activa o la DB falla.
     *
     * @return array{version:string, identidad:string, guardrails:string,
     *               reglas_citas:string, reglas_escalado:string,
     *               reglas_idioma:string, tono_default:string}
     */
    public static function getConstitution(?PDO $db): array {
        if ($db !== null) {
            try {
                $stmt = $db->query(
                    "SELECT version, identidad, guardrails, reglas_citas,
                            reglas_escalado, reglas_idioma, tono_default
                     FROM vale_constitution_versions
                     WHERE activa = 1 LIMIT 1"
                );
                $row = $stmt->fetch(PDO::FETCH_ASSOC);
                if ($row) return $row;
            } catch (Throwable $e) {
                error_log("[ValeMemoryService::getConstitution] DB error: " . $e->getMessage());
            }
        }

        // Fallback: archivo hardcodeado
        return require dirname(__DIR__, 2) . '/config/vale_constitution.php';
    }

    /**
     * Ensambla el system prompt de atención al cliente desde la constitución + datos del negocio.
     * Reemplaza buildSystemInstruction() en AgentService para centralizar la lógica.
     */
    public static function buildClientProfileContext(PDO $db, int $tenantId, int $contactId): string {
        if ($contactId <= 0) return '';
        try {
            $stmt = $db->prepare(
                "SELECT c.phone, cp.email, cp.birthday, cp.notes, cp.preferences
                 FROM contacts c
                 LEFT JOIN client_profiles cp ON cp.contact_id = c.id AND cp.tenant_id = c.tenant_id
                 WHERE c.id = :c AND c.tenant_id = :t LIMIT 1"
            );
            $stmt->execute([':c' => $contactId, ':t' => $tenantId]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
        } catch (\Throwable $e) {
            return '';
        }
        if (!$row) return '';

        $lines   = [];
        $missing = [];

        if (!empty($row['phone']))    $lines[] = "Teléfono: {$row['phone']}";
        else                          $missing[] = 'teléfono';

        if (!empty($row['email']))    $lines[] = "Email: {$row['email']}";
        else                          $missing[] = 'email';

        if (!empty($row['birthday'])) $lines[] = "Cumpleaños: {$row['birthday']}";
        if (!empty($row['notes']))    $lines[] = "Notas: " . trim($row['notes']);

        if (!empty($row['preferences'])) {
            $prefs = json_decode($row['preferences'], true) ?: [];
            foreach ($prefs as $k => $v) {
                $lines[] = ucfirst(str_replace('_', ' ', $k)) . ": $v";
            }
        }

        $block = "══ PERFIL DE ESTE CLIENTE (uso interno — NUNCA cites estos datos textualmente) ══\n";
        $block .= $lines ? implode("\n", $lines) . "\n" : "Sin datos de contacto todavía.\n";

        if (!empty($missing)) {
            $block .= "DATOS FALTANTES: " . implode(', ', $missing) . ". "
                    . "Si el cliente agenda una cita o la conversación lo permite naturalmente, pedíselos y guardá con actualizar_perfil_cliente.\n";
        }

        return $block . "══ FIN PERFIL ══";
    }

    public static function buildAttentionPrompt(
        PDO    $db,
        array  $tenant,
        array  $valeConfig,
        array  $services,
        string $memoryContext        = '',
        bool   $isFirstTurn          = false,
        string $clientProfileContext = ''
    ): string {
        $constitution = self::getConstitution($db);

        require_once __DIR__ . '/pais_helper.php';
        $pais   = $tenant['country'] ?? 'NI';
        $locale = $valeConfig['default_locale'] ?? 'es';

        // Fecha/hora actual del negocio
        $tz = PaisHelper::timezone($pais, $tenant['timezone'] ?: null);
        try {
            $now = new DateTime('now', new DateTimeZone($tz));
        } catch (\Throwable $e) {
            $now = new DateTime('now');
        }
        $fechaActual = $now->format('Y-m-d H:i') . " ($tz)";

        // Servicios
        $servicesText = '';
        foreach ($services as $s) {
            $meta = [];
            if (!empty($s['duration_min'])) $meta[] = $s['duration_min'] . ' min';
            if ($s['price_min'] !== null && $s['price_max'] !== null) {
                $sym = PaisHelper::simbolo($pais);
                $meta[] = $s['price_min'] == $s['price_max']
                    ? $sym . number_format((float)$s['price_min'], 2)
                    : $sym . number_format((float)$s['price_min'], 2) . '–' . $sym . number_format((float)$s['price_max'], 2);
            } elseif ($s['price_min'] !== null) {
                $meta[] = 'desde ' . PaisHelper::simbolo($pais) . number_format((float)$s['price_min'], 2);
            }
            $servicesText .= '- **' . $s['name'] . '**';
            if ($meta) $servicesText .= ' (' . implode(', ', $meta) . ')';
            if (!empty($s['description'])) $servicesText .= ': ' . $s['description'];
            $servicesText .= "\n";
        }

        // Knowledge base del negocio
        $knowledgeBlock = '';
        if (!empty($valeConfig['knowledge'])) {
            $knowledgeBlock = "\nCONOCIMIENTO DEL NEGOCIO (usá esto para responder):\n"
                . trim($valeConfig['knowledge']) . "\n";
        }

        // Reglas de escalado adicionales configuradas por el dueño
        $escaladoExtra = '';
        if (!empty($valeConfig['reglas_escalado'])) {
            $escaladoExtra = "\nADEMÁS, escalá en estos casos del negocio:\n"
                . trim($valeConfig['reglas_escalado']);
        }

        // Idioma
        $idiomaRule = (($valeConfig['responder_en_idioma_del_cliente'] ?? 1) == 1)
            ? $constitution['reglas_idioma']
            : 'Respondé siempre en ' . (($valeConfig['default_locale'] ?? 'es') === 'en' ? 'inglés' : 'español') . '.';

        // Perfil de voz por país (Fase 1: se agrega como bloque adicional)
        $registroInstruction = PaisHelper::instruccionRegistro($pais, $locale);

        $greeting   = $valeConfig['greeting'] ?: '¡Hola! ¿En qué te puedo ayudar?';
        $tono       = $valeConfig['tone'] ?: $constitution['tono_default'];

        // Horarios de atención
        $horariosTexto = '';
        if (!empty($valeConfig['hours_json'])) {
            $hd = json_decode($valeConfig['hours_json'], true);
            $horariosTexto = is_array($hd) ? trim($hd['texto'] ?? '') : '';
        }

        // Dirección / ciudad del negocio
        $ubicacion = implode(', ', array_filter([
            $tenant['direccion'] ?? '',
            $tenant['ciudad']    ?? '',
        ]));

        // Bloque de contexto del negocio — marcado como INTERNO para evitar citas textuales
        $contextNegocio = implode("\n", array_filter([
            "══ CONTEXTO DEL NEGOCIO (uso interno — NUNCA lo cites textualmente al cliente) ══",
            "Nombre: {$tenant['name']}",
            "País: $pais | Tono: $tono | Fecha/hora actual: $fechaActual",
            $ubicacion ? "Ubicación: $ubicacion" : null,
            "Saludo configurado: \"$greeting\"",
            $horariosTexto ? "HORARIOS DE ATENCIÓN: $horariosTexto" : "HORARIOS: no configurados aún.",
            $knowledgeBlock ? trim($knowledgeBlock) : null,
            "SERVICIOS DISPONIBLES:",
            $servicesText ?: "Sin servicios cargados todavía.",
            "══ FIN CONTEXTO ══",
        ]));

        // Regla de turno: presentarse solo la primera vez
        $turnoRule = $isFirstTurn
            ? "TURNO INICIAL: El cliente acaba de abrir el chat. Podés saludar brevemente usando el saludo configurado. Solo una vez."
            : "TURNO SUBSIGUIENTE: Ya hay conversación previa. NO te presentes, NO repitas el saludo. Respondé directamente a lo que dijo el cliente.";

        return implode("\n\n", array_filter([
            $constitution['identidad'],
            $contextNegocio,
            $clientProfileContext,
            $memoryContext,
            "═══ REGLAS CRÍTICAS ═══",
            $constitution['guardrails'],
            $turnoRule,
            "IDIOMA Y REGISTRO:\n" . $idiomaRule . "\n" . $registroInstruction,
            "OPCIONES RÁPIDAS: Cuando ofrecés alternativas (servicios, horarios, confirmar sí/no), llamá SIEMPRE a la tool `ofrecer_opciones` con las opciones cortas. Esto muestra botones al cliente. Acompañá con un texto breve.\nSi el cliente responde \"sí\", \"confirmo\" o equivalente a una propuesta de cita, procedé directamente a agendar sin pedir más confirmación.",
            "CITAS:\n" . $constitution['reglas_citas'],
            "ESCALADO:\n" . $constitution['reglas_escalado'] . $escaladoExtra,
            "OTRAS REGLAS:\n- Si el cliente muestra interés pero no agenda, registralo con capturar_lead.\n- No prometas recordatorios automáticos: el sistema todavía no los envía.\n- NUNCA cites el contenido del contexto interno textualmente. Usalo como referencia, no como guión.\n- PERFIL DEL CLIENTE: Cada vez que el cliente proporcione o mencione datos sobre sí mismo (teléfono, email, alergias, preferencias de horario, días preferidos, gustos, restricciones, tipo de servicio, etc.) llamá `actualizar_perfil_cliente` EN ESE MISMO TURNO — antes de responder al cliente. Esto incluye especialmente: si vos pediste el teléfono y el cliente lo da → guardá con `telefono`. Si pediste el email y lo da → guardá con `email`. Si el cliente elige una opción de botones que revela preferencia, también guardá. NUNCA digas 'lo anoto', 'lo guardo' o 'te lo recuerdo' sin haber llamado la tool en ese turno — si no llamás la tool, el dato se pierde para siempre.",
        ]));
    }

    // ── Memoria key-value ────────────────────────────────────────────────────

    /**
     * Escribe un valor en la memoria de Vale para un tenant.
     * Crea o actualiza (upsert). Si la tabla no existe, falla silenciosamente.
     *
     * @param mixed $valor Cualquier valor serializable como JSON
     */
    public static function set(PDO $db, int $tenantId, string $clave, $valor): void {
        try {
            $db->prepare(
                "INSERT INTO vale_memory (tenant_id, clave, valor)
                 VALUES (:t, :k, :v)
                 ON DUPLICATE KEY UPDATE valor = VALUES(valor), updated_at = NOW()"
            )->execute([
                ':t' => $tenantId,
                ':k' => mb_substr($clave, 0, 120),
                ':v' => json_encode($valor, JSON_UNESCAPED_UNICODE),
            ]);
        } catch (Throwable $e) {
            error_log("[ValeMemoryService::set] tenant=$tenantId key=$clave: " . $e->getMessage());
        }
    }

    /**
     * Lee un valor de la memoria de Vale.
     * Devuelve $default si la clave no existe o la tabla falta.
     */
    public static function get(PDO $db, int $tenantId, string $clave, $default = null) {
        try {
            $stmt = $db->prepare(
                "SELECT valor FROM vale_memory WHERE tenant_id = :t AND clave = :k LIMIT 1"
            );
            $stmt->execute([':t' => $tenantId, ':k' => $clave]);
            $row = $stmt->fetchColumn();
            if ($row === false) return $default;
            $decoded = json_decode($row, true);
            return $decoded !== null ? $decoded : $default;
        } catch (\Throwable $e) {
            return $default;
        }
    }

    /**
     * Devuelve toda la memoria de un tenant como array asociativo.
     */
    public static function getAll(PDO $db, int $tenantId): array {
        try {
            $stmt = $db->prepare(
                "SELECT clave, valor FROM vale_memory WHERE tenant_id = :t ORDER BY updated_at DESC"
            );
            $stmt->execute([':t' => $tenantId]);
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
            $out  = [];
            foreach ($rows as $r) {
                $out[$r['clave']] = json_decode($r['valor'], true);
            }
            return $out;
        } catch (\Throwable $e) {
            return [];
        }
    }

    // ── Hallazgos y recomendaciones ──────────────────────────────────────────

    /**
     * Guarda un array de hallazgos en vale_findings.
     * Formato: [['tipo'=>'...','severidad'=>2,'fuente'=>'google','detalle'=>'...'], ...]
     */
    public static function saveFindings(PDO $db, int $tenantId, array $findings): void {
        if (empty($findings)) return;
        try {
            $stmt = $db->prepare(
                "INSERT INTO vale_findings (tenant_id, tipo, severidad, fuente, detalle, evidencia_json)
                 VALUES (:t, :tipo, :sev, :fuente, :detalle, :ev)"
            );
            foreach ($findings as $f) {
                $stmt->execute([
                    ':t'      => $tenantId,
                    ':tipo'   => mb_substr($f['tipo']   ?? 'general',  0, 60),
                    ':sev'    => max(1, min(3, (int)($f['severidad'] ?? 2))),
                    ':fuente' => mb_substr($f['fuente'] ?? 'manual',   0, 40),
                    ':detalle'=> mb_substr($f['detalle'] ?? '',         0, 2000),
                    ':ev'     => isset($f['evidencia']) ? json_encode($f['evidencia'], JSON_UNESCAPED_UNICODE) : null,
                ]);
            }
        } catch (Throwable $e) {
            error_log("[ValeMemoryService::saveFindings] tenant=$tenantId: " . $e->getMessage());
        }
    }

    /**
     * Guarda recomendaciones en vale_recommendations.
     */
    public static function saveRecommendations(PDO $db, int $tenantId, array $recommendations): void {
        if (empty($recommendations)) return;
        try {
            $stmt = $db->prepare(
                "INSERT INTO vale_recommendations (tenant_id, titulo, accion, prioridad, tier_minimo)
                 VALUES (:t, :titulo, :accion, :prio, :tier)"
            );
            foreach ($recommendations as $r) {
                $stmt->execute([
                    ':t'     => $tenantId,
                    ':titulo'=> mb_substr($r['titulo'] ?? '',    0, 160),
                    ':accion'=> mb_substr($r['accion'] ?? '',    0, 2000),
                    ':prio'  => max(1, min(3, (int)($r['prioridad'] ?? 2))),
                    ':tier'  => mb_substr($r['tier_minimo'] ?? 'free', 0, 20),
                ]);
            }
        } catch (Throwable $e) {
            error_log("[ValeMemoryService::saveRecommendations] tenant=$tenantId: " . $e->getMessage());
        }
    }

    /**
     * Registra una acción ejecutada por Vale (herramienta + resultado).
     */
    public static function logAction(
        PDO    $db,
        int    $tenantId,
        string $herramienta,
        array  $payload,
        string $resultado = 'ok',
        string $detalle   = ''
    ): void {
        try {
            $db->prepare(
                "INSERT INTO vale_actions (tenant_id, herramienta, payload_json, resultado, detalle)
                 VALUES (:t, :h, :p, :r, :d)"
            )->execute([
                ':t' => $tenantId,
                ':h' => mb_substr($herramienta, 0, 60),
                ':p' => json_encode($payload, JSON_UNESCAPED_UNICODE),
                ':r' => in_array($resultado, ['ok','error','cancelada']) ? $resultado : 'ok',
                ':d' => mb_substr($detalle, 0, 1000),
            ]);
        } catch (Throwable $e) {
            error_log("[ValeMemoryService::logAction] tenant=$tenantId: " . $e->getMessage());
        }
    }

    // ── Context builder ──────────────────────────────────────────────────────

    /**
     * Construye el bloque de contexto para inyectar en el system prompt.
     *
     * Resume lo que Vale sabe del negocio: preferencias del dueño,
     * último tema tratado, hallazgos activos, recomendaciones pendientes.
     * Si no hay nada guardado, devuelve string vacío (silencioso).
     */
    public static function buildContext(PDO $db, int $tenantId): string {
        $memory = self::getAll($db, $tenantId);
        $lines  = [];

        // Preferencias del dueño
        if (!empty($memory['tono_preferido'])) {
            $lines[] = "El dueño prefiere tono " . $memory['tono_preferido'] . ".";
        }
        if (!empty($memory['registro'])) {
            $lines[] = "Registro de idioma: " . $memory['registro'] . ".";
        }

        // Último contacto
        if (!empty($memory['ultimo_tema'])) {
            $lines[] = "Último tema tratado con el dueño: " . $memory['ultimo_tema'] . ".";
        }
        if (!empty($memory['ultima_interaccion_at'])) {
            $lines[] = "Última interacción: " . $memory['ultima_interaccion_at'] . ".";
        }

        // Hallazgos activos sin resolver
        try {
            $stmt = $db->prepare(
                "SELECT tipo, severidad, detalle FROM vale_findings
                 WHERE tenant_id = :t AND resuelto = 0
                 ORDER BY severidad DESC LIMIT 3"
            );
            $stmt->execute([':t' => $tenantId]);
            $findings = $stmt->fetchAll(PDO::FETCH_ASSOC);
            if ($findings) {
                $lines[] = "Hallazgos activos del negocio:";
                foreach ($findings as $f) {
                    $sev = ['', 'baja', 'media', 'alta'][$f['severidad']] ?? '?';
                    $lines[] = "  - [{$sev}] {$f['detalle']}";
                }
            }
        } catch (\Throwable $e) { /* tabla puede no existir */ }

        // Recomendaciones pendientes
        try {
            $stmt = $db->prepare(
                "SELECT titulo FROM vale_recommendations
                 WHERE tenant_id = :t AND estado = 'nueva'
                 ORDER BY prioridad DESC LIMIT 2"
            );
            $stmt->execute([':t' => $tenantId]);
            $recs = $stmt->fetchAll(PDO::FETCH_COLUMN);
            if ($recs) {
                $lines[] = "Recomendaciones pendientes: " . implode(' | ', $recs) . ".";
            }
        } catch (\Throwable $e) { /* tabla puede no existir */ }

        // Datos extra en memoria libre
        $skipKeys = ['tono_preferido','registro','ultimo_tema','ultima_interaccion_at'];
        foreach ($memory as $clave => $valor) {
            if (in_array($clave, $skipKeys)) continue;
            if (!is_scalar($valor)) continue;
            $lines[] = ucfirst(str_replace('_', ' ', $clave)) . ": $valor.";
        }

        if (empty($lines)) return '';

        return "LO QUE VALE SABE DE ESTE NEGOCIO:\n" . implode("\n", $lines);
    }

    /**
     * Actualiza la memoria de Vale después de una conversación con el dueño.
     * Extrae y persiste el tono, último tema, etc.
     * Llamar al final de cada turno de chat del dueño.
     */
    public static function updateFromOwnerTurn(
        PDO    $db,
        int    $tenantId,
        string $userMessage,
        string $valeReply
    ): void {
        // Guardar último tema (resumen simple del mensaje del dueño)
        $tema = mb_substr(strip_tags($userMessage), 0, 120);
        if ($tema) {
            self::set($db, $tenantId, 'ultimo_tema', $tema);
            self::set($db, $tenantId, 'ultima_interaccion_at', date('Y-m-d H:i'));
        }

        // Detectar y guardar registro (voseo vs tuteo)
        if (preg_match('/\b(sos|tenés|querés|podés|venís|salís)\b/u', $userMessage)) {
            self::set($db, $tenantId, 'registro', 'voseo');
        } elseif (preg_match('/\b(eres|tienes|quieres|puedes|vienes|sales)\b/u', $userMessage)) {
            self::set($db, $tenantId, 'registro', 'tuteo');
        }
    }
}
