<?php

namespace App\Libraries;

use CodeIgniter\HTTP\Files\UploadedFile;

/**
 * FileUploadHandler
 *
 * Secure, centralised file upload with type/size validation,
 * image resizing, and organised directory structure.
 */
class FileUploadHandler
{
    // Maximum file sizes (bytes)
    private const MAX_IMAGE_SIZE    =  5 * 1024 * 1024;   //  5 MB
    private const MAX_VIDEO_SIZE    = 200 * 1024 * 1024;   // 200 MB
    private const MAX_DOCUMENT_SIZE =  20 * 1024 * 1024;   //  20 MB

    // Allowed MIME types per category
    private const ALLOWED_IMAGE_TYPES = [
        'image/jpeg', 'image/jpg', 'image/png',
        'image/gif', 'image/webp',
    ];
    private const ALLOWED_VIDEO_TYPES = [
        'video/mp4', 'video/avi', 'video/mov',
        'video/quicktime', 'video/x-msvideo', 'video/webm',
    ];
    private const ALLOWED_DOCUMENT_TYPES = [
        'application/pdf',
        'application/msword',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'application/vnd.ms-powerpoint',
        'application/vnd.openxmlformats-officedocument.presentationml.presentation',
        'application/vnd.ms-excel',
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'text/plain',
    ];

    // Target dimensions for profile photos
    private const PHOTO_MAX_WIDTH  = 800;
    private const PHOTO_MAX_HEIGHT = 800;

    private string $uploadBase;

    public function __construct()
    {
        // Store uploads inside public/uploads/ so they are web-accessible directly via URL.
        // FCPATH = the public/ directory, defined as a constant in public/index.php.
        $this->uploadBase = defined('FCPATH') ? FCPATH . 'uploads/' : WRITEPATH . 'uploads/';
    }

    // ----------------------------------------------------------------
    // Public API
    // ----------------------------------------------------------------

    /**
     * Upload and optionally resize a profile image.
     *
     * @param  UploadedFile $file
     * @param  string       $subdir  e.g. 'profiles', 'qr_logos'
     * @return array{success: bool, path: string, message: string}
     */
    public function uploadImage(UploadedFile $file, string $subdir = 'profiles'): array
    {
        $mime = $this->getMimeType($file);

        if (! in_array($mime, self::ALLOWED_IMAGE_TYPES, true)) {
            return $this->error('Invalid image type. Allowed: JPG, PNG, GIF, WebP.');
        }
        if ($file->getSize() > self::MAX_IMAGE_SIZE) {
            return $this->error('Image exceeds 5 MB limit.');
        }

        $dir      = $this->ensureDir($subdir);
        $filename = $this->safeName($file);
        $fullPath = $dir . $filename;

        if (! $file->move($dir, $filename)) {
            return $this->error('Failed to save image.');
        }

        // Resize / compress if GD available
        if (extension_loaded('gd') && in_array($mime, ['image/jpeg','image/jpg','image/png'], true)) {
            $this->resizeImage($fullPath, $mime, self::PHOTO_MAX_WIDTH, self::PHOTO_MAX_HEIGHT);
        }

        return $this->ok($subdir . '/' . $filename);
    }

    /**
     * Upload a video or document file.
     *
     * @param  UploadedFile $file
     * @param  string       $subdir  'videos' or 'documents'
     * @return array{success: bool, path: string, message: string}
     */
    public function uploadMedia(UploadedFile $file, string $subdir): array
    {
        $mime     = $this->getMimeType($file);
        $maxSize  = ($subdir === 'videos') ? self::MAX_VIDEO_SIZE : self::MAX_DOCUMENT_SIZE;
        $allowed  = ($subdir === 'videos')
                    ? self::ALLOWED_VIDEO_TYPES
                    : self::ALLOWED_DOCUMENT_TYPES;

        if (! in_array($mime, $allowed, true)) {
            return $this->error('File type not allowed.');
        }
        if ($file->getSize() > $maxSize) {
            $mb = $maxSize / (1024 * 1024);
            return $this->error("File exceeds {$mb} MB limit.");
        }

        $dir      = $this->ensureDir($subdir);
        $filename = $this->safeName($file);

        if (! $file->move($dir, $filename)) {
            return $this->error('Failed to save file.');
        }

        return $this->ok($subdir . '/' . $filename);
    }

    /**
     * Delete a file relative to the uploads base.
     */
    public function deleteFile(string $relativePath): void
    {
        $full = $this->uploadBase . ltrim($relativePath, '/');
        if (file_exists($full)) {
            @unlink($full);
        }
    }

    // ----------------------------------------------------------------
    // Private helpers
    // ----------------------------------------------------------------

    private function ensureDir(string $subdir): string
    {
        $path = $this->uploadBase . $subdir . '/';
        if (! is_dir($path)) {
            mkdir($path, 0775, true);
        }
        return $path;
    }

    /**
     * Generate a safe, unique filename.
     */
    private function safeName(UploadedFile $file): string
    {
        $ext  = strtolower($file->getExtension());
        // Allowlist extensions
        $safe = preg_replace('/[^a-z0-9]/', '', $ext);
        return bin2hex(random_bytes(16)) . '.' . ($safe ?: 'bin');
    }

    /**
     * Detect MIME type via finfo for security (not trusting client header).
     */
    private function getMimeType(UploadedFile $file): string
    {
        $tmp = $file->getTempName();
        if (function_exists('finfo_file')) {
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            $mime  = finfo_file($finfo, $tmp);
            finfo_close($finfo);
            return $mime ?: $file->getMimeType();
        }
        return $file->getMimeType();
    }

    /**
     * Resize image in-place using GD.
     */
    private function resizeImage(string $path, string $mime, int $maxW, int $maxH): void
    {
        [$origW, $origH] = getimagesize($path);

        if ($origW <= $maxW && $origH <= $maxH) {
            return; // Already small enough
        }

        $ratio  = min($maxW / $origW, $maxH / $origH);
        $newW   = (int)($origW * $ratio);
        $newH   = (int)($origH * $ratio);

        $src = match ($mime) {
            'image/jpeg', 'image/jpg' => imagecreatefromjpeg($path),
            'image/png'               => imagecreatefrompng($path),
            default                   => null,
        };

        if (! $src) {
            return;
        }

        $dst = imagecreatetruecolor($newW, $newH);

        // Preserve transparency for PNG
        if ($mime === 'image/png') {
            imagealphablending($dst, false);
            imagesavealpha($dst, true);
        }

        imagecopyresampled($dst, $src, 0, 0, 0, 0, $newW, $newH, $origW, $origH);

        match ($mime) {
            'image/jpeg', 'image/jpg' => imagejpeg($dst, $path, 85),
            'image/png'               => imagepng($dst, $path, 8),
            default                   => null,
        };

        imagedestroy($src);
        imagedestroy($dst);
    }

    private function ok(string $path): array
    {
        return ['success' => true, 'path' => $path, 'message' => ''];
    }

    private function error(string $msg): array
    {
        return ['success' => false, 'path' => '', 'message' => $msg];
    }
}
