<?php

namespace App\Http\Controllers;

use App\Jobs\GenerateImageCreativeJob;
use App\Jobs\RenderVideoCreativeJob;
use App\Models\Creative;
use App\Services\AI\VideoAvatarProviderFactory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
use Laravel\Ai\Models\Conversation;

class CreativeController extends Controller
{
    public function index(Request $request): View
    {
        $sortable = ['id', 'status', 'source', 'created'];
        $sort = in_array($request->query('sort'), $sortable, true) ? $request->query('sort') : 'created';
        $dir = $request->query('dir') === 'asc' ? 'asc' : 'desc';
        $perPage = in_array((int) $request->query('per_page'), [10, 25, 50], true) ? (int) $request->query('per_page') : 10;

        $query = $request->user()->creatives()
            ->when($request->filled('status') && $request->query('status') !== 'all', fn ($q) => $q->where('status', $request->query('status')))
            ->when($request->filled('source') && $request->query('source') !== 'all', fn ($q) => $q->where('source', $request->query('source')))
            ->when($request->filled('q'), fn ($q) => $q->where(fn ($q2) => $q2
                ->where('prompt', 'like', '%'.$request->query('q').'%')
                ->orWhere('product_url', 'like', '%'.$request->query('q').'%')
            ))
            ->orderBy($sort === 'created' ? 'created_at' : $sort, $dir);

        $creatives = $query->paginate($perPage)->withQueryString();

        $stats = [
            'all' => $request->user()->creatives()->count(),
            'completed' => $request->user()->creatives()->where('status', 'completed')->count(),
            'active' => $request->user()->creatives()->whereIn('status', ['queued', 'processing', 'generating'])->count(),
            'failed' => $request->user()->creatives()->where('status', 'failed')->count(),
        ];

        return view('creatives.index', [
            'creatives' => $creatives,
            'stats' => $stats,
            'sort' => $sort,
            'dir' => $dir,
            // The sidebar is the same component the chat uses, and these pages
            // never handed it anything, so it said "No chats yet" next to a
            // list of four on the page you had just come from.
            'conversations' => $this->conversations(),
        ]);
    }

