<?php

declare(strict_types=1);

namespace App\Livewire\Admin\Blog\Posts;

use App\Models\BlogCategory;
use App\Models\BlogPost;
use App\Models\BlogTag;
use App\Services\Blog\RandomBlogPostReviewGenerator;
use App\Support\PublicStorage;
use Carbon\Carbon;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Computed;
use Livewire\Component;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
use Livewire\WithFileUploads;

final class Form extends Component
{
    use WithFileUploads;

    public ?int $editingId = null;

    public string $title = '';

    public string $excerpt = '';

    public string $content = '';

    public string $status = 'draft';

    /** @var string Format for HTML datetime-local (app timezone). */
    public string $published_at_input = '';

    public ?int $blog_category_id = null;

    public bool $is_featured = false;

    public int $featured_order = 0;

    public string $meta_title = '';

    public string $meta_description = '';

    /** @var array<int, int> */
    public array $tagIds = [];

    public ?TemporaryUploadedFile $coverImage = null;

    public ?TemporaryUploadedFile $inlineImage = null;

    public string $youtubeUrl = '';

    public ?string $existingCoverImage = null;

    public int $randomReviewsCount = 5;

    public ?string $randomReviewsDateFrom = null;

    public ?string $randomReviewsDateTo = null;

    public function mount(): void
    {
        $routePost = request()->route('post');
        $post = $routePost instanceof BlogPost ? $routePost : null;

        if ($post !== null) {
            $post->load('tags');
            $this->editingId = (int) $post->id;
            $this->title = $post->title;
            $this->excerpt = (string) ($post->excerpt ?? '');
            $this->content = $post->content;
            $this->status = $post->status;
            $this->blog_category_id = (int) $post->blog_category_id;
            $this->is_featured = $post->is_featured;
            $this->featured_order = (int) $post->featured_order;
            $this->meta_title = (string) ($post->meta_title ?? '');
            $this->meta_description = (string) ($post->meta_description ?? '');
            $this->tagIds = $post->tags->pluck('id')->all();
            $this->existingCoverImage = $post->cover_image ? PublicStorage::url($post->cover_image) : null;
            $this->published_at_input = $post->published_at !== null
                ? $post->published_at->timezone(config('app.timezone'))->format('Y-m-d\TH:i')
                : now()->timezone(config('app.timezone'))->format('Y-m-d\TH:i');
        }

        if ($this->published_at_input === '') {
            $this->published_at_input = now()->timezone(config('app.timezone'))->format('Y-m-d\TH:i');
        }

        if ($this->blog_category_id === null) {
            $id = BlogCategory::query()->orderBy('sort_order')->orderBy('name')->value('id');
            $this->blog_category_id = $id !== null ? (int) $id : null;
        }
    }

    public function layoutTitle(): string
    {
        return $this->editingId ? 'Edit blog post' : 'Add blog post';
    }

    #[Computed]
    public function categories()
    {
        return BlogCategory::query()
            ->orderBy('sort_order')
            ->orderBy('name')
            ->get();
    }

    #[Computed]
    public function createBlockedWithoutCategories(): bool
    {
        return $this->editingId === null && $this->categories->isEmpty();
    }

    #[Computed]
    public function allTags()
    {
        return BlogTag::query()->orderBy('name')->get();
    }

