<?php

declare(strict_types=1);

namespace App\Domain\Media;

use App\Models\Article;
use App\Models\Attachment;
use Illuminate\Support\Facades\Storage;

/**
 * Resolve / assign article covers from content, attachments, or template generation.
 */
class ArticleCoverService
{
    public const SOURCE_NONE = 'none';

    public const SOURCE_MANUAL = 'manual';

    public const SOURCE_ATTACHMENT = 'attachment';

    public const SOURCE_CONTENT_IMAGE = 'content_image';

    public const SOURCE_GENERATED = 'generated';

    public const STATUS_NONE = 'none';

    public const STATUS_PENDING = 'pending';

    public const STATUS_READY = 'ready';

    public const STATUS_FAILED = 'failed';

    public function __construct(
        protected ArticleCoverGenerator $generator,
    ) {}

    /**
     * @return array{disk: ?string, path: string, source: string}|null
     */
    public function resolve(Article $article, string $strategy = 'auto'): ?array
    {
        return match ($strategy) {
            'attachment' => $this->fromAttachments($article),
            'from_content', 'content_image' => $this->fromContent($article),
            'generate' => $this->generator->generate($article),
            default => $this->fromContent($article) ?? $this->fromAttachments($article),
        };
    }

    public function apply(Article $article, string $strategy = 'auto'): Article
    {
        $article->forceFill([
            'cover_status' => self::STATUS_PENDING,
        ])->save();

        try {
            $resolved = $this->resolve($article, $strategy);
        } catch (\Throwable $exception) {
            $article->forceFill([
                'cover_status' => self::STATUS_FAILED,
                'cover_source' => $strategy === 'generate' ? self::SOURCE_GENERATED : self::SOURCE_NONE,
                'cover_generated_at' => now(),
            ])->save();

            throw $exception;
        }

        if ($resolved === null) {
            $article->forceFill([
                'cover_disk' => null,
                'cover_path' => null,
                'cover_source' => self::SOURCE_NONE,
                'cover_status' => self::STATUS_FAILED,
                'cover_generated_at' => now(),
            ])->save();

            return $article->refresh();
        }

        $article->forceFill([
            'cover_disk' => $resolved['disk'],
            'cover_path' => $resolved['path'],
            'cover_source' => $resolved['source'],
            'cover_status' => self::STATUS_READY,
            'cover_generated_at' => now(),
        ])->save();

        return $article->refresh();
    }

    public function publicUrl(Article $article): ?string
    {
        if (! $article->hasCover()) {
            return null;
        }

        $path = (string) $article->cover_path;

        if ($article->cover_disk === 'url' || str_starts_with($path, 'http://') || str_starts_with($path, 'https://')) {
            return $path;
        }

        $disk = (string) ($article->cover_disk ?: config('larablog.attachments_disk', 'attachments'));

        try {
            return Storage::disk($disk)->url($path);
        } catch (\Throwable) {
            return url('/'.$path);
        }
    }

    /**
     * @return array{disk: ?string, path: string, source: string}|null
     */
    protected function fromContent(Article $article): ?array
    {
        $content = (string) $article->content;

        if (preg_match('/!\[[^\]]*\]\(attach:(\d+)\)/i', $content, $matches) === 1
            || preg_match('/\[attach=(\d+)\]/i', $content, $matches) === 1) {
            $attachment = Attachment::query()->find((int) $matches[1]);
            if ($attachment !== null && $this->isImage($attachment)) {
                return [
                    'disk' => $attachment->disk,
                    'path' => $attachment->path,
                    'source' => self::SOURCE_CONTENT_IMAGE,
                ];
            }
        }

        if (preg_match('/!\[[^\]]*\]\((https?:\/\/[^)\s]+)\)/i', $content, $matches) === 1) {
            return [
                'disk' => 'url',
                'path' => $matches[1],
                'source' => self::SOURCE_CONTENT_IMAGE,
            ];
        }

        if (preg_match('/<img[^>]+src=["\'](https?:\/\/[^"\']+)["\']/i', $content, $matches) === 1) {
            return [
                'disk' => 'url',
                'path' => $matches[1],
                'source' => self::SOURCE_CONTENT_IMAGE,
            ];
        }

        return null;
    }

    /**
     * @return array{disk: ?string, path: string, source: string}|null
     */
    protected function fromAttachments(Article $article): ?array
    {
        $attachment = $article->attachments()
            ->orderBy('id')
            ->get()
            ->first(fn (Attachment $item): bool => $this->isImage($item));

        if ($attachment === null) {
            return null;
        }

        return [
            'disk' => $attachment->disk,
            'path' => $attachment->path,
            'source' => self::SOURCE_ATTACHMENT,
        ];
    }

    protected function isImage(Attachment $attachment): bool
    {
        if (is_string($attachment->mime) && str_starts_with($attachment->mime, 'image/')) {
            return true;
        }

        $ext = strtolower(pathinfo((string) $attachment->filename, PATHINFO_EXTENSION));

        return in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg'], true);
    }
}
