<?php
// ELSA V2 
// Diego Quisi, Gabriela Cajamarca

namespace local_elsa;

use Exception;

/**
 * Class ElsaServer
 *
 * @package    local_elsa
 * @copyright  2024 DIEGO QUISI <dquisip@gmail.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class ElsaServer {
    private $serverUrl;
    private $token;
    public $scopes;

    /**
     * Constructor.
     * Inicializa las variables de configuración.
     */
    public function __construct() {
        $this->serverUrl = get_config('local_elsa', 'server_url');
        $this->token = get_config('local_elsa', 'token');
    }

    /**
     * Envía datos al servidor.
     *
     * @param array $data Datos a enviar.
     * @param string $action Acción a realizar.
     * @param string $type Tipo de solicitud (post o put).
     * @return mixed Respuesta del servidor.
     */
    public function sendToServer($data = null, $action = null, $type = "post", $collectionName = null, $retries = 2) {
        try {
            $cleanUrl = preg_replace('#([^:])//+#', '\\1/', $this->serverUrl . $action);
            if ($type == "delete" && $collectionName) {
                $cleanUrl .= "/" . urlencode($collectionName);
            }
            $headers = [
                'Accept: application/json',
                'Authorization: Bearer ' . $this->token
            ];
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $cleanUrl);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($type));
            if ($type == "post" || $type == "put") {
                if($action == '/api/v1/ai/upload'){
                    $multipartData = [];
                    if (isset($data['files']) && !empty($data['files'])) {
                        $file = array_shift($data['files']);
                        unset($data['files']);
                        $temp_file = tempnam(sys_get_temp_dir(), 'upload_');
                        file_put_contents($temp_file, $file['contents']);
                        $multipartData['files'] = new \CURLFile($temp_file, $file['type'], $file['filename']);
                    }
                    $multipartData['upload_input'] = json_encode($data);
                    curl_setopt($ch, CURLOPT_POSTFIELDS, $multipartData);
                    curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge($headers, ['Content-Type: multipart/form-data']));
                } else {
                    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
                    curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge($headers, ['Content-Type: application/json']));
                }
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($type));
            }
            $response = curl_exec($ch);
            $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            if (curl_errno($ch)) {
                throw new Exception(curl_error($ch));
            }
            curl_close($ch);
            if ($httpcode >= 200 && $httpcode < 300) {
                $decodedResponse = json_decode($response, true);
                if (json_last_error() === JSON_ERROR_NONE) {
                    return json_decode($response);
                } else {
                    return $response;
                }
	    } elseif ($httpcode == 429 || $httpcode == 413) {
	    	return json_decode($response)->detail;
	    } else {
                throw new Exception("HTTP Error: " . $httpcode);
            }
        } catch (Exception $e) {
            if ($retries > 0) {
                error_log('Exception during server request: ' . $e->getMessage());
                return $this->sendToServer($data, $action, $type, $collectionName, $retries - 1);
            } else {
                error_log('Exception during server request: ' . $e->getMessage());
                return null;
            }
        }
    }

    /**
     * Procesa errores y obtiene detalles del mensaje de error.
     *
     * @param Exception $e Excepción capturada.
     * @return string Detalle del error.
     */
    public function processError($e){
        $pattern = '/"detail":"([^"]*)"/';
        if (preg_match($pattern, $e->getMessage(), $matches)) {
            $error_detail = $matches[1];
            return $error_detail;
        } else {
            return get_string('errorServiceLLM', 'local_elsa');
        }
    }
    
    /**
     * Decodifica una cadena base64url.
     *
     * @param string $data Datos codificados.
     * @return string Datos decodificados.
     */
    public function base64url_decode($data) {
        try {
            $padding = 4 - (strlen($data) % 4);
            if ($padding < 4) {
                $data .= str_repeat('=', $padding);
            }
            return base64_decode(strtr($data, '-_', '+/'));
        } catch (Exception $e) {
            error_log('Excepción al base64 el JWT: ',  $e->getMessage(), "\n");
        }
        return null;
    }

    /**
     * Codifica una cadena a base64url.
     *
     * @param string $data Datos a codificar.
     * @return string Datos codificados.
     */
    public function base64url_encode($data) {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }

    /**
     * Genera una URL para un iframe de Metabase.
     *
     * @param array $payload Datos a incluir en el payload.
     * @return string URL generada.
     */
    public function getUrlMetabase($payload, $institution){
        $header = json_encode([
            'alg' => 'HS256',
            'typ' => 'JWT'
        ]);
        $base64UrlHeader = $this->base64url_encode($header);
        $base64UrlPayload = $this->base64url_encode($payload);
        $signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $institution["report_token"], true);
        $base64UrlSignature = $this->base64url_encode($signature);
        $jwt = $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature;
        return $institution["report_url"] . "/embed/dashboard/" . $jwt . "#theme=transparent&bordered=false&titled=false";
    }

    /**
     * Valida el token verificando su expiración.
     *
     * @return institucion si el token es válido, null si no lo es.
     */
    public function getValidateToken(): ?array{
        return array('name' => "UPS");
        if (session_status() == PHP_SESSION_NONE) {
            session_start();
        }
        if (isset($_SESSION['institution_token']) &&
            isset($_SESSION['token_end_date']) &&
            isset($_SESSION['last_updated']) &&
            $_SESSION['token_end_date'] > time() &&
            (time() - $_SESSION['last_updated'] < 1080)) {
            return array(
                'name' => $_SESSION['institution_token']['name'],
                'description' => $_SESSION['institution_token']['description'],
                'city' => $_SESSION['institution_token']['city'],
                'contact_name' => $_SESSION['institution_token']['contact_name'],
                'contact_email' => $_SESSION['institution_token']['contact_email'],
                'contact_mobile' => $_SESSION['institution_token']['contact_mobile'],
                'query_limit' => $_SESSION['institution_token']['query_limit'],
                'storage_limit' => $_SESSION['institution_token']['storage_limit'],
                'token_scopes' => json_decode($_SESSION['institution_token']['token_scopes']),
                'report_url' => $_SESSION['institution_token']['report_url'],
                'report_token' => $_SESSION['institution_token']['report_token'],
                'domain' => $_SESSION['institution_token']['domain'],
                'id' => $_SESSION['institution_token']['id']
            );
        }
        $institution = $this->sendToServer(null, '/api/v1/auth/instance', 'get');
        if ($institution != null && strtotime($institution->token_end_date) > time()) {
            $_SESSION['institution_token'] = array(
                'name' => $institution->name,
                'description' => $institution->description,
                'city' => $institution->city,
                'contact_name' => $institution->contact_name,
                'contact_email' => $institution->contact_email,
                'contact_mobile' => $institution->contact_mobile,
                'query_limit' => $institution->query_limit,
                'storage_limit' => $institution->storage_limit,
                'token_scopes' => json_encode($institution->token_scopes),
                'report_url' =>$institution->report_url,
                'report_token' => $institution->report_token,
                'domain' => $institution->domain,
                'id' => $institution->id,
                'token_end_date' => $institution->token_end_date
            );
            $_SESSION['last_updated'] = time();
            $_SESSION['token_end_date'] = $institution->token_end_date;
            return $_SESSION['institution_token'];
        }
        return null;
    }

    /**
     * Obtiene el nombre del curso.
     *
     * @return string Nombre del curso.
     */
    public function getNameCourse() {
        global $COURSE;
        $institution = $this->getValidateToken();
        if($institution != null)
            return $institution['id'] . "_" . $COURSE->id;
        return $COURSE->id . "_Course_" . substr($COURSE->name, 0, 1);
    }

    public function sendMetrics($metrics){
        if($this->getValidateToken() != null){
            return $this->sendToServer($metrics, '/api/v1/metrics', 'post');
        }else {
            error_log("Error al enviar métricas invalido token ");
        }
        return null;
    }   
        
}
