<?php
/**
 * Simple MediaService
 * - fetchFromGraph(mediaId) -> gets Graph API URL and downloads
 * - storeContent($bytes, $destPath)
 * - createThumbnail($srcPath, $thumbPath)
 */
class MediaService {
    private $token;
    private $apiUrl;
    private $uploadsDir;

    public function __construct() {
        // Usar constante WHATSAPP_TOKEN (que ya tiene fallback a env var) si getConfigFromDB retorna vacío
        $dbToken = getConfigFromDB('whatsapp_token', '');
        $this->token = !empty($dbToken) ? $dbToken : (defined('WHATSAPP_TOKEN') ? WHATSAPP_TOKEN : getenv('WHATSAPP_TOKEN'));
        
        $dbApiUrl = getConfigFromDB('whatsapp_api_url', '');
        $this->apiUrl = rtrim(!empty($dbApiUrl) ? $dbApiUrl : (defined('WHATSAPP_API_URL') ? WHATSAPP_API_URL : 'https://graph.facebook.com/v22.0/'), '/');
        
        $this->uploadsDir = __DIR__ . '/../uploads/media';
        if (!is_dir($this->uploadsDir)) mkdir($this->uploadsDir, 0755, true);
    }

    /**
     * Hacer petición GET al Graph API con fallback si curl falla
     */
    private function graphApiRequest($url) {
        // Intentar con curl primero
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $this->token],
            CURLOPT_TIMEOUT => 15,
            CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        ]);
        $resp = curl_exec($ch);
        $http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $err = curl_error($ch);
        curl_close($ch);

        if (!$err && $http >= 200 && $http < 400 && $resp) {
            return $resp;
        }

        // Fallback: file_get_contents con stream context
        error_log("[MediaService::graphApiRequest] curl failed (http={$http} err={$err}), trying file_get_contents...");
        return $this->fileGetContentsWithAuth($url);
    }

    public function fetchAndStoreFromGraph($mediaId, $subdir = '') {
        // Obtener metadata (url) via Graph API
        $endpoint = "{$this->apiUrl}/{$mediaId}";
        $resp = $this->graphApiRequest($endpoint);
        if ($resp === false) {
            throw new Exception('Graph API fetch failed for media ' . $mediaId);
        }
        $decoded = json_decode($resp, true);
        $mediaUrl = $decoded['url'] ?? ($decoded['data'][0]['url'] ?? null);
        if (!$mediaUrl) throw new Exception('No media url from Graph');

        // Para URLs de lookaside.fbsbx.com, usar wget directamente porque curl/OpenSSL falla con ese CDN
        // Obtener URL fresca justo antes de descargar para evitar expiración
        $content = false;
        if (strpos($mediaUrl, 'lookaside.fbsbx.com') !== false) {
            $authHeaders = !empty($this->token) ? ['Authorization: Bearer ' . $this->token] : [];
            // Intentar wget directo con URL actual
            $content = $this->shellDownload($mediaUrl, $authHeaders);
            if ($content === false) {
                // URL puede haber expirado — obtener URL fresca y reintentar
                error_log("[MediaService] wget failed, getting fresh URL for {$mediaId}...");
                $resp2 = $this->graphApiRequest($endpoint);
                if ($resp2) {
                    $decoded2 = json_decode($resp2, true);
                    $freshUrl = $decoded2['url'] ?? $mediaUrl;
                    $content = $this->shellDownload($freshUrl, $authHeaders);
                }
            }
        }

        // Fallback al cascade completo (curl → file_get_contents → wget) para otras URLs
        if ($content === false) {
            $content = $this->downloadUrl($mediaUrl);
            if ($content === false) {
                // Obtener URL fresca y reintentar
                error_log("[MediaService] downloadUrl failed, getting fresh URL...");
                $resp2 = $this->graphApiRequest($endpoint);
                if ($resp2) {
                    $decoded2 = json_decode($resp2, true);
                    $freshUrl = $decoded2['url'] ?? $mediaUrl;
                    $content = $this->downloadUrl($freshUrl);
                }
            }
        }

        if ($content === false) throw new Exception('Failed to download media content from ' . $mediaUrl);

        // Detectar extension desde mime o url
        $mime = $decoded['mime_type'] ?? null;
        $ext = $this->extensionFromMime($mime) ?: pathinfo(parse_url($mediaUrl, PHP_URL_PATH), PATHINFO_EXTENSION) ?: 'bin';

        $dir = $this->uploadsDir . ($subdir ? '/' . trim($subdir, '/') : '');
        if (!is_dir($dir)) mkdir($dir, 0755, true);

        $filename = uniqid('m_') . '.' . $ext;
        $fullPath = $dir . '/' . $filename;
        file_put_contents($fullPath, $content);

        $result = ['local_file' => 'uploads/media' . ($subdir ? '/' . trim($subdir, '/') : '') . '/' . $filename, 'mime_type' => $mime, 'size' => filesize($fullPath)];

        // Si es imagen, generar miniatura
        if ($this->isImageMime($mime) || preg_match('#^jpe?g|png|gif$#i', $ext)) {
            $thumbPath = $dir . '/thumb_' . $filename . '.jpg';
            $thumbRel = dirname($result['local_file']) . '/thumb_' . $filename . '.jpg';
            if ($this->createThumbnail($fullPath, $thumbPath)) {
                $result['local_thumb'] = $thumbRel;
            }
        }

        return $result;
    }

    // Nuevo: descargar y almacenar desde una URL directa
    public function fetchAndStoreFromUrl($url, $subdir = '') {
        $content = $this->downloadUrl($url);
        if ($content === false) {
            $content = $this->downloadUrl($url, ['Referer: https://graph.facebook.com']);
        }
        if ($content === false) throw new Exception('Failed to download media content from url ' . $url);

        // intentar inferir mime
        $mime = null;
        $ext = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION) ?: 'bin';
        $dir = $this->uploadsDir . ($subdir ? '/' . trim($subdir, '/') : '');
        if (!is_dir($dir)) mkdir($dir, 0755, true);
        $filename = uniqid('m_') . '.' . $ext;
        $fullPath = $dir . '/' . $filename;
        file_put_contents($fullPath, $content);

        $result = ['local_file' => 'uploads/media' . ($subdir ? '/' . trim($subdir, '/') : '') . '/' . $filename, 'mime_type' => $mime, 'size' => filesize($fullPath)];

        // Si parece imagen, intentar crear thumb
        if (preg_match('#jpe?g|png|gif#i', $ext)) {
            $thumbPath = $dir . '/thumb_' . $filename . '.jpg';
            $thumbRel = dirname($result['local_file']) . '/thumb_' . $filename . '.jpg';
            if ($this->createThumbnail($fullPath, $thumbPath)) {
                $result['local_thumb'] = $thumbRel;
            }
        }

        return $result;
    }

    public function downloadUrl($url, $extraHeaders = []) {
        // Incluir Authorization Bearer si está configurado (necesario para algunos endpoints)
        $authHeader = [];
        if (!empty($this->token)) {
            $authHeader[] = 'Authorization: Bearer ' . $this->token;
        }
        $headers = array_merge(['User-Agent: WhatsAppMediaFetcher/1.0', 'Accept: */*'], $authHeader, $extraHeaders);

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_CONNECTTIMEOUT => 30,
            CURLOPT_TIMEOUT => 180,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_BUFFERSIZE => 65536,
            CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        ]);
        $content = curl_exec($ch);
        $err = curl_error($ch);
        $http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $errno = curl_errno($ch);
        curl_close($ch);

        // Si curl funcionó correctamente, retornar contenido
        if (!$err && $http >= 200 && $http < 400 && $content !== false && strlen($content) > 0) {
            return $content;
        }

        // Fallback: file_get_contents con stream context (usa implementación SSL diferente a curl)
        error_log("[MediaService::downloadUrl] curl failed (http={$http} errno={$errno} err={$err}), trying file_get_contents fallback...");
        $content = $this->fileGetContentsWithAuth($url);
        if ($content !== false && strlen($content) > 0) {
            error_log("[MediaService::downloadUrl] file_get_contents OK, " . strlen($content) . " bytes");
            return $content;
        }

        // Fallback: exec wget (solo funciona en CLI, no en php-fpm con exec deshabilitado)
        $content = $this->shellDownload($url, $headers);
        if ($content !== false) {
            return $content;
        }

        error_log("[MediaService::downloadUrl] FAIL url=" . substr($url, 0, 120) . " http={$http} err={$err} all_fallbacks_failed");
        return false;
    }

    /**
     * Descargar usando file_get_contents con stream context (usa implementación SSL nativa de PHP, diferente a curl)
     */
    private function fileGetContentsWithAuth($url) {
        $opts = [
            'http' => [
                'method' => 'GET',
                'header' => "Authorization: Bearer {$this->token}\r\nUser-Agent: WhatsAppMediaFetcher/1.0\r\nAccept: */*\r\n",
                'follow_location' => true,
                'max_redirects' => 5,
                'timeout' => 120,
                'ignore_errors' => false,
            ],
            'ssl' => [
                'verify_peer' => true,
                'verify_peer_name' => true,
            ],
        ];
        $ctx = stream_context_create($opts);
        $content = @file_get_contents($url, false, $ctx);
        if ($content === false) {
            $lastErr = error_get_last();
            error_log("[MediaService::fileGetContentsWithAuth] FAIL: " . ($lastErr['message'] ?? 'unknown'));
            return false;
        }
        return $content;
    }

    /**
     * Fallback: descargar con wget via exec/proc_open (solo funciona en CLI)
     */
    private function shellDownload($url, $headers = []) {
        // Verificar si exec está realmente disponible
        // php_admin_value[disable_functions] NO aparece en ini_get('disable_functions')
        // así que hacemos un test real con try/catch
        $execAvailable = function_exists('exec');
        if ($execAvailable) {
            try {
                @exec('echo 1', $testOut, $testRc);
            } catch (\Error $e) {
                $execAvailable = false;
            }
        }

        if (!$execAvailable) {
            return $this->procOpenDownload($url, $headers);
        }

        $tmpFile = tempnam(sys_get_temp_dir(), 'wget_');
        // Solo pasar Authorization header para evitar problemas con User-Agent personalizado en CDN
        $headerArgs = '';
        foreach ($headers as $h) {
            if (stripos($h, 'Authorization:') === 0) {
                $headerArgs .= ' --header=' . escapeshellarg($h);
            }
        }
        // --continue: reanuda desde donde se cortó (evita reiniciar ante Connection reset by peer)
        // --tries=10 --waitretry=3: reintenta hasta 10 veces esperando 3s entre intentos
        // --prefer-family=IPv4: evitar problemas IPv6
        $cmd = 'wget --continue --tries=10 --waitretry=3 --prefer-family=IPv4 -O ' . escapeshellarg($tmpFile) . $headerArgs . ' ' . escapeshellarg($url) . ' 2>&1';
        try {
            exec($cmd, $output, $exitCode);
        } catch (\Error $e) {
            error_log("[MediaService::shellDownload] exec() threw Error: " . $e->getMessage());
            if (file_exists($tmpFile)) unlink($tmpFile);
            return $this->procOpenDownload($url, $headers);
        }

        $fileSize = (file_exists($tmpFile) ? filesize($tmpFile) : 0);
        error_log("[MediaService::shellDownload] wget exit={$exitCode} size={$fileSize} output=" . implode(' | ', array_slice($output, -3)));

        if ($exitCode === 0 && $fileSize > 0) {
            $content = file_get_contents($tmpFile);
            unlink($tmpFile);
            error_log("[MediaService::shellDownload] wget OK, " . strlen($content) . " bytes");
            return $content;
        }

        error_log("[MediaService::shellDownload] wget FAIL exit={$exitCode} size={$fileSize}");
        if (file_exists($tmpFile)) unlink($tmpFile);
        return false;
    }

    /**
     * Descargar con proc_open (alternativa a exec)
     */
    private function procOpenDownload($url, $headers = []) {
        // Test real de disponibilidad: php_admin_value no se refleja en ini_get
        $procAvailable = function_exists('proc_open');
        if ($procAvailable) {
            try {
                $testProc = @proc_open('echo 1', [1 => ['pipe', 'w']], $testPipes);
                if (is_resource($testProc)) {
                    fclose($testPipes[1]);
                    proc_close($testProc);
                } else {
                    $procAvailable = false;
                }
            } catch (\Error $e) {
                $procAvailable = false;
            }
        }
        if (!$procAvailable) {
            error_log("[MediaService::procOpenDownload] proc_open not available, skipping");
            return false;
        }

        $tmpFile = tempnam(sys_get_temp_dir(), 'wget_');
        $headerArgs = '';
        foreach ($headers as $h) {
            $headerArgs .= ' --header=' . escapeshellarg($h);
        }
        $cmd = 'wget -q --prefer-family=IPv4 -O ' . escapeshellarg($tmpFile) . $headerArgs . ' ' . escapeshellarg($url);
        
        try {
            $proc = proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
        } catch (\Error $e) {
            error_log("[MediaService::procOpenDownload] proc_open() threw Error: " . $e->getMessage());
            if (file_exists($tmpFile)) unlink($tmpFile);
            return false;
        }
        if (!is_resource($proc)) {
            error_log("[MediaService::procOpenDownload] proc_open failed");
            if (file_exists($tmpFile)) unlink($tmpFile);
            return false;
        }
        $stdout = stream_get_contents($pipes[1]); fclose($pipes[1]);
        $stderr = stream_get_contents($pipes[2]); fclose($pipes[2]);
        $exitCode = proc_close($proc);

        if ($exitCode === 0 && file_exists($tmpFile) && filesize($tmpFile) > 0) {
            $content = file_get_contents($tmpFile);
            unlink($tmpFile);
            error_log("[MediaService::procOpenDownload] OK, " . strlen($content) . " bytes");
            return $content;
        }

        error_log("[MediaService::procOpenDownload] FAIL exit={$exitCode} err={$stderr}");
        if (file_exists($tmpFile)) unlink($tmpFile);
        return false;
    }

    public function extensionFromMime($mime) {
        if (!$mime) return null;
        // Remover parámetros del mime type (ej: "audio/ogg; codecs=opus" -> "audio/ogg")
        $baseMime = strtolower(trim(explode(';', $mime)[0]));
        $map = [
            'image/jpeg' => 'jpg',
            'image/png' => 'png',
            'image/gif' => 'gif',
            'image/webp' => 'webp',
            'image/svg+xml' => 'svg',
            'audio/mpeg' => 'mp3',
            'audio/ogg' => 'ogg',
            'audio/opus' => 'ogg',
            'audio/aac' => 'aac',
            'audio/amr' => 'amr',
            'audio/mp4' => 'm4a',
            'video/mp4' => 'mp4',
            'video/3gpp' => '3gp',
            'video/quicktime' => 'mov',
            'video/webm' => 'webm',
            'text/csv' => 'csv',
            'text/plain' => 'txt',
            'application/pdf' => 'pdf',
            'application/msword' => 'doc',
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
            'application/vnd.ms-excel' => 'xls',
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
            'application/vnd.ms-powerpoint' => 'ppt',
            'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
            'application/zip' => 'zip',
            'application/x-rar-compressed' => 'rar',
            'application/octet-stream' => 'bin',
        ];
        return $map[$baseMime] ?? null;
    }

    public function isImageMime($mime) {
        return strpos($mime, 'image/') === 0;
    }

    public function createThumbnail($src, $dest, $maxW = 400, $maxH = 400) {
        if (!function_exists('getimagesize')) return false;
        $info = @getimagesize($src);
        if (!$info) return false;
        list($w, $h) = $info;
        $mime = $info['mime'];

        switch ($mime) {
            case 'image/jpeg': $srcImg = imagecreatefromjpeg($src); break;
            case 'image/png': $srcImg = imagecreatefrompng($src); break;
            case 'image/gif': $srcImg = imagecreatefromgif($src); break;
            default: return false;
        }

        $ratio = min($maxW / $w, $maxH / $h, 1);
        $nw = (int)($w * $ratio);
        $nh = (int)($h * $ratio);

        $thumb = imagecreatetruecolor($nw, $nh);
        imagecopyresampled($thumb, $srcImg, 0,0,0,0, $nw, $nh, $w, $h);
        imagejpeg($thumb, $dest, 85);
        imagedestroy($thumb);
        imagedestroy($srcImg);
        return true;
    }
}
