<?php

include_once "lib/Curl.slm";
include_once "lib/SSPBootstrapTable.slm";
include_once "lib/SSP.slm";
include_once "email.slm";

//======================================================================================================================
// Elasticsearch Connection Functions
//======================================================================================================================

/**
 * Gets the Elasticsearch server URL based on environment.
 *
 * @return string The Elasticsearch server URL
 */
function getEsServer(): string
{
    return isProd() ? ES_SERVER : DEV_ES_SERVER;
}

/**
 * Gets Elasticsearch credentials based on environment.
 *
 * @return string Credentials in "username:password" format
 */
function getEsCredentials(): string
{
    return isProd() ? ES_USERNAME . ':' . ES_PASSWORD : DEV_ES_USERNAME . ':' . DEV_ES_PASSWORD;
}

/**
 * Gets the Elasticsearch index based on environment.
 *
 * @return string The Elasticsearch index name
 */
function getEsIndex(): string
{
    return isProd() ? ES_INDEX : DEV_ES_INDEX;
}

/**
 * Gets the Elasticsearch log index.
 *
 * @return string The log index name
 */
function getEsIndexLog(): string
{
    return isProd() ? ES_INDEX_LOG : DEV_ES_INDEX_LOG;
}

/**
 * Gets the sessions index name.
 *
 * @return string The sessions index name
 */
function getEsIndexSessions(): string
{
    return "sessions";
}

/**
 * Prepares a cURL handle for HTTP requests.
 *
 * @param string $qUrl The URL to request
 * @param string $json JSON data to send in the body
 * @param string $verb HTTP verb to use
 * @return CurlHandle|bool Returns a cURL handle on success, or false on error
 * @throws Exception
 */
function prepare_cUrl(string $qUrl, string $json = "", string $verb = ""): CurlHandle|bool
{
    $ch = curl_init($qUrl);

    if (!$ch) {
        throw new Exception('Could not initialize cURL session');
    }

    curl_setopt($ch, CURLOPT_USERPWD, getEsCredentials());
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_USERAGENT, "SLM_ES_CONTROL V1.0");

    if ($json !== "") {
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
        curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Content-Length: ' . strlen($json)]);
    }

    if ($verb !== "") {
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $verb);
    }

    return $ch;
}

//======================================================================================================================
// General Functions
//======================================================================================================================

/**
 * Converts search fields into an Elasticsearch query.
 *
 * @param array $fields Fields to search
 * @param string $searchTerm Search term
 * @return string The query string
 */
function convertFields(array $fields, string $searchTerm): string
{
    $query = "";
    foreach ($fields as $field) {
        if (str_contains($field, 'keyword')) {
            $explode = explode('.', $field);
            $field = $explode[0];
            $keyword = $explode[1];
            $query .= "OR($field.$keyword:$searchTerm)OR($field:*$searchTerm)OR($field:$searchTerm*)";
        } else {
            $query .= "OR($field:$searchTerm)OR($field:*$searchTerm)OR($field:$searchTerm*)";
        }
    }
    return substr($query, 2);
}

/**
 * Gets the current timestamp in milliseconds.
 *
 * @return float Current timestamp in milliseconds
 */
function getTimestamp(): float
{
    return round(microtime(true) * 1000);
}

//======================================================================================================================
// Elasticsearch Interaction Functions
//======================================================================================================================

/**
 * Executes an HTTP request to Elasticsearch.
 *
 * @param string $url The request URL
 * @param string $json JSON data to send
 * @param string $verb HTTP verb
 * @return bool|string The response or false on error
 * @throws InvalidArgumentException If verb is invalid
 * @throws Exception If cURL execution fails
 */
function es_execute(string $url, string $json = "", string $verb = ""): bool|string
{
    $valid_verbs = ["", "GET", "POST", "PUT", "DELETE"];
    if (!in_array($verb, $valid_verbs)) {
        throw new InvalidArgumentException("Invalid verb $verb. Must be one of: " . implode(", ", $valid_verbs));
    }

    $qUrl = getEsServer() . ((str_starts_with($url, "/")) ? "" : "/") . $url;
    $ch = prepare_cUrl($qUrl, $json, $verb);
    $response = curl_exec($ch);
    if (!$response && curl_error($ch)) {
        throw new Exception('cURL error: ' . curl_error($ch));
    }
    curl_close($ch);

    return $response;
}

/**
 * Creates a new object in Elasticsearch.
 *
 * @param array $data The object data
 * @param string $id Optional document ID
 * @param string $es_index Optional index name
 * @return array The response from Elasticsearch
 * @throws Exception
 */
function setNewObject(array $data, string $id = "", string $es_index = ""): array
{
    if (empty($es_index)) {
        $es_index = getEsIndex();
    }

    if (!isset($data["slm"]["timestamp"])) {
        $data["slm"]["timestamp"] = getTimestamp();
    }

    return json_decode(es_execute("/$es_index/_doc/$id?refresh=true", json_encode($data)), true);
}

/**
 * Updates an object by ID.
 *
 * @param string $id Document ID
 * @param array $data Data to update
 * @param string $es_index Optional index name
 * @return array The update response
 * @throws Exception
 */
function updateObjectByID(string $id, array $data, string $es_index = ""): array
{
    if (empty($es_index)) {
        $es_index = getEsIndex();
    }

    if (array_key_exists('script', $data)) {
        $update = $data;
    } else {
        $update["doc"] = $data;
        $update["detect_noop"] = false;
    }

    return @json_decode(es_execute("/$es_index/_update/$id?refresh=true", json_encode($update)), true);
}

/**
 * Gets an object by ID.
 *
 * @param string $id Document ID
 * @param string $ext Additional query parameters
 * @param string $es_index Optional index name
 * @return false|array The document data or false if not found
 * @throws Exception
 */
function getObjectById(string $id, string $ext = "", string $es_index = ""): false|array
{
    if (empty($es_index)) {
        $es_index = getEsIndex();
    }

    if (empty($id))
        return false;

    if (!empty($ext))
        $id .= "?$ext";

    $result = @json_decode(es_execute("/$es_index/_doc/$id"), true);
    if (!isset($result["found"]) || $result["found"] !== true)
        return false;

    return $result["_source"];
}

/**
 * Gets an object by field value.
 *
 * @param string $type Document type (slm.type)
 * @param string $field Field to search
 * @param string $value Value to match
 * @param string $es_index Optional index name
 * @return array The document with ID merged, or empty array if not found
 * @throws Exception
 */
function getObjectWithValue(string $type, string $field, string $value, string $es_index = ""): array
{
    if (empty($es_index)) {
        $es_index = getEsIndex();
    }

    if (empty($value)) {
        return [];
    }

    // Construir query DSL
    $query = [
        'query' => [
            'bool' => [
                'must' => [
                    ['term' => ['slm.type' => $type]]
                ]
            ]
        ],
        'size' => 1
    ];

    // Determinar el tipo de match según el campo
    if (str_contains($field, 'keyword')) {
        $query['query']['bool']['must'][] = ['term' => [$field => $value]];
    } else {
        $query['query']['bool']['must'][] = ['match' => [$field => $value]];
    }

    $request = json_decode(es_execute("/$es_index/_search", json_encode($query)), true);

    if (isset($request['error'])) {
        return [];
    }

    if ($request['hits']['total']['value'] > 0) {
        $request = $request['hits']['hits'][0];
        return array_merge(['id' => $request['_id']], $request['_source']);
    }

    return [];
}
