<?php
namespace GlpiPlugin\Wbstore;

if (!defined('GLPI_ROOT')) {
   die("Sorry. You can't access this file directly");
}

class Utils {

   public static function uuidv4(): string {
      $data = random_bytes(16);
      $data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
      $data[8] = chr((ord($data[8]) & 0x3f) | 0x80);
      return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
   }

   public static function currentDomain(): string {
      // Use GLPI base URL if available, fallback to HTTP_HOST
      $host = $_SERVER['HTTP_HOST'] ?? '';
      $host = preg_replace('/:\d+$/', '', $host);
      return strtolower(trim($host));
   }

   public static function pluginsRoot(): string {
      // GLPI 10 plugins directory
      // We try in order:
      // 1) GLPI_MARKETPLACE_DIR/plugins (if defined)
      // 2) GLPI_ROOT/plugins
      if (defined('GLPI_MARKETPLACE_DIR')) {
         $p = rtrim(GLPI_MARKETPLACE_DIR, '/\\') . DIRECTORY_SEPARATOR . 'plugins';
         if (is_dir($p)) {
            return $p;
         }
      }
      return rtrim(GLPI_ROOT, '/\\') . DIRECTORY_SEPARATOR . 'plugins';
   }

   public static function tmpFile(string $prefix, string $ext = 'tmp'): string {
      $dir = defined('GLPI_TMP_DIR') ? GLPI_TMP_DIR : sys_get_temp_dir();
      if (!is_dir($dir)) {
         @mkdir($dir, 0775, true);
      }
      $name = $prefix . '_' . bin2hex(random_bytes(8)) . '.' . $ext;
      return rtrim($dir, '/\\') . DIRECTORY_SEPARATOR . $name;
   }

   public static function sha256File(string $path): string {
      return hash_file('sha256', $path);
   }

   public static function jsonDecode(string $body): array {
      $data = json_decode($body, true);
      if (!is_array($data)) {
         return [];
      }
      return $data;
   }

   public static function ensureDir(string $dir): bool {
      if (is_dir($dir)) {
         return is_writable($dir);
      }
      if (@mkdir($dir, 0775, true)) {
         return true;
      }
      return false;
   }

