<?php

namespace Openguru\OpenCartFramework\ImageTool;

use Intervention\Image\Image;
use Intervention\Image\ImageManager;
use RuntimeException;
use Symfony\Component\HttpFoundation\Response;

class ImageFactory
{
    private ?string $path = null;
    private string $fullPath;
    private array $modifications = [];

    private static array $modificatorMethods = [
        'resize' => [self::class, 'resizeModification'],
        'cover' => [self::class, 'coverModification'],
        'contain' => [self::class, 'containModification'],
    ];

    private string $imageDir;
    private string $siteUrl;
    private ImageManager $manager;


    private ?Image $image = null;
    private array $options = [];

    private static array $defaults = [
        'format' => 'webp',
        'quality' => 90,
        'no_image_path' => 'no_image.png',
        'cache_path' => 'cache/telecart',
    ];

    public function __construct(string $imageDir, string $siteUrl, string $driver, array $options = [])
    {
        $this->imageDir = rtrim($imageDir, '/');
        $this->siteUrl = rtrim($siteUrl, '/');
        $this->manager = new ImageManager(['driver' => $driver]);
        $this->options = array_merge(self::$defaults, $options);
    }

    /**
     * @throws ImageNotFoundException
     */
    public function make(?string $path = null): self
    {
        $this->modifications = [];
        $this->path = $path;
        $this->fullPath = $this->imageDir . '/' . $this->path;

        if (! $this->path || ! file_exists($this->fullPath)) {
            $this->path = $this->options['no_image_path'];
            $this->fullPath = $this->imageDir . '/' . $this->path;
        }

        if (file_exists($this->fullPath)) {
            $this->ensureFileExists($this->fullPath);
        }

        return $this;
    }

    public function crop(string $algorithm, ?int $width = null, ?int $height = null): self
    {
        return $this->{$algorithm}($width, $height);
    }

    public function resize(?int $width = null, ?int $height = null): self
    {
        $this->modifications['resize'] = [
            'width' => $width,
            'height' => $height,
        ];

        return $this;
    }

    public function cover(?int $width = null, ?int $height = null): self
    {
        $this->modifications['cover'] = [
            'width' => $width,
            'height' => $height,
        ];

        return $this;
    }

    public function contain(?int $width = null, ?int $height = null, $background = 'ffffff'): self
    {
        $this->modifications['contain'] = [
            'width' => $width,
            'height' => $height,
            'background' => $background,
        ];

        return $this;
    }

    public function url(?string $format = null, ?int $quality = null): string
    {
        $format = $format ?? $this->options['format'];
        $quality = $quality ?? $this->options['quality'];

        $newImage = $this->resolveNewImagePath($format);

        if (file_exists($newImage)) {
            return $this->buildUrl($newImage);
        }

        $this->image = $this->manager->make($this->fullPath);

        $this->applyModifications();

        $this->ensureDestinationFolterExists($newImage);

        $this->image->encode($format, $quality)->save($newImage);

        return $this->buildUrl($newImage);
    }

    private function buildUrl(string $path): string
    {
        $relativePath = substr($path, strlen($this->imageDir));

        return $this->siteUrl . '/image/' . ltrim($relativePath, '/');
    }

    public function response(?string $format = null, ?int $quality = null): Response
    {
        $format = $format ?? $this->options['format'];
        $quality = $quality ?? $this->options['quality'];

        $newImage = $this->resolveNewImagePath($format);

        if (file_exists($newImage)) {
            return $this->manager->make($newImage)->response();
        }

        $this->image = $this->manager->make($this->fullPath);

        $this->applyModifications();

        $this->ensureDestinationFolterExists($newImage);

        $this->image->encode($format, $quality)->save($newImage);

        return $this->image->response();
    }

    public function getRealSize(): array
    {
        $this->image = $this->manager->make($this->fullPath);

        return [$this->image->getWidth(), $this->image->getHeight()];
    }

    /**
     * @throws ImageNotFoundException
     */
    private function ensureFileExists(string $fullPath): void
    {
        if (! file_exists($fullPath)) {
            throw new ImageNotFoundException($fullPath);
        }
    }

    private function resolveNewImagePath(string $format): string
    {
        $filename = $this->path;

        $pathinfo = pathinfo($filename);
        $filename = $pathinfo['filename'];
        $basename = $pathinfo['basename'];
        $imagePath = rtrim($this->path, $basename);

        if ($this->modifications) {
            $filename .= '_' . substr(hash('SHA256', json_encode($this->modifications)), 0, 12);
        }

        $cachePath = $this->options['cache_path'] ?? 'cache/telecart';
        $cachePath = ltrim($cachePath, '/');

        return $this->imageDir . '/' . $cachePath . '/' . $imagePath . $filename . '.' . $format;
    }

    private function applyModifications(): void
    {
        foreach ($this->modifications as $name => $value) {
            $method = self::$modificatorMethods[$name][1] ?? null;
            if (! $method) {
                throw new RuntimeException('Modification for ' . $name . ' does not exist');
            }
            $this->{$method}(...array_values($value));
        }
    }

    private function resizeModification(?int $width = null, ?int $height = null): void
    {
        if ($width || $height) {
            $this->image->resize($width, $height, static fn($constraint) => $constraint->aspectRatio());
        }
    }

    private function coverModification(?int $width = null, ?int $height = null): void
    {
        if ($width || $height) {
            $this->image->fit($width, $height);
        }
    }

    private function containModification(?int $width = null, ?int $height = null, $background = 'ffffff'): void
    {
        if ($width || $height) {
            $this->image->resize($width, $height, static fn($constraint) => $constraint->aspectRatio());
            $this->image->resizeCanvas($width, $height, 'center', false, $background);
        }
    }

    private function ensureDestinationFolterExists(string $newImage): void
    {
        $path = pathinfo($newImage, PATHINFO_DIRNAME);
        if (! file_exists($path)) {
            if (! mkdir($path, 0755, true) && ! is_dir($path)) {
                throw new RuntimeException(sprintf('Directory "%s" was not created', $path));
            }
        }
    }
}
