<?php

namespace App\Services\Meet\Media;

use Agence104\LiveKit\EgressServiceClient;
use App\Models\Recording;
use App\Models\Session;
use Illuminate\Support\Facades\Log;
use Livekit\EncodedFileOutput;
use Livekit\EncodedFileType;
use Livekit\EgressInfo;
use Livekit\S3Upload;
use Livekit\WebhookConfig;
use RuntimeException;

class LiveKitEgressService
{
    public function isAvailable(): bool
    {
        if (! (bool) config('meet.recordings.egress.enabled', false)) {
            return false;
        }

        return app(LiveKitProvider::class)->isConfigured();
    }

    public function apiHost(): string
    {
        return app(LiveKitProvider::class)->apiHost();
    }

    public function startRoomRecording(Session $session, Recording $recording, bool $audioOnly = false): EgressInfo
    {
        $client = $this->client();
        $layout = $this->egressLayout($recording->layout);
        $output = $this->buildFileOutput($session, $recording);
        return $client->startRoomCompositeEgress(
            $session->media_room_name,
            $layout,
            $output,
            null,
            $audioOnly,
            false,
            '',
            \Livekit\AudioMixing::DEFAULT_MIXING,
            $this->egressWebhooks(),
        );
    }

    protected function startRoomRecordingWithRetry(Session $session, Recording $recording, bool $audioOnly): EgressInfo
    {
        $attempts = max(1, (int) config('meet.recordings.egress.start_attempts', 5));
        $lastError = null;

        for ($attempt = 1; $attempt <= $attempts; $attempt++) {
            try {
                return $this->startRoomRecording($session, $recording, $audioOnly);
            } catch (\Throwable $e) {
                $lastError = $e;
                if (! $this->isMissingRoomError($e) || $attempt === $attempts) {
                    throw $e;
                }

                app(LiveKitProvider::class)->createRoom($session->media_room_name);
                usleep(500_000);
            }
        }

        throw $lastError ?? new RuntimeException('LiveKit egress could not start.');
    }

    protected function isMissingRoomError(\Throwable $e): bool
    {
        return str_contains(strtolower($e->getMessage()), 'room does not exist');
    }

    public function stopEgress(string $egressId): EgressInfo
    {
        return $this->client()->stopEgress($egressId);
    }

    protected function client(): EgressServiceClient
    {
        return new EgressServiceClient(
            $this->apiHost(),
            (string) config('meet.media.livekit.api_key'),
            (string) config('meet.media.livekit.api_secret'),
        );
    }

    protected function buildFileOutput(Session $session, Recording $recording): EncodedFileOutput
    {
        $filepath = sprintf(
            'recordings/%s/%s.mp4',
            $session->uuid,
            $recording->uuid,
        );

        $output = new EncodedFileOutput([
            'file_type' => EncodedFileType::MP4,
            'filepath' => $filepath,
        ]);

        $s3 = $this->s3UploadConfig();
        if ($s3 !== null) {
            $output->setS3(new S3Upload($s3));
        }

        return $output;
    }

    /**
     * @return array<string, bool|string>|null
     */
    protected function s3UploadConfig(): ?array
    {
        $egress = config('meet.recordings.egress.s3', []);
        $bucket = (string) ($egress['bucket'] ?? '');

        if ($bucket === '') {
            return null;
        }

        $config = [
            'bucket' => $bucket,
            'region' => (string) ($egress['region'] ?? 'default'),
            'force_path_style' => (bool) ($egress['force_path_style'] ?? true),
        ];

        foreach (['access_key', 'secret', 'session_token', 'endpoint', 'assume_role_arn', 'assume_role_external_id'] as $key) {
            $value = (string) ($egress[$key] ?? '');
            if ($value !== '') {
                $config[$key] = $value;
            }
        }

        return $config;
    }

    protected function egressLayout(?string $layout): string
    {
        return match ($layout) {
            'speaker', 'screen' => 'speaker',
            default => 'grid',
        };
    }

    /**
     * @return list<WebhookConfig>
     */
    protected function egressWebhooks(): array
    {
        $url = (string) config('meet.recordings.egress.webhook_url', '');
        if ($url === '') {
            $url = rtrim((string) config('app.url'), '/').'/api/livekit/webhook';
        }

        return [new WebhookConfig(['url' => $url])];
    }

    public function tryStart(Session $session, Recording $recording): ?array
    {
        if (! $this->isAvailable()) {
            return null;
        }

        try {
            $audioOnly = $session->room->isAudioOnly();
            $info = $this->startRoomRecordingWithRetry($session, $recording, $audioOnly);
            $egressId = $info->getEgressId();

            if ($egressId === '') {
                throw new RuntimeException('LiveKit egress did not return an egress id.');
            }

            $filepath = sprintf('recordings/%s/%s.mp4', $session->uuid, $recording->uuid);

            return [
                'egress_id' => $egressId,
                'egress_filepath' => $filepath,
                'capture_mode' => 'egress',
                'audio_only' => $audioOnly,
            ];
        } catch (\Throwable $e) {
            Log::warning('LiveKit egress start failed; falling back to browser capture.', [
                'session_uuid' => $session->uuid,
                'recording_uuid' => $recording->uuid,
                'error' => $e->getMessage(),
            ]);

            return null;
        }
    }

    public function tryStop(?string $egressId): void
    {
        if (! filled($egressId)) {
            return;
        }

        try {
            $this->stopEgress($egressId);
        } catch (\Throwable $e) {
            Log::warning('LiveKit egress stop failed.', [
                'egress_id' => $egressId,
                'error' => $e->getMessage(),
            ]);
        }
    }
}
