<?php

namespace App\Services;

use App\Models\Gallery;
use App\Models\GalleryType;
use App\Models\GalleryImage;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Intervention\Image\ImageManager;
use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
 * GalleryService
 *
 * Core business logic for the Gallery Module.
 * Handles gallery management, image uploads with optimization,
 * file storage, and cleanup operations.
 */
class GalleryService
{
    protected const STORAGE_DISK = 'public';
    protected const STORAGE_PATH = 'gallery-images';
    protected const MAX_FILE_SIZE = 5242880; // 5MB in bytes
    protected const ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
    protected const IMAGE_QUALITY = 85;

    public function __construct(protected ImageManager $imageManager) {}

    // =========================================================================
    // GALLERY TYPE MANAGEMENT
    // =========================================================================

    /**
     * Create a new gallery type.
     *
     * @param array $data Gallery type data
     * @return GalleryType
     */
    public function createGalleryType(array $data): GalleryType
    {
        return GalleryType::create($data);
    }

    /**
     * Update an existing gallery type.
     *
     * @param GalleryType $type
     * @param array $data Updated data
     * @return GalleryType
     */
    public function updateGalleryType(GalleryType $type, array $data): GalleryType
    {
        $type->update($data);
        return $type->fresh();
    }

    /**
     * Delete a gallery type with guards.
     *
     * @param GalleryType $type
     * @return bool
     * @throws \RuntimeException if galleries exist for this type
     */
    public function deleteGalleryType(GalleryType $type): bool
    {
        $galleryCount = $type->galleries()->count();
        if ($galleryCount > 0) {
            throw new \RuntimeException(
                "Cannot delete gallery type: {$galleryCount} galleries exist with this type."
            );
        }

        Log::info('Gallery type deleted', ['type_id' => $type->id, 'name' => $type->name]);
        return $type->delete();
    }

    // =========================================================================
    // GALLERY MANAGEMENT
    // =========================================================================

    /**
     * Create a new gallery.
     *
     * @param int $organizationId
     * @param array $data Gallery data
     * @param int $createdBy User ID creating the gallery
     * @return Gallery
     */
    public function createGallery(int $organizationId, array $data, int $createdBy): Gallery
    {
        $data['organization_id'] = $organizationId;
        $data['created_by'] = $createdBy;
        $data['updated_by'] = $createdBy;

        return DB::transaction(function () use ($data) {
            $gallery = Gallery::create($data);

            Log::info('Gallery created', [
                'gallery_id'      => $gallery->id,
                'organization_id' => $gallery->organization_id,
                'created_by'      => $gallery->created_by,
            ]);

            return $gallery;
        });
    }

    /**
     * Update an existing gallery.
     *
     * @param Gallery $gallery
     * @param array $data Updated data
     * @param int $updatedBy User ID updating the gallery
     * @return Gallery
     */
    public function updateGallery(Gallery $gallery, array $data, int $updatedBy): Gallery
    {
        $data['updated_by'] = $updatedBy;
        $gallery->update($data);

        Log::info('Gallery updated', ['gallery_id' => $gallery->id, 'updated_by' => $updatedBy]);

        return $gallery->fresh();
    }

    /**
     * Delete a gallery with cascade image cleanup.
     *
     * @param Gallery $gallery
     * @return bool
     */
    public function deleteGallery(Gallery $gallery): bool
    {
        return DB::transaction(function () use ($gallery) {
            // Delete all images and their files
            foreach ($gallery->images as $image) {
                $this->deleteGalleryImage($image);
            }

            Log::info('Gallery deleted', [
                'gallery_id' => $gallery->id,
                'image_count' => $gallery->image_count,
            ]);

            return $gallery->delete();
        });
    }

    /**
     * Publish/unpublish a gallery.
     *
     * @param Gallery $gallery
     * @param bool $publish
     * @param int $updatedBy
     * @return Gallery
     */
    public function publishGallery(Gallery $gallery, bool $publish, int $updatedBy): Gallery
    {
        return $this->updateGallery($gallery, ['is_published' => $publish], $updatedBy);
    }

    // =========================================================================
    // IMAGE UPLOAD & MANAGEMENT
    // =========================================================================

    /**
     * Upload an image to a gallery with optimization.
     *
     * @param Gallery $gallery
     * @param UploadedFile $file
     * @param string $title
     * @param string|null $description
     * @param int $uploadedBy User ID uploading the image
     *
     * @return GalleryImage
     * @throws \RuntimeException if upload fails
     */
    public function uploadImage(
        Gallery $gallery,
        UploadedFile $file,
        string $title,
        ?string $description = null,
        int $uploadedBy = 0
    ): GalleryImage {
        // Validate file
        $this->validateImageFile($file);

        return DB::transaction(function () use ($gallery, $file, $title, $description, $uploadedBy) {
            // Read image
            $image = $this->imageManager->read($file->getPathname());

            // Optimize image (reduce quality/size while maintaining visual quality)
            $image->scaleDown(width: 2000, height: 2000);
            $image->toJpeg(quality: self::IMAGE_QUALITY);

            // Generate unique filename
            $filename = $this->generateUniqueFilename($file);
            $storagePath = self::STORAGE_PATH . '/' . date('Y/m/d');

            // Store optimized image
            $path = Storage::disk(self::STORAGE_DISK)->putFileAs(
                $storagePath,
                $file,
                $filename
            );

            // Extract image dimensions
            $dimensions = $image->size();

            // Create gallery image record
            $galleryImage = $gallery->addImage([
                'title'        => $title,
                'description'  => $description,
                'image_path'   => $path,
                'image_name'   => $filename,
                'file_size'    => $file->getSize(),
                'mime_type'    => $file->getMimeType(),
                'width'        => $dimensions->width,
                'height'       => $dimensions->height,
                'uploaded_by'  => $uploadedBy,
            ]);

            // Increment gallery image count
            $gallery->incrementImageCount();

            Log::info('Image uploaded to gallery', [
                'gallery_id'  => $gallery->id,
                'image_id'    => $galleryImage->id,
                'filename'    => $filename,
                'file_size'   => $file->getSize(),
                'dimensions'  => "{$dimensions->width}x{$dimensions->height}",
                'uploaded_by' => $uploadedBy,
            ]);

            return $galleryImage;
        });
    }