   public static function rrmdir(string $dir): void {
      if (!is_dir($dir)) return;
      $items = new \RecursiveIteratorIterator(
         new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS),
         \RecursiveIteratorIterator::CHILD_FIRST
      );
      foreach ($items as $item) {
         if ($item->isDir()) {
            @rmdir($item->getPathname());
         } else {
            @unlink($item->getPathname());
         }
      }
      @rmdir($dir);
   }

   /**
    * Verifica se um plugin está instalado/ativado pelo diretório.
    * (Sem depender de banco, mas tenta usar a classe Plugin quando disponível.)
    */
   public static function pluginStatus(string $pluginDir): array {
      $pluginDir = trim($pluginDir);
      if ($pluginDir === '') {
         return ['installed' => false, 'activated' => false];
      }

      $root = rtrim(self::pluginsRoot(), '/\\') . DIRECTORY_SEPARATOR . $pluginDir;
      $installed = is_dir($root) && is_file($root . DIRECTORY_SEPARATOR . 'setup.php');

      $activated = false;
      if ($installed && class_exists('Plugin') && method_exists('Plugin', 'isActivated')) {
         try {
            $activated = (bool)\Plugin::isActivated($pluginDir);
         } catch (\Throwable $e) {
            $activated = false;
         }
      }

      return ['installed' => $installed, 'activated' => $activated];
   }


   public static function glpiUrl(): string {
      global $CFG_GLPI;
      if (isset($CFG_GLPI['url_base']) && is_string($CFG_GLPI['url_base']) && $CFG_GLPI['url_base'] !== '') {
         return (string)$CFG_GLPI['url_base'];
      }
      // fallback
      $proto = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
      $host = $_SERVER['HTTP_HOST'] ?? '';
      $base = rtrim(dirname($_SERVER['SCRIPT_NAME'] ?? '/'), '/\\');
      return $proto . '://' . $host . $base;
   }

   public static function httpPostJson(string $url, array $payload, bool $verifySsl = true, int $timeout = 20): array {
      $body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
      if (!is_string($body)) {
         $body = '{}';
      }

      // cURL
      if (function_exists('curl_init')) {
         $ch = curl_init();
         curl_setopt($ch, CURLOPT_URL, $url);
         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
         curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
         curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, min(10, $timeout));
         curl_setopt($ch, CURLOPT_POST, true);
         curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
         curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'Accept: application/json'
         ]);
         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verifySsl);
         curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifySsl ? 2 : 0);
         $resp = curl_exec($ch);
         $err  = curl_error($ch);
         $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
         if (defined('PHP_VERSION_ID') && PHP_VERSION_ID < 80500) {
            $canClose = false;
            if (is_resource($ch)) {
               $canClose = true;
            } elseif (class_exists('CurlHandle') && $ch instanceof \CurlHandle) {
               $canClose = true;
            }
            if ($canClose) {
               curl_close($ch);
            }
         }
         unset($ch);
         return ['code' => $code, 'body' => (string)$resp, 'error' => (string)$err];
      }

      // fallback
      $opts = [
         'http' => [
            'method'  => 'POST',
            'header'  => "Content-Type: application/json\r\nAccept: application/json\r\n",
            'content' => $body,
            'timeout' => $timeout,
         ],
      ];
      $ctx = stream_context_create($opts);
      $resp = @file_get_contents($url, false, $ctx);
      $code = 0;
      $responseHeaders = [];
      if (function_exists('http_get_last_response_headers')) {
         $tmpHeaders = @http_get_last_response_headers();
         if (is_array($tmpHeaders)) {
            $responseHeaders = $tmpHeaders;
         }
      } elseif (isset($http_response_header) && is_array($http_response_header)) {
         $responseHeaders = $http_response_header;
      }
      foreach ($responseHeaders as $h) {
         if (preg_match('#^HTTP/\S+\s+(\d{3})#', $h, $m)) {
            $code = (int)$m[1];
            break;
         }
      }
      return ['code' => $code, 'body' => (string)$resp, 'error' => ''];
   }

   /**
    * Monta a URL do iframe de avisos (renderizado no Portal/Store) para esta instalação.
    *
    * Padrão: {store_url}/wbstore/avisos
    * Override (opcional): config notices_iframe_url
    */
   
   /**
    * Gera e persiste um portal_token local (usado para iframes/avisos e validações leves no Portal),
    * evitando depender de token externo. É estável e assinado pela license_key.
    */
   public static function ensurePortalToken(): string {
      $current = trim((string)Config::get('portal_token', ''));
      $iid = (string)Config::get('instance_id', '');
      $dom = self::currentDomain();
      $lk  = (string)Config::get('license_key', '');
      $key  = ($lk !== '' ? $lk : 'wbstore');

      // Token determinístico por instância/domínio (evita "token inválido" por troca/limpeza de cache)
      $desired = substr(hash_hmac('sha256', $iid . '|' . $dom, $key), 0, 40);

      if ($current === '' || $current !== $desired) {
         Config::set(['portal_token' => $desired]);
         return $desired;
      }
      return $current;
   }

   /**
    * Assina um payload (HMAC) usando a license_key como segredo.
    */
   public static function hmac(string $payload): string {
      $lk = (string)Config::get('license_key', '');
      $key = ($lk !== '' ? $lk : 'wbstore');
      return hash_hmac('sha256', $payload, $key);
   }