    protected function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'excerpt' => ['nullable', 'string', 'max:400'],
            'content' => ['required', 'string', 'min:20'],
            'status' => ['required', 'in:draft,published'],
            'published_at_input' => ['required_if:status,published', 'nullable', 'string', 'date_format:Y-m-d\TH:i'],
            'blog_category_id' => ['required', 'integer', 'exists:blog_categories,id'],
            'is_featured' => ['boolean'],
            'featured_order' => ['integer', 'min:0', 'max:99999'],
            'meta_title' => ['nullable', 'string', 'max:255'],
            'meta_description' => ['nullable', 'string', 'max:500'],
            'tagIds' => ['array'],
            'tagIds.*' => ['integer', 'exists:blog_tags,id'],
            'coverImage' => ['nullable', 'image', 'max:5120'],
        ];
    }

    public function insertUploadedImage(): void
    {
        $this->validate([
            'inlineImage' => ['required', 'image', 'max:5120'],
        ]);

        $path = $this->inlineImage?->store('blog/content', 'public');
        if (! $path) {
            return;
        }

        $url = PublicStorage::url($path);
        $this->content .= "\n\n".'<p><img src="'.$url.'" alt="Blog image" class="rounded-xl my-4 w-full h-auto" /></p>';
        $this->inlineImage = null;
    }

    public function generateRandomReviews(RandomBlogPostReviewGenerator $generator): void
    {
        if ($this->editingId === null) {
            return;
        }

        $this->validate([
            'randomReviewsCount' => ['required', 'integer', 'min:1', 'max:100'],
            'randomReviewsDateFrom' => [
                'nullable',
                'date',
                Rule::requiredIf(fn (): bool => filled($this->randomReviewsDateTo)),
            ],
            'randomReviewsDateTo' => [
                'nullable',
                'date',
                Rule::requiredIf(fn (): bool => filled($this->randomReviewsDateFrom)),
                'after_or_equal:randomReviewsDateFrom',
            ],
        ]);

        $post = BlogPost::query()->findOrFail($this->editingId);

        $from = null;
        $to = null;
        if (filled($this->randomReviewsDateFrom) && filled($this->randomReviewsDateTo)) {
            $tz = (string) config('app.timezone');
            $from = Carbon::parse((string) $this->randomReviewsDateFrom, $tz)->startOfDay();
            $to = Carbon::parse((string) $this->randomReviewsDateTo, $tz)->endOfDay();
        }

        $created = $generator->generate($post, $this->randomReviewsCount, $from, $to);

        session()->flash(
            'reviews_generated',
            "Added {$created} random reader review".($created === 1 ? '' : 's').' to this post.'
        );
    }

    public function insertYoutubeEmbed(): void
    {
        $this->validate([
            'youtubeUrl' => ['required', 'url'],
        ]);

        $videoId = $this->extractYoutubeId($this->youtubeUrl);
        if (! $videoId) {
            $this->addError('youtubeUrl', 'Please provide a valid YouTube URL.');

            return;
        }

        $embed = '<div class="my-6 aspect-video w-full overflow-hidden rounded-xl"><iframe class="h-full w-full" src="https://www.youtube.com/embed/'.$videoId.'" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></div>';

        $this->content .= "\n\n".$embed;
        $this->youtubeUrl = '';
    }

    private function extractYoutubeId(string $url): ?string
    {
        $patterns = [
            '/youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})/',
            '/youtu\.be\/([a-zA-Z0-9_-]{11})/',
            '/youtube\.com\/embed\/([a-zA-Z0-9_-]{11})/',
        ];

        foreach ($patterns as $pattern) {
            if (preg_match($pattern, $url, $matches) === 1) {
                return $matches[1];
            }
        }

        return null;
    }

    private function uniqueSlugFromTitle(string $title, ?int $ignorePostId = null): string
    {
        $base = Str::slug($title);
        if ($base === '') {
            $base = 'post';
        }
        $slug = $base;
        $n = 0;
        while (BlogPost::query()
            ->where('slug', $slug)
            ->when($ignorePostId, fn ($q) => $q->where('id', '!=', $ignorePostId))
            ->exists()) {
            $n++;
            $slug = $base.'-'.$n;
        }

        return $slug;
    }

    public function save(): void
    {
        $validated = $this->validate();

        $payload = [
            'author_id' => auth()->id(),
            'blog_category_id' => (int) $validated['blog_category_id'],
            'title' => $validated['title'],
            'excerpt' => $validated['excerpt'] ?: Str::limit(strip_tags($validated['content']), 220),
            'content' => $validated['content'],
            'status' => $validated['status'],
            'is_featured' => (bool) $validated['is_featured'],
            'featured_order' => (int) $validated['featured_order'],
            'meta_title' => $validated['meta_title'] !== '' ? $validated['meta_title'] : null,
            'meta_description' => $validated['meta_description'] !== '' ? $validated['meta_description'] : null,
        ];

        if ($this->coverImage) {
            $payload['cover_image'] = $this->coverImage->store('blog/covers', 'public');
        }

        if ($validated['status'] === 'published') {
            $payload['published_at'] = Carbon::createFromFormat(
                'Y-m-d\TH:i',
                $validated['published_at_input'],
                (string) config('app.timezone')
            );
        } else {
            $payload['published_at'] = null;
        }

        if ($this->editingId) {
            $existing = BlogPost::query()->findOrFail($this->editingId);
            $payload['slug'] = $existing->slug;
            if (! isset($payload['cover_image'])) {
                $payload['cover_image'] = $existing->cover_image;
            } elseif ($existing->cover_image) {
                Storage::disk('public')->delete($existing->cover_image);
            }
            $existing->update($payload);
            $existing->tags()->sync($validated['tagIds'] ?? []);
            session()->flash('success', 'Post updated.');
        } else {
            $payload['slug'] = $this->uniqueSlugFromTitle($validated['title']);
            $post = BlogPost::query()->create($payload);
            $post->tags()->sync($validated['tagIds'] ?? []);
            session()->flash('success', 'Post created.');
        }

        $this->redirect(route('admin.blog.posts.index'), navigate: true);
    }

    public function render()
    {
        return view('livewire.admin.blog.posts.form')
            ->layout('layouts.admin', ['title' => $this->layoutTitle()]);
    }
}
