<?php

namespace App\Jobs;

use App\DTOs\VideoRenderResult;
use App\Exceptions\HiggsfieldRenderFailedException;
use App\Exceptions\HiggsfieldRenderPendingException;
use App\Exceptions\VideoDialogueException;
use App\Exceptions\VideoPromptFilteredException;
use App\Models\Avatar;
use App\Models\Creative;
use App\Models\Voice;
use App\Services\AI\HiggsfieldSeedanceVideoAvatarProvider;
use App\Services\AI\VideoAvatarProviderFactory;
use App\Services\Video\SpokenCaptionBurner;
use App\Support\VideoDialogue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Throwable;

/** Runs the selected video engine outside the web request. A failed Higgsfield render falls back to Veo. */
class RenderVideoCreativeJob implements ShouldQueue
{
    use Queueable;

    /** Not safely re-runnable: a retry would start a brand new (billable) vendor render rather than resuming the old one. */
    public int $tries = 1;

    /** Allows Vertex Veo base generation and multiple extensions to finish. */
    public int $timeout = 1500;

    public function __construct(
        public int $creativeId,
        public int $avatarId,
        public int $voiceId,
        public array $script,
        public array $storyboard,
        public int $duration,
        public string $ratio,
    ) {}

    public function handle(VideoAvatarProviderFactory $providerFactory): void
    {
        $creative = Creative::findOrFail($this->creativeId);

        if ($creative->status === 'completed') {
            return; // already rendered; safe to retry without duplicating work
        }

        $engine = $this->videoEngine($creative);
        $stored = is_array($creative->provider) ? $creative->provider : [];
        $resuming = $this->canResumeHiggsfield($engine, $stored);

        if ($resuming) {
            $creative->update([
                'status' => 'processing',
                'failure_reason' => null,
                'render_stage' => 'Waiting on Higgsfield',
            ]);
        } else {
            $creative->update([
                'status' => 'processing',
                'render_progress' => 3,
                'render_stage' => 'Preparing your video',
            ]);
        }

        try {
            $avatar = Avatar::findOrFail($this->avatarId);
            $voice = Voice::findOrFail($this->voiceId);
            if ($engine === HiggsfieldSeedanceVideoAvatarProvider::ENGINE) {
                $provider = app(HiggsfieldSeedanceVideoAvatarProvider::class);
            } else {
                $provider = $providerFactory->forDuration($this->duration);
            }

            $lastProgress = 3;
            $reportProgress = static function (int $percent, string $stage) use ($creative, &$lastProgress): void {
                $lastProgress = max($lastProgress, min(99, max(0, $percent)));

                Creative::whereKey($creative->id)->update([
                    'render_progress' => $lastProgress,
                    'render_stage' => $stage,
                ]);
            };

            $context = [
                'project_name' => $creative->project_name,
                'brief' => $creative->brief,
                'product_url' => $creative->product_url,
            ];

            if ($provider instanceof HiggsfieldSeedanceVideoAvatarProvider && $resuming) {
                $result = $provider->resume(
                    (string) $stored['status_url'],
                    (string) $stored['request_id'],
                    $stored,
                    $reportProgress,
                );
            } else {
                if ($provider instanceof HiggsfieldSeedanceVideoAvatarProvider) {
                    $provider->whenSubmitted(function (array $submission) use ($creative, $engine): void {
                        $this->rememberHiggsfieldSubmission($creative, $engine, $submission);
                    });
                }

                $result = $provider->render($avatar, $voice, $this->script, $this->storyboard, $this->duration, $this->ratio, $creative->style, $reportProgress, $context);
            }

            $spokenLine = $this->captionLine($result);
            if ($spokenLine !== '') {
                $reportProgress(97, 'Adding captions');
                app(SpokenCaptionBurner::class)->apply(
                    (string) config('creative.disk', 'public'),
                    $result->assetPath,
                    $spokenLine,
                );
            }

            $creative->update([
                'status' => 'completed',
                'asset_path' => $result->assetPath,
                'thumbnail_path' => $result->thumbnailPath,
                'provider' => [...$result->meta, 'engine' => $engine, 'requested_duration' => $this->duration],
                // Vertex extensions may produce a slightly different duration than requested.
                'duration' => $result->meta['duration'] ?? $this->duration,
                'failure_reason' => null,
                'render_progress' => 100,
                'render_stage' => 'Ready',
            ]);
        } catch (HiggsfieldRenderPendingException $exception) {
            $this->rememberHiggsfieldSubmission($creative, $engine, [
                'request_id' => $exception->requestId,
                'status_url' => $exception->statusUrl,
            ]);
            $creative->update([
                'status' => 'processing',
                'failure_reason' => null,
                'render_stage' => 'Waiting on Higgsfield',
            ]);
            $this->dispatchContinuation();
        } catch (HiggsfieldRenderFailedException $exception) {
            $this->fallBackToVeo($creative, $exception);
        } catch (Throwable $e) {
            // Log the real cause; never surface a raw vendor/exception message in the UI.
            logger()->error('Video render job failed', ['creative_id' => $this->creativeId, 'exception_type' => $e::class]);

            $creative->update([
                'status' => 'failed',
                'failure_reason' => match (true) {
                    $e instanceof VideoDialogueException => $e->getMessage(),
                    $e instanceof VideoPromptFilteredException => 'The video prompt was rejected by the provider safety filter. Please revise the description and try again.',
                    default => 'Video generation failed. Please try again.',
                },
                'render_stage' => 'Generation failed',
            ]);

            throw $e;
        }
    }