public static function portalNoticesIframeUrl(array $extraParams = []): string {
      $base = trim((string)Config::get('notices_iframe_url', ''));
      if ($base === '') {
         $store = rtrim((string)Config::get('store_url', ''), '/');
         if ($store === '') {
            return '';
         }
         $base = $store . '/wbstore/avisos';
      }

      $params = [
         'instance_id' => (string)Config::get('instance_id', ''),
         'license_key' => (string)Config::get('license_key', ''),
         'domain'      => self::currentDomain(),
         // cache-bust básico
         'ts'          => time(),
      ];

      // Token de autenticação do Portal (avisos via iframe)
      // IMPORTANT: portal_token é gerado localmente no GLPI e pode ser registrado no Portal via sync.
      $portalToken = self::ensurePortalToken();
      if ($portalToken !== '') {
         // Portal aceita portal_token e (fallback) token
         $params['portal_token'] = $portalToken;
         $params['token'] = $portalToken;
      }

      // Não adiciona token de API no iframe (evita vazar segredo em URL).

      if (!empty($extraParams)) {
         foreach ($extraParams as $k => $v) {
            $params[(string)$k] = (string)$v;
         }
      }

      $sep = (strpos($base, '?') === false) ? '?' : '&';
      return $base . $sep . http_build_query($params);
   }

   public static function glpiVersion(): string {
      return defined('GLPI_VERSION') ? (string)GLPI_VERSION : '';
   }

   public static function glpiMajor(): int {
      return self::versionMajor(self::glpiVersion());
   }

   public static function versionMajor(string $version): int {
      $version = trim($version);
      if ($version === '') {
         return 0;
      }
      if (preg_match('/^(\d{1,2})(?:\.|$)/', $version, $m)) {
         return (int)$m[1];
      }
      if (preg_match('/(\d{1,2})/', $version, $m)) {
         return (int)$m[1];
      }
      return 0;
   }

   private static function pushGlpiMajorsFromValue($value, array &$majors): void {
      if (is_array($value)) {
         foreach ($value as $item) {
            self::pushGlpiMajorsFromValue($item, $majors);
         }
         return;
      }

      if (is_int($value) || is_float($value)) {
         $major = (int)$value;
         if ($major > 0 && $major < 100) {
            $majors[] = $major;
         }
         return;
      }

      $value = trim((string)$value);
      if ($value === '') {
         return;
      }

      if (preg_match_all('/(?<![\d.])(\d{1,2})(?:\.\d+(?:\.\d+)?)?/', $value, $matches)) {
         foreach (($matches[1] ?? []) as $rawMajor) {
            $major = (int)$rawMajor;
            if ($major > 0 && $major < 100) {
               $majors[] = $major;
            }
         }
      }
   }

   public static function packageSupportedGlpiMajors(array $package): array {
      $majors = [];
      $keys = [
         'glpi_major',
         'glpi_majors',
         'glpi_target_major',
         'target_glpi_major',
         'glpi_branch',
         'glpi_branches',
         'glpi_version',
         'glpi_versions',
         'target_glpi',
         'target_glpi_versions',
         'supported_glpi',
         'supported_glpi_versions',
         'compatible_glpi',
         'compatibility',
         'glpi_compatibility',
         'glpi_range',
         'version_glpi'
      ];

      foreach ($keys as $key) {
         if (array_key_exists($key, $package)) {
            self::pushGlpiMajorsFromValue($package[$key], $majors);
         }
      }

      $minGlpi = trim((string)($package['min_glpi'] ?? ($package['glpi_min'] ?? '')));
      $maxGlpi = trim((string)($package['max_glpi'] ?? ($package['glpi_max'] ?? '')));
      $minMajor = self::versionMajor($minGlpi);
      $maxMajor = self::versionMajor($maxGlpi);

      if ($minMajor > 0 && $maxMajor > 0 && $maxMajor >= $minMajor) {
         for ($i = $minMajor; $i <= $maxMajor; $i++) {
            $majors[] = $i;
         }
      } elseif ($minMajor > 0) {
         $majors[] = $minMajor;
      }

      $majors = array_values(array_unique(array_filter(array_map('intval', $majors), static function ($major) {
         return $major > 0;
      })));
      sort($majors);
      return $majors;
   }

   public static function packageMatchesGlpiMajor(array $package, int $major): bool {
      if ($major <= 0) {
         return true;
      }
      $majors = self::packageSupportedGlpiMajors($package);
      if (!empty($majors)) {
         return in_array($major, $majors, true);
      }
      return true;
   }

   public static function packageMatchesCurrentGlpi(array $package): bool {
      return self::packageMatchesGlpiMajor($package, self::glpiMajor());
   }

   public static function packageGlpiLabel(array $package): string {
      $majors = self::packageSupportedGlpiMajors($package);
      if (!empty($majors)) {
         if (count($majors) === 1) {
            return 'GLPI ' . (int)$majors[0];
         }
         return 'GLPI ' . implode('/', array_map('intval', $majors));
      }

      $minGlpi = trim((string)($package['min_glpi'] ?? ($package['glpi_min'] ?? '')));
      if ($minGlpi !== '') {
         return 'GLPI ' . $minGlpi . '+';
      }

      return 'Sem filtro GLPI';
   }

}