    public function store(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'source' => 'required|in:prompt,product_url',
            'prompt' => 'required_if:source,prompt|nullable|string|max:2000',
            'product_url' => 'required_if:source,product_url|nullable|url|max:2000',
            'ratio' => 'required|in:1:1,4:5,9:16,16:9',
            'style' => 'nullable|string|max:255',
        ]);

        $creative = $request->user()->creatives()->create([
            'type' => 'image',
            'status' => 'queued',
            'source' => $validated['source'],
            'ratio' => $validated['ratio'],
            'style' => $validated['style'] ?? null,
            'product_url' => $validated['product_url'] ?? null,
            'prompt' => $validated['prompt'] ?? null,
        ]);

        try {
            GenerateImageCreativeJob::dispatch($creative->id);
        } catch (\Throwable $e) {
            // The job already records failure on the creative before rethrowing
            // (so queue workers can retry it); with the sync driver that rethrow
            // would otherwise surface as a 500 instead of the failed-status page.
        }

        return redirect()->route('creatives.show', $creative);
    }

    public function manualUpload(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'title' => ['required', 'string', 'max:100'],
            'project_name' => ['nullable', 'string', 'max:120'],
            'type' => ['required', 'in:image,video'],
            'file' => [
                'required',
                'file',
                Rule::when($request->input('type') === 'image', ['mimes:jpeg,jpg,png,webp,gif']),
                Rule::when($request->input('type') === 'video', ['mimes:mp4,webm,mov']),
            ],
        ]);

        /** @var UploadedFile $file */
        $file = $request->file('file');
        $directory = $validated['type'] === 'video' ? 'creatives/videos' : 'creatives/images';
        $path = $file->store($directory, 'public');

        $projectName = trim((string) ($validated['project_name'] ?? ''));

        $creative = $request->user()->creatives()->create([
            'type' => $validated['type'],
            'status' => 'completed',
            'render_progress' => 100,
            'source' => 'manual_upload',
            'ratio' => $this->ratioForUpload($file, $validated['type']),
            'title' => trim($validated['title']),
            'project_name' => $projectName !== '' ? $projectName : null,
            'asset_path' => $path,
        ]);

        return redirect()->route('creatives.show', $creative);
    }

    public function show(Creative $creative): View
    {
        $this->authorizeCreative($creative);

        $creative->load(['avatar', 'voice']);

        return view('creatives.show', ['creative' => $creative, 'conversations' => $this->conversations()]);
    }

    public function destroy(Creative $creative): RedirectResponse
    {
        $this->authorizeCreative($creative);

        $creative->delete();

        return redirect()->route('creatives.index');
    }

    public function regenerate(Creative $creative, VideoAvatarProviderFactory $providerFactory): RedirectResponse
    {
        $this->authorizeCreative($creative);

        if ($creative->source === 'manual_upload') {
            return redirect()->route('creatives.show', $creative);
        }

        $requestedDuration = null;
        if ($creative->type === 'video') {
            $requestedDuration = (int) ($creative->provider['requested_duration'] ?? $creative->duration);
            if ($requestedDuration === 29 && ($creative->provider['provider'] ?? null) === 'gemini-veo-vertex' && ($creative->provider['extend_calls'] ?? null) === 3) {
                $requestedDuration = 30;
            }

            if (! $providerFactory->supportsDuration($requestedDuration)) {
                return redirect()->route('creatives.show', $creative)
                    ->with('video_error', 'This video duration is no longer supported. Create a new 8, 15, or 30-second video.');
            }
        }

        $creative->update([
            'status' => $creative->type === 'video' ? 'queued' : 'generating',
            'failure_reason' => null,
            'asset_path' => null,
            'thumbnail_path' => null,
        ]);

        if ($creative->type === 'video') {
            RenderVideoCreativeJob::dispatch(
                $creative->id,
                $creative->avatar_id,
                $creative->voice_id,
                $creative->script,
                $creative->storyboard,
                $requestedDuration,
                $creative->ratio,
            );
        } else {
            try {
                GenerateImageCreativeJob::dispatch($creative->id);
            } catch (\Throwable $e) {
                // The job already records failure on the creative before rethrowing
                // (so queue workers can retry it); with the sync driver that rethrow
                // would otherwise surface as a 500 instead of the failed-status page.
            }
        }

        return redirect()->route('creatives.show', $creative);
    }

    /**
     * What the shared sidebar needs, matching what the chat shows.
     *
     * Everyone's, like the chat's own list: shared work, shared list.
     *
     * @return Collection<int, Conversation>
     */
    private function conversations(): Collection
    {
        return Conversation::query()->latest('updated_at')->limit(30)->get();
    }

    private function authorizeCreative(Creative $creative): void
    {
        abort_unless($creative->user_id === auth()->id(), 403);
    }

    private function ratioForUpload(UploadedFile $file, string $type): ?string
    {
        if ($type !== 'image') {
            return null;
        }

        $size = @getimagesize($file->getRealPath());

        if ($size === false) {
            return null;
        }

        return $this->simplifiedRatio($size[0], $size[1]);
    }

    private function simplifiedRatio(int $width, int $height): ?string
    {
        if ($width <= 0 || $height <= 0) {
            return null;
        }

        $divisor = $this->greatestCommonDivisor($width, $height);

        $ratioWidth = intdiv($width, $divisor);
        $ratioHeight = intdiv($height, $divisor);

        return $this->normalizedRatio($ratioWidth, $ratioHeight);
    }

    private function greatestCommonDivisor(int $a, int $b): int
    {
        while ($b !== 0) {
            [$a, $b] = [$b, $a % $b];
        }

        return $a === 0 ? 1 : $a;
    }

    /**
     * Snaps close-but-not-exact ratios (e.g. 1079x1920) to the nearest common
     * standard so uploads don't show odd values like "1079:1920" — matches
     * how ad platforms bucket dimensions into standard aspect ratios.
     */
    private function normalizedRatio(int $width, int $height): string
    {
        $standardRatios = [
            '1:1' => 1 / 1,
            '4:5' => 4 / 5,
            '9:16' => 9 / 16,
            '16:9' => 16 / 9,
            '4:3' => 4 / 3,
            '3:4' => 3 / 4,
            '2:3' => 2 / 3,
            '3:2' => 3 / 2,
        ];

        $actual = $width / $height;
        $closest = null;
        $smallestDiff = null;

        foreach ($standardRatios as $label => $value) {
            $diff = abs($actual - $value);

            if ($smallestDiff === null || $diff < $smallestDiff) {
                $smallestDiff = $diff;
                $closest = $label;
            }
        }

        // Only snap if it's a near match (within 1.5%); otherwise show the exact simplified ratio.
        if ($closest !== null && $smallestDiff <= 0.015) {
            return $closest;
        }

        return "{$width}:{$height}";
    }
}
