<?php
namespace App\Services;

use RuntimeException;

final class UploadService
{
    public function storePhoto(array $file): array
    {
        if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
            throw new RuntimeException($this->uploadErrorMessage((int)($file['error'] ?? UPLOAD_ERR_NO_FILE), 'Photo upload failed.'));
        }

        $config = $GLOBALS['config']['uploads'];
        $maxBytes = ((int)$config['max_photo_mb']) * 1024 * 1024;
        if (($file['size'] ?? 0) > $maxBytes) {
            throw new RuntimeException('Photo is too large. Maximum is ' . $config['max_photo_mb'] . ' MB.');
        }

        $gps = $this->extractGpsFromImage((string)$file['tmp_name']);
        [$mime, $ext] = $this->validateImage($file['tmp_name'], (string)($file['name'] ?? ''));
        $name = date('Ymd_His') . '_' . bin2hex(random_bytes(8)) . '.' . $ext;
        $photoDir = rtrim($config['photo_dir'], '/');
        $thumbDir = rtrim($config['thumbnail_dir'], '/');
        if (!is_dir($photoDir)) mkdir($photoDir, 0775, true);
        if (!is_dir($thumbDir)) mkdir($thumbDir, 0775, true);

        $dest = $photoDir . '/' . $name;
        if ($this->isHeicMime($mime)) {
            $this->convertHeicToJpeg($file['tmp_name'], $dest);
            $mime = 'image/jpeg';
        } elseif (!move_uploaded_file($file['tmp_name'], $dest)) {
            throw new RuntimeException('Unable to save uploaded photo.');
        }

        $thumbName = pathinfo($name, PATHINFO_FILENAME) . '_thumb.' . $ext;
        $thumbDest = $thumbDir . '/' . $thumbName;
        $this->createThumbnail($dest, $thumbDest, $mime, 720, 720);