    /**
     * Upload multiple images to a gallery.
     *
     * @param Gallery $gallery
     * @param array<UploadedFile> $files
     * @param array<string> $titles
     * @param array<string|null> $descriptions
     * @param int $uploadedBy
     *
     * @return array<GalleryImage>
     */
    public function uploadMultipleImages(
        Gallery $gallery,
        array $files,
        array $titles,
        array $descriptions = [],
        int $uploadedBy = 0
    ): array {
        $uploadedImages = [];

        foreach ($files as $index => $file) {
            try {
                $title = $titles[$index] ?? $file->getClientOriginalName();
                $description = $descriptions[$index] ?? null;

                $image = $this->uploadImage($gallery, $file, $title, $description, $uploadedBy);
                $uploadedImages[] = $image;
            } catch (\Exception $e) {
                Log::warning('Image upload failed', [
                    'file' => $file->getClientOriginalName(),
                    'error' => $e->getMessage(),
                ]);
            }
        }

        return $uploadedImages;
    }

    /**
     * Update image metadata (title, description).
     *
     * @param GalleryImage $image
     * @param array $data
     * @return GalleryImage
     */
    public function updateImage(GalleryImage $image, array $data): GalleryImage
    {
        $image->update($data);
        return $image->fresh();
    }

    /**
     * Delete an image and clean up its file.
     *
     * @param GalleryImage $image
     * @return bool
     */
    public function deleteGalleryImage(GalleryImage $image): bool
    {
        return DB::transaction(function () use ($image) {
            $gallery = $image->gallery;

            // Delete file from storage
            if ($image->image_path && Storage::disk(self::STORAGE_DISK)->exists($image->image_path)) {
                Storage::disk(self::STORAGE_DISK)->delete($image->image_path);
            }

            // Decrement gallery image count
            $gallery->decrementImageCount();

            Log::info('Gallery image deleted', [
                'image_id'   => $image->id,
                'gallery_id' => $gallery->id,
                'file_path'  => $image->image_path,
            ]);

            return $image->delete();
        });
    }

    /**
     * Reorder images in a gallery.
     *
     * @param Gallery $gallery
     * @param array<int> $imageIds Ordered array of image IDs
     * @return void
     */
    public function reorderImages(Gallery $gallery, array $imageIds): void
    {
        foreach ($imageIds as $index => $imageId) {
            GalleryImage::where('id', $imageId)
                ->where('gallery_id', $gallery->id)
                ->update(['sort_order' => $index]);
        }

        Log::info('Gallery images reordered', [
            'gallery_id' => $gallery->id,
            'image_count' => count($imageIds),
        ]);
    }

    // =========================================================================
    // VALIDATION & HELPERS
    // =========================================================================

    /**
     * Validate uploaded image file.
     *
     * @param UploadedFile $file
     * @throws \RuntimeException
     */
    protected function validateImageFile(UploadedFile $file): void
    {
        // Check file size
        if ($file->getSize() > self::MAX_FILE_SIZE) {
            throw new \RuntimeException(
                "File size exceeds maximum of 5MB. Uploaded: " . 
                $this->formatBytes($file->getSize())
            );
        }

        // Check MIME type
        if (!in_array($file->getMimeType(), self::ALLOWED_MIME_TYPES)) {
            throw new \RuntimeException(
                "Invalid file type. Allowed: JPEG, PNG, WebP, GIF"
            );
        }
    }

    /**
     * Generate unique filename for storage.
     *
     * @param UploadedFile $file
     * @return string
     */
    protected function generateUniqueFilename(UploadedFile $file): string
    {
        $timestamp = now()->timestamp;
        $random = str_pad(random_int(0, 9999), 4, '0', STR_PAD_LEFT);
        $extension = $file->getClientOriginalExtension();

        return "img_{$timestamp}_{$random}.{$extension}";
    }

    /**
     * Format bytes to human-readable size.
     *
     * @param int $bytes
     * @return string
     */
    protected function formatBytes(int $bytes): string
    {
        if ($bytes >= 1073741824) {
            return number_format($bytes / 1073741824, 2) . ' GB';
        } elseif ($bytes >= 1048576) {
            return number_format($bytes / 1048576, 2) . ' MB';
        } elseif ($bytes >= 1024) {
            return number_format($bytes / 1024, 2) . ' KB';
        }
        return $bytes . ' B';
    }
}