    private function captionLine(VideoRenderResult $result): string
    {
        $fitted = trim((string) ($result->meta['spoken_line'] ?? ''));
        if ($fitted !== '') {
            return $fitted;
        }

        $fallback = trim((string) ($this->script['spoken_line'] ?? ''));
        if ($fallback === '') {
            return '';
        }

        return VideoDialogue::limitToBudget($fallback, (int) ($result->meta['duration'] ?? $this->duration));
    }

    private function videoEngine(Creative $creative): string
    {
        $engine = is_array($creative->provider) ? ($creative->provider['engine'] ?? 'veo') : 'veo';

        if (! in_array($engine, ['veo', HiggsfieldSeedanceVideoAvatarProvider::ENGINE], true)) {
            throw new \InvalidArgumentException('Unsupported video engine.');
        }

        return $engine;
    }

    /**
     * Covers the case handle()'s own catch can't: the worker process itself being
     * killed (timeout, restart, OOM) mid-render. Without this, tries=1 + no catch
     * running leaves the creative stuck on "processing" forever with no way out in the UI.
     */
    public function failed(?Throwable $exception): void
    {
        $creative = Creative::find($this->creativeId);

        if ($creative === null || in_array($creative->status, ['completed', 'failed'], true)) {
            return;
        }

        $stored = is_array($creative->provider) ? $creative->provider : [];
        if ($this->canResumeHiggsfield($this->videoEngine($creative), $stored)
            && ! $exception instanceof VideoDialogueException
            && ! $exception instanceof VideoPromptFilteredException) {
            logger()->warning('Higgsfield render worker stopped; polling will continue', [
                'creative_id' => $this->creativeId,
                'request_id' => $stored['request_id'],
            ]);
            $creative->update([
                'status' => 'processing',
                'failure_reason' => null,
                'render_stage' => 'Waiting on Higgsfield',
            ]);
            $this->dispatchContinuation();

            return;
        }

        if ($exception !== null) {
            logger()->error('Video render job failed permanently', ['creative_id' => $this->creativeId, 'exception_type' => $exception::class]);
        }

        $creative->update([
            'status' => 'failed',
            'failure_reason' => match (true) {
                $exception instanceof VideoDialogueException => $exception->getMessage(),
                $exception instanceof VideoPromptFilteredException => 'The video prompt was rejected by the provider safety filter. Please revise the description and try again.',
                default => 'Video generation failed. Please try again.',
            },
            'render_stage' => 'Generation failed',
        ]);
    }

    /**
     * @param  array<string, mixed>  $stored
     */
    private function canResumeHiggsfield(string $engine, array $stored): bool
    {
        return $engine === HiggsfieldSeedanceVideoAvatarProvider::ENGINE
            && filled($stored['request_id'] ?? null)
            && filled($stored['status_url'] ?? null);
    }