        return [
            'file_path' => 'uploads/photos/' . $name,
            'thumbnail_path' => 'uploads/thumbnails/' . $thumbName,
            'gps' => $gps,
        ];
    }

    public function deleteStoredPhoto(array $stored): void
    {
        $this->deletePublicUploadFile((string)($stored['file_path'] ?? ''));
        $this->deletePublicUploadFile((string)($stored['thumbnail_path'] ?? ''));
    }

    private function deletePublicUploadFile(string $relativePath): void
    {
        $relativePath = ltrim(str_replace('\\', '/', $relativePath), '/');
        if ($relativePath === '' || !preg_match('#^uploads/(photos|thumbnails)/#', $relativePath)) {
            return;
        }

        $publicRoot = realpath(__DIR__ . '/../../public');
        if (!$publicRoot) {
            return;
        }

        $target = $publicRoot . '/' . $relativePath;
        $targetDir = realpath(dirname($target));
        if (!$targetDir || !str_starts_with($targetDir, $publicRoot) || !is_file($target)) {
            return;
        }

        @unlink($target);
    }

    public function storeAvatar(array $file): string
    {
        if (($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
            return '';
        }
        if (($file['error'] ?? UPLOAD_ERR_OK) !== UPLOAD_ERR_OK) {
            throw new RuntimeException($this->uploadErrorMessage((int)($file['error'] ?? UPLOAD_ERR_OK), 'Profile photo upload failed.'));
        }

        $config = $GLOBALS['config']['uploads'];
        $maxMb = (int)($config['max_avatar_mb'] ?? 4);
        $maxBytes = $maxMb * 1024 * 1024;
        if (($file['size'] ?? 0) > $maxBytes) {
            throw new RuntimeException('Profile photo is too large. Maximum is ' . $maxMb . ' MB.');
        }

        [$mime, $ext] = $this->validateImage($file['tmp_name'], (string)($file['name'] ?? ''));
        $avatarDir = rtrim($config['avatar_dir'] ?? (__DIR__ . '/../../public/uploads/avatars'), '/');
        if (!is_dir($avatarDir)) mkdir($avatarDir, 0775, true);

        $name = 'avatar_' . date('Ymd_His') . '_' . bin2hex(random_bytes(8)) . '.' . $ext;
        $dest = $avatarDir . '/' . $name;
        $tmpResized = $avatarDir . '/tmp_' . $name;

        if ($this->isHeicMime($mime)) {
            $this->createThumbnail($file['tmp_name'], $dest, $mime, 640, 640);
            if (!is_file($dest)) {
                $this->convertHeicToJpeg($file['tmp_name'], $dest);
            }
        } else {
            $this->createThumbnail($file['tmp_name'], $tmpResized, $mime, 640, 640);
            if (!is_file($tmpResized)) {
                if (!move_uploaded_file($file['tmp_name'], $dest)) {
                    throw new RuntimeException('Unable to save profile photo.');
                }
            } else {
                rename($tmpResized, $dest);
            }
        }

        return 'uploads/avatars/' . $name;
    }


    private function extractGpsFromImage(string $tmp): array
    {
        if (!function_exists('exif_read_data') || !is_file($tmp)) {
            return [];
        }

        try {
            $exif = @exif_read_data($tmp, 'GPS', true);
            $gps = is_array($exif['GPS'] ?? null) ? $exif['GPS'] : [];
            if (!$gps) return [];

            $lat = $this->gpsCoordinateToDecimal($gps['GPSLatitude'] ?? null, (string)($gps['GPSLatitudeRef'] ?? ''));
            $lng = $this->gpsCoordinateToDecimal($gps['GPSLongitude'] ?? null, (string)($gps['GPSLongitudeRef'] ?? ''));
            if ($lat === null || $lng === null) return [];

            return [
                'latitude' => $lat,
                'longitude' => $lng,
                'accuracy' => null,
                'source' => 'image_exif_gps',
                'captured_at' => null,
            ];
        } catch (\Throwable) {
            return [];
        }
    }

    private function gpsCoordinateToDecimal(mixed $coordinate, string $hemisphere): ?float
    {
        if (!is_array($coordinate) || count($coordinate) < 2) return null;
        $degrees = $this->gpsRationalToFloat($coordinate[0] ?? null);
        $minutes = $this->gpsRationalToFloat($coordinate[1] ?? null);
        $seconds = $this->gpsRationalToFloat($coordinate[2] ?? 0);
        if ($degrees === null || $minutes === null || $seconds === null) return null;

        $decimal = $degrees + ($minutes / 60) + ($seconds / 3600);
        $hemisphere = strtoupper(trim($hemisphere));
        if (in_array($hemisphere, ['S', 'W'], true)) $decimal *= -1;
        return round($decimal, 7);
    }

    private function gpsRationalToFloat(mixed $value): ?float
    {
        if (is_int($value) || is_float($value)) return (float)$value;
        $value = trim((string)$value);
        if ($value === '') return null;
        if (str_contains($value, '/')) {
            [$num, $den] = array_pad(explode('/', $value, 2), 2, '1');
            $den = (float)$den;
            if ($den == 0.0) return null;
            return (float)$num / $den;
        }
        return is_numeric($value) ? (float)$value : null;
    }

    private function validateImage(string $tmp, string $originalName = ''): array
    {
        $info = @getimagesize($tmp);
        $mime = is_array($info) ? (string)($info['mime'] ?? '') : '';

        if (!$mime && function_exists('finfo_open')) {
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            if ($finfo) {
                $detected = finfo_file($finfo, $tmp);
                finfo_close($finfo);
                if (is_string($detected)) $mime = $detected;
            }
        }

        $extFromName = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
        $allowed = [
            'image/jpeg' => 'jpg',
            'image/png' => 'png',
            'image/webp' => 'webp',
        ];

        if (isset($allowed[$mime]) && $info) {
            return [$mime, $allowed[$mime]];
        }

        if ($this->isHeicMime($mime) || in_array($extFromName, ['heic', 'heif'], true)) {
            if (!$this->imagickSupportsHeic()) {
                throw new RuntimeException('This phone photo is HEIC/HEIF. The server cannot process HEIC yet. Please upload JPG/PNG/WEBP, or enable the PHP Imagick extension with HEIC support.');
            }
            return [$mime ?: 'image/heic', 'jpg'];
        }

        throw new RuntimeException('Only JPG, PNG, WEBP, HEIC, and HEIF images are allowed.');
    }

    private function createThumbnail(string $source, string $dest, string $mime, int $maxW, int $maxH): void
    {
        if ($this->isHeicMime($mime)) {
            if ($this->createThumbnailWithImagick($source, $dest, $maxW, $maxH)) {
                return;
            }
            copy($source, $dest);
            return;
        }

        if (!function_exists('imagecreatetruecolor')) {
            copy($source, $dest);
            return;
        }
        [$w, $h] = getimagesize($source);
        $ratio = min($maxW / $w, $maxH / $h, 1);
        $newW = (int)round($w * $ratio);
        $newH = (int)round($h * $ratio);

        $canvas = imagecreatetruecolor($newW, $newH);
        imagealphablending($canvas, false);
        imagesavealpha($canvas, true);
        $src = match ($mime) {
            'image/jpeg' => imagecreatefromjpeg($source),
            'image/png' => imagecreatefrompng($source),
            'image/webp' => imagecreatefromwebp($source),
            default => null,
        };

        if (!$src) {
            copy($source, $dest);
            return;
        }

        imagecopyresampled($canvas, $src, 0, 0, 0, 0, $newW, $newH, $w, $h);
        match ($mime) {
            'image/jpeg' => imagejpeg($canvas, $dest, 84),
            'image/png' => imagepng($canvas, $dest, 6),
            'image/webp' => function_exists('imagewebp') ? imagewebp($canvas, $dest, 84) : imagejpeg($canvas, $dest, 84),
            default => imagejpeg($canvas, $dest, 84),
        };
        imagedestroy($canvas);
        imagedestroy($src);
    }

    private function uploadErrorMessage(int $code, string $fallback): string
    {
        return match ($code) {
            UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'Photo is too large for the server upload limit.',
            UPLOAD_ERR_PARTIAL => 'Photo upload was interrupted. Please try again.',
            UPLOAD_ERR_NO_FILE => 'Photo file is required.',
            UPLOAD_ERR_NO_TMP_DIR => 'Server upload temp folder is missing.',
            UPLOAD_ERR_CANT_WRITE => 'Server could not write the uploaded photo.',
            UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the photo upload.',
            default => $fallback,
        };
    }

    private function isHeicMime(string $mime): bool
    {
        $mime = strtolower(trim($mime));
        return in_array($mime, ['image/heic', 'image/heif', 'image/heic-sequence', 'image/heif-sequence'], true);
    }

    private function imagickSupportsHeic(): bool
    {
        if (!class_exists('Imagick')) {
            return false;
        }
        try {
            $formats = \Imagick::queryFormats();
            foreach ($formats as $format) {
                if (in_array(strtoupper((string)$format), ['HEIC', 'HEIF'], true)) {
                    return true;
                }
            }
        } catch (\Throwable $e) {
            return true;
        }
        return false;
    }

    private function convertHeicToJpeg(string $source, string $dest): void
    {
        if (!$this->imagickSupportsHeic()) {
            throw new RuntimeException('This phone photo is HEIC/HEIF. The server cannot process HEIC yet. Please upload JPG/PNG/WEBP, or enable the PHP Imagick extension with HEIC support.');
        }

        try {
            $img = new \Imagick();
            $img->readImage($source);
            $img->setImageOrientation(\Imagick::ORIENTATION_TOPLEFT);
            $img->setImageFormat('jpeg');
            $img->setImageCompressionQuality(88);
            $img->stripImage();
            $img->writeImage($dest);
            $img->clear();
            $img->destroy();
        } catch (\Throwable $e) {
            throw new RuntimeException('Could not convert this HEIC/HEIF phone photo. Please upload a JPG/PNG/WEBP image.');
        }
    }

    private function createThumbnailWithImagick(string $source, string $dest, int $maxW, int $maxH): bool
    {
        if (!$this->imagickSupportsHeic()) {
            return false;
        }
        try {
            $img = new \Imagick();
            $img->readImage($source);
            $w = max(1, $img->getImageWidth());
            $h = max(1, $img->getImageHeight());
            $ratio = min($maxW / $w, $maxH / $h, 1);
            $newW = (int)round($w * $ratio);
            $newH = (int)round($h * $ratio);
            $img->thumbnailImage($newW, $newH, true);
            $img->setImageOrientation(\Imagick::ORIENTATION_TOPLEFT);
            $img->setImageFormat('jpeg');
            $img->setImageCompressionQuality(84);
            $img->stripImage();
            $img->writeImage($dest);
            $img->clear();
            $img->destroy();
            return true;
        } catch (\Throwable $e) {
            return false;
        }
    }
}
