<?php

namespace App\Http\Controllers\User;

use App\Http\Controllers\Controller;
use App\Support\CoverUrl;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Drivers\Gd\Driver as GdDriver;
use Intervention\Image\Drivers\Imagick\Driver as ImagickDriver;
use Intervention\Image\Encoders\WebpEncoder;
use Intervention\Image\ImageManager;
use RuntimeException;

class ProfileCoverController extends Controller
{
    private const ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp'];

    private const MAX_FILE_SIZE_KB = 5120;

    private const TARGET_WIDTH = 1920;

    private const TARGET_HEIGHT = 480;

    private const MIN_UPLOAD_WIDTH = 640;

    private const MIN_UPLOAD_HEIGHT = 160;

    private ?ImageManager $manager = null;

    public function __construct()
    {
        try {
            $this->manager = extension_loaded('gd')
                ? new ImageManager(new GdDriver())
                : new ImageManager(new ImagickDriver());
        } catch (\Throwable) {
            $this->manager = null;
        }
    }

    public function upload(Request $request): JsonResponse
    {
        $user = $request->user();
        if (! $user) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        $validated = $request->validate([
            'cover' => [
                'required',
                'file',
                'image',
                'max:' . self::MAX_FILE_SIZE_KB,
                'mimes:jpg,jpeg,png,webp',
                'mimetypes:image/jpeg,image/png,image/webp',
            ],
        ]);

        /** @var UploadedFile $file */
        $file = $validated['cover'];

        try {
            $stored = $this->storeCoverFile($file);

            $this->deleteCoverFile((string) $user->cover_hash, (string) $user->cover_ext);

            $user->forceFill([
                'cover_hash' => $stored['hash'],
                'cover_ext' => $stored['ext'],
                'cover_position' => 50,
            ])->save();

            return response()->json([
                'success' => true,
                'cover_url' => CoverUrl::forUser($user->cover_hash, $user->cover_ext, time()),
                'cover_position' => (int) $user->cover_position,
            ]);
        } catch (RuntimeException $e) {
            return response()->json([
                'error' => 'Validation failed',
                'message' => $e->getMessage(),
            ], 422);
        } catch (\Throwable $e) {
            logger()->error('Profile cover upload failed', [
                'user_id' => (int) $user->id,
                'message' => $e->getMessage(),
            ]);

            return response()->json(['error' => 'Processing failed'], 500);
        }
    }

    public function updatePosition(Request $request): JsonResponse
    {
        $user = $request->user();
        if (! $user) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        $validated = $request->validate([
            'position' => ['required', 'integer', 'min:0', 'max:100'],
        ]);

        if (! $user->cover_hash || ! $user->cover_ext) {
            return response()->json(['error' => 'No cover image to update.'], 422);
        }

        $user->forceFill([
            'cover_position' => (int) $validated['position'],
        ])->save();

        return response()->json([
            'success' => true,
            'cover_position' => (int) $user->cover_position,
        ]);
    }

    public function destroy(Request $request): JsonResponse
    {
        $user = $request->user();
        if (! $user) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        $this->deleteCoverFile((string) $user->cover_hash, (string) $user->cover_ext);

        $user->forceFill([
            'cover_hash' => null,
            'cover_ext' => null,
            'cover_position' => 50,
        ])->save();

        return response()->json([
            'success' => true,
            'cover_url' => null,
            'cover_position' => 50,
        ]);
    }

    private function coverDiskName(): string
    {
        return (string) config('covers.disk', 's3');
    }

    private function coverDirectory(string $hash): string
    {
        $p1 = substr($hash, 0, 2);
        $p2 = substr($hash, 2, 2);

        return 'covers/' . $p1 . '/' . $p2;
    }

    private function coverPath(string $hash, string $ext): string
    {
        return $this->coverDirectory($hash) . '/' . $hash . '.' . $ext;
    }

    /**
     * @return array{hash: string, ext: string}
     */
    private function storeCoverFile(UploadedFile $file): array
    {
        $this->assertImageManager();
        $this->assertStorageIsAllowed();

        $uploadPath = (string) ($file->getRealPath() ?: $file->getPathname());
        if ($uploadPath === '' || ! is_readable($uploadPath)) {
            throw new RuntimeException('Unable to resolve uploaded image path.');
        }

        $raw = file_get_contents($uploadPath);
        if ($raw === false || $raw === '') {
            throw new RuntimeException('Unable to read uploaded image.');
        }

        $finfo = new \finfo(FILEINFO_MIME_TYPE);
        $mime = strtolower((string) $finfo->buffer($raw));
        if (! in_array($mime, self::ALLOWED_MIME_TYPES, true)) {
            throw new RuntimeException('Unsupported image mime type.');
        }

        $size = @getimagesizefromstring($raw);
        if (! is_array($size) || ($size[0] ?? 0) < 1 || ($size[1] ?? 0) < 1) {
            throw new RuntimeException('Uploaded file is not a valid image.');
        }

        $width = (int) ($size[0] ?? 0);
        $height = (int) ($size[1] ?? 0);
        if ($width < self::MIN_UPLOAD_WIDTH || $height < self::MIN_UPLOAD_HEIGHT) {
            throw new RuntimeException(sprintf(
                'Image is too small. Minimum required size is %dx%d.',
                self::MIN_UPLOAD_WIDTH,
                self::MIN_UPLOAD_HEIGHT,
            ));
        }

        $image = $this->manager->read($raw);
        $processed = $image->cover(self::TARGET_WIDTH, self::TARGET_HEIGHT, 'center');
        $ext = 'webp';
        $encoded = $this->encodeByExtension($processed, $ext);

        $hash = hash('sha256', $encoded);
        $disk = Storage::disk($this->coverDiskName());
        $written = $disk->put($this->coverPath($hash, $ext), $encoded, [
            'visibility' => 'public',
            'CacheControl' => 'public, max-age=31536000, immutable',
            'ContentType' => match ($ext) {
                'jpg' => 'image/jpeg',
                'png' => 'image/png',
                default => 'image/webp',
            },
        ]);

        if ($written !== true) {
            throw new RuntimeException('Unable to store cover image in object storage.');
        }

        return ['hash' => $hash, 'ext' => $ext];
    }

    private function encodeByExtension($image, string $ext): string
    {
        return match ($ext) {
            default => (string) $image->encode(new WebpEncoder(85)),
        };
    }

    private function deleteCoverFile(string $hash, string $ext): void
    {
        $trimHash = trim($hash);
        $trimExt = strtolower(trim($ext));

        if ($trimHash === '' || $trimExt === '') {
            return;
        }

        Storage::disk($this->coverDiskName())->delete($this->coverPath($trimHash, $trimExt));
    }

    private function assertImageManager(): void
    {
        if ($this->manager !== null) {
            return;
        }

        throw new RuntimeException('Image processing is not available on this environment.');
    }

    private function assertStorageIsAllowed(): void
    {
        if (! app()->environment('production')) {
            return;
        }

        $diskName = $this->coverDiskName();
        if (in_array($diskName, ['local', 'public'], true)) {
            throw new RuntimeException('Production cover storage must use object storage, not local/public disks.');
        }
    }
}