    /**
     * @param  array<string, mixed>  $submission
     */
    private function rememberHiggsfieldSubmission(Creative $creative, string $engine, array $submission): void
    {
        $existing = is_array($creative->provider) ? $creative->provider : [];

        $creative->update([
            'provider' => [
                ...$existing,
                'engine' => $engine,
                'provider' => HiggsfieldSeedanceVideoAvatarProvider::ENGINE,
                'request_id' => $submission['request_id'] ?? ($existing['request_id'] ?? null),
                'status_url' => $submission['status_url'] ?? ($existing['status_url'] ?? null),
                'model' => $submission['model'] ?? ($existing['model'] ?? null),
                'duration' => $submission['duration'] ?? ($existing['duration'] ?? $this->duration),
                'spoken_line' => $submission['spoken_line'] ?? ($existing['spoken_line'] ?? null),
                'prompt_hash' => $submission['prompt_hash'] ?? ($existing['prompt_hash'] ?? null),
                'requested_duration' => $this->duration,
            ],
        ]);
        $creative->refresh();
    }

    private function fallBackToVeo(Creative $creative, HiggsfieldRenderFailedException $exception): void
    {
        $creative->refresh();
        $existing = is_array($creative->provider) ? $creative->provider : [];

        $requestId = $exception->requestId ?? ($existing['request_id'] ?? null);

        Log::channel('ai_video')->error('Higgsfield render failed; falling back to Veo', [
            'creative_id' => $creative->id,
            'request_id' => $requestId,
            'status_url' => $exception->statusUrl ?? ($existing['status_url'] ?? null),
            'http_status' => $exception->httpStatus,
            'reason' => $exception->reason,
            'body' => $exception->body,
        ]);

        $this->notifySlackOfHiggsfieldFailure($creative, $exception, is_string($requestId) ? $requestId : null);

        unset($existing['request_id'], $existing['status_url']);

        $creative->update([
            'status' => 'processing',
            'failure_reason' => null,
            'render_stage' => 'Switching to Veo',
            'provider' => [
                ...$existing,
                'engine' => 'veo',
                'fell_back_from' => HiggsfieldSeedanceVideoAvatarProvider::ENGINE,
                'higgsfield_failure_reason' => $exception->reason,
            ],
        ]);

        $this->dispatchContinuation(delaySeconds: 0);
    }

    private function notifySlackOfHiggsfieldFailure(Creative $creative, HiggsfieldRenderFailedException $exception, ?string $requestId): void
    {
        $webhookUrl = trim((string) config('services.slack.SLACK_WEBHOOK_URL'));

        if ($webhookUrl === '') {
            return;
        }

        $message = sprintf(
            "Higgsfield video failed for creative %d.\nRequest id: %s\n%s",
            $creative->id,
            $requestId !== null && $requestId !== '' ? $requestId : 'none',
            $this->higgsfieldFailureMessage($exception),
        );

        try {
            $response = Http::timeout(10)
                ->asJson()
                ->post($webhookUrl, ['text' => $message]);

            if ($response->status() !== 200) {
                Log::channel('ai_video')->warning('Slack alert failed', [
                    'creative_id' => $creative->id,
                    'http_status' => $response->status(),
                ]);
            }
        } catch (Throwable $slackException) {
            Log::channel('ai_video')->warning('Slack alert failed', [
                'creative_id' => $creative->id,
                'error' => $slackException->getMessage(),
            ]);
        }
    }

    private function higgsfieldFailureMessage(HiggsfieldRenderFailedException $exception): string
    {
        $detail = null;
        $body = $exception->body;

        if (is_array($body)) {
            foreach (['detail', 'message', 'error'] as $key) {
                $value = $body[$key] ?? null;

                if (is_string($value) && $value !== '') {
                    $detail = $value;
                    break;
                }
            }
        } elseif (is_string($body) && trim($body) !== '') {
            $detail = trim($body);
        }

        if ($detail === null || $detail === $exception->getMessage()) {
            return $exception->getMessage();
        }

        return $exception->getMessage()."\n".$detail;
    }

    private function dispatchContinuation(?int $delaySeconds = null): void
    {
        $interval = $delaySeconds ?? max(0, (int) config('creative.higgsfield_seedance.poll_interval_seconds', 10));
        $pending = self::dispatch(
            $this->creativeId,
            $this->avatarId,
            $this->voiceId,
            $this->script,
            $this->storyboard,
            $this->duration,
            $this->ratio,
        )->delay(now()->addSeconds($interval));

        if ($this->queue !== null) {
            $pending->onQueue($this->queue);
        }
    }
}
