<?php

namespace App\Services\Meet;

use App\Models\Recording;
use App\Models\Session;
use App\Models\User;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use RuntimeException;

class RecordingService
{
    public function start(Session $session, User $host, string $layout = 'gallery'): Recording
    {
        $active = $session->recordings()->where('status', 'recording')->first();
        if ($active) {
            return $active;
        }

        $recording = Recording::create([
            'owner_ref' => $session->owner_ref,
            'session_id' => $session->id,
            'status' => 'recording',
            'layout' => $layout,
            'started_by_ref' => $host->ownerRef(),
            'started_at' => now(),
        ]);

        AuditLogger::record(
            $session->owner_ref,
            'recording.started',
            $session->room->organization_id,
            $host->ownerRef(),
            Recording::class,
            $recording->id,
        );

        return $recording;
    }

    public function stop(Recording $recording, string $actorRef): Recording
    {
        if ($recording->status !== 'recording') {
            return $recording;
        }

        $recording->update([
            'status' => 'processing',
            'ended_at' => now(),
            'duration_seconds' => $recording->started_at
                ? (int) $recording->started_at->diffInSeconds(now())
                : null,
        ]);

        AuditLogger::record(
            $recording->owner_ref,
            'recording.stopped',
            $recording->session->room->organization_id,
            $actorRef,
            Recording::class,
            $recording->id,
        );

        return $recording->fresh();
    }

    /** Store a browser-captured recording on Ladill storage (not LiveKit). */
    public function storeUpload(Recording $recording, UploadedFile $file): Recording
    {
        if ($recording->status !== 'processing') {
            return $recording;
        }

        $session = $recording->session;
        $extension = strtolower($file->getClientOriginalExtension() ?: $file->extension() ?: 'webm');
        if (! in_array($extension, ['webm', 'mp4', 'mkv'], true)) {
            $extension = 'webm';
        }

        $filename = 'recordings/'.$session->uuid.'/'.$recording->uuid.'.'.$extension;
        $disk = Storage::disk(config('meet.recordings.disk', 'local'));
        $disk->putFileAs(
            'recordings/'.$session->uuid,
            $file,
            $recording->uuid.'.'.$extension,
        );

        $size = $disk->size($filename) ?: $file->getSize();

        if ($size <= 0) {
            throw new RuntimeException('Recording upload was empty.');
        }

        $recording->update([
            'status' => 'ready',
            'storage_path' => $filename,
            'file_size' => $size,
            'thumbnail_path' => null,
        ]);

        AuditLogger::record(
            $recording->owner_ref,
            'recording.ready',
            $session->room->organization_id,
            null,
            Recording::class,
            $recording->id,
        );

        app(MeetNotificationService::class)->recordingReady($recording);

        app(\App\Services\Integrations\WebhookDispatcher::class)->recordingReady($recording);

        app(MeetBillingService::class)->chargeRecordingStorageInitial($recording->fresh());

        if ($session->room->setting('auto_ai_summary', false) || config('meet.ai.auto_summarize', true)) {
            app(TranscriptService::class)->ensureForSession($session, $recording);
        }

        return $recording->fresh();
    }

    public function fail(Recording $recording, string $reason, ?string $actorRef = null): Recording
    {
        if (! in_array($recording->status, ['recording', 'processing'], true)) {
            return $recording;
        }

        $recording->update([
            'status' => 'failed',
            'failure_reason' => $reason,
            'ended_at' => $recording->ended_at ?? now(),
            'duration_seconds' => $recording->duration_seconds
                ?? ($recording->started_at ? (int) $recording->started_at->diffInSeconds(now()) : null),
        ]);

        if ($actorRef) {
            AuditLogger::record(
                $recording->owner_ref,
                'recording.failed',
                $recording->session->room->organization_id,
                $actorRef,
                Recording::class,
                $recording->id,
            );
        }

        return $recording->fresh();
    }

    /** Fail recordings left in processing long after the meeting ended. */
    public function resolveProcessingState(Recording $recording): Recording
    {
        if ($recording->status !== 'processing') {
            return $this->ensureStorageValid($recording);
        }

        $timeoutMinutes = max(5, (int) config('meet.recordings.processing_timeout_minutes', 15));
        $staleAt = $recording->ended_at ?? $recording->updated_at;

        if ($staleAt && $staleAt->lt(now()->subMinutes($timeoutMinutes))) {
            return $this->fail(
                $recording,
                'Recording upload did not complete. Keep the host browser open briefly after ending the meeting so the file can upload.',
            );
        }

        return $recording;
    }

    public function failStaleProcessingRecordings(?int $timeoutMinutes = null): int
    {
        $timeoutMinutes = max(5, $timeoutMinutes ?? (int) config('meet.recordings.processing_timeout_minutes', 15));
        $cutoff = now()->subMinutes($timeoutMinutes);
        $failed = 0;

        Recording::query()
            ->where('status', 'processing')
            ->where(function ($query) use ($cutoff) {
                $query->where('ended_at', '<', $cutoff)
                    ->orWhere(function ($query) use ($cutoff) {
                        $query->whereNull('ended_at')->where('updated_at', '<', $cutoff);
                    });
            })
            ->orderBy('id')
            ->each(function (Recording $recording) use (&$failed) {
                $this->fail(
                    $recording,
                    'Recording upload did not complete. Keep the host browser open briefly after ending the meeting so the file can upload.',
                );
                $failed++;
            });

        return $failed;
    }

    /** Mark ready recordings with missing/empty files as failed. */
    public function ensureStorageValid(Recording $recording): Recording
    {
        if ($recording->status !== 'ready' || ! $recording->storage_path) {
            return $recording;
        }

        $disk = Storage::disk(config('meet.recordings.disk', 'local'));
        $size = $disk->exists($recording->storage_path) ? (int) $disk->size($recording->storage_path) : 0;

        if ($size > 0) {
            if ((int) ($recording->file_size ?? 0) !== $size) {
                $recording->update(['file_size' => $size]);
            }

            return $recording->fresh();
        }

        if ($recording->storage_path) {
            $disk->delete($recording->storage_path);
        }

        $recording->update([
            'status' => 'failed',
            'storage_path' => null,
            'file_size' => null,
            'failure_reason' => 'Recording file is missing or empty. Please record the meeting again.',
        ]);

        return $recording->fresh();
    }

    public function delete(Recording $recording, string $actorRef): void
    {
        if ($recording->storage_path) {
            Storage::disk(config('meet.recordings.disk', 'local'))->delete($recording->storage_path);
        }

        AuditLogger::record(
            $recording->owner_ref,
            'recording.deleted',
            $recording->session->room->organization_id,
            $actorRef,
            Recording::class,
            $recording->id,
        );

        $recording->delete();
    }
}
