<?php

declare(strict_types=1);

final class MyronVideoAssetRepository
{
    private const QUALITIES = ['1080p', '720p', '480p'];

    private ?MyronMediaAccessibilityAttachmentRepository $attachments = null;

    public function __construct(
        private readonly PDO $pdo,
        private readonly MyronFileRepository $files,
    ) {
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function listVideoAssets(): array
    {
        $statement = $this->pdo->query(
            'SELECT * FROM video_assets ORDER BY created_at DESC, title ASC'
        );

        $assets = [];
        foreach ($statement->fetchAll() as $row) {
            if (is_array($row)) {
                $assets[] = $this->decorateAsset($row);
            }
        }

        return $assets;
    }

    public function getVideoAssetById(string $assetId): ?array
    {
        $statement = $this->pdo->prepare('SELECT * FROM video_assets WHERE id = :id LIMIT 1');
        $statement->execute([':id' => $assetId]);
        $row = $statement->fetch();

        return is_array($row) ? $this->decorateAsset($row) : null;
    }

    /**
     * @param array<string, mixed> $input
     */
    public function createVideoAsset(array $input, string $actorUserId, ?string $fileId = null, string $quality = '1080p'): array
    {
        $assetId = myronGenerateId('vidasset');
        $now = myronNow();

        $statement = $this->pdo->prepare(
            'INSERT INTO video_assets (
                id,
                title,
                description,
                poster_file_id,
                captions_url,
                created_by_user_id,
                updated_by_user_id,
                created_at,
                updated_at
            ) VALUES (
                :id,
                :title,
                :description,
                :poster_file_id,
                :captions_url,
                :created_by_user_id,
                :updated_by_user_id,
                :created_at,
                :updated_at
            )'
        );
        $statement->execute([
            ':id' => $assetId,
            ':title' => trim((string) ($input['title'] ?? 'Video asset')),
            ':description' => trim((string) ($input['description'] ?? '')),
            ':poster_file_id' => $this->normalizeNullableId((string) ($input['poster_file_id'] ?? '')),
            ':captions_url' => trim((string) ($input['captions_url'] ?? '')),
            ':created_by_user_id' => $actorUserId,
            ':updated_by_user_id' => $actorUserId,
            ':created_at' => $now,
            ':updated_at' => $now,
        ]);

        if ($fileId !== null && $fileId !== '') {
            $this->saveRendition($assetId, $fileId, $quality);
        }

        $asset = $this->getVideoAssetById($assetId);
        if (!is_array($asset)) {
            throw new RuntimeException('Video asset could not be loaded after save.');
        }

        return $asset;
    }

    /**
     * @param array<string, mixed> $input
     */
    public function updateVideoAsset(string $assetId, array $input, string $actorUserId): void
    {
        $asset = $this->getVideoAssetById($assetId);
        if (!is_array($asset)) {
            throw new RuntimeException('The selected video asset could not be found.');
        }

        $statement = $this->pdo->prepare(
            'UPDATE video_assets
             SET title = :title,
                 description = :description,
                 poster_file_id = :poster_file_id,
                 captions_url = :captions_url,
                 updated_by_user_id = :updated_by_user_id,
                 updated_at = :updated_at
             WHERE id = :id'
        );
        $statement->execute([
            ':title' => trim((string) ($input['title'] ?? $asset['title'] ?? 'Video asset')),
            ':description' => trim((string) ($input['description'] ?? '')),
            ':poster_file_id' => $this->normalizeNullableId((string) ($input['poster_file_id'] ?? '')),
            ':captions_url' => array_key_exists('captions_url', $input)
                ? trim((string) ($input['captions_url'] ?? ''))
                : (string) ($asset['stored_captions_url'] ?? $asset['captions_url'] ?? ''),
            ':updated_by_user_id' => $actorUserId,
            ':updated_at' => myronNow(),
            ':id' => $assetId,
        ]);
    }

    public function addRendition(string $assetId, string $fileId, string $quality): void
    {
        if (!is_array($this->getVideoAssetById($assetId))) {
            throw new RuntimeException('The selected video asset could not be found.');
        }

        $this->saveRendition($assetId, $fileId, $quality);
    }

    public function deleteVideoAsset(string $assetId, bool $deleteFiles = true): void
    {
        $asset = $this->getVideoAssetById($assetId);
        if (!is_array($asset)) {
            throw new RuntimeException('The selected video asset could not be found.');
        }

        $fileIds = [];
        foreach (($asset['renditions'] ?? []) as $rendition) {
            if (is_array($rendition) && (string) ($rendition['file_id'] ?? '') !== '') {
                $fileIds[] = (string) $rendition['file_id'];
            }
        }

        $this->pdo->prepare('DELETE FROM video_asset_renditions WHERE video_asset_id = :id')->execute([':id' => $assetId]);
        $this->attachments()->deleteAttachment('video_asset', $assetId, 'captions', $deleteFiles);
        $this->pdo->prepare('DELETE FROM video_assets WHERE id = :id')->execute([':id' => $assetId]);

        if ($deleteFiles) {
            foreach (array_unique($fileIds) as $fileId) {
                $this->files->deleteFile($fileId);
            }
        }
    }

    /**
     * @param array<string, mixed> $row
     * @return array<string, mixed>
     */
    private function decorateAsset(array $row): array
    {
        $renditions = $this->listRenditions((string) ($row['id'] ?? ''));
        $captionAttachment = $this->attachments()->getAttachment('video_asset', (string) ($row['id'] ?? ''), 'captions');
        $storedCaptionsUrl = (string) ($row['captions_url'] ?? '');

        $row['renditions'] = $renditions;
        $row['rendition_count'] = count($renditions);
        $row['primary_rendition'] = $renditions[0] ?? null;
        $row['file_url'] = is_array($row['primary_rendition'] ?? null)
            ? (string) ($row['primary_rendition']['file_url'] ?? '')
            : '';
        $row['stored_captions_url'] = $storedCaptionsUrl;
        $row['caption_attachment'] = $captionAttachment;
        $row['captions_url'] = is_array($captionAttachment) && (string) ($captionAttachment['file_url'] ?? '') !== ''
            ? (string) $captionAttachment['file_url']
            : $storedCaptionsUrl;

        return $row;
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function listRenditions(string $assetId): array
    {
        $statement = $this->pdo->prepare(
            'SELECT * FROM video_asset_renditions
             WHERE video_asset_id = :asset_id
             ORDER BY CASE quality_label
                WHEN \'1080p\' THEN 1
                WHEN \'720p\' THEN 2
                WHEN \'480p\' THEN 3
                ELSE 4
             END'
        );
        $statement->execute([':asset_id' => $assetId]);

        $renditions = [];
        foreach ($statement->fetchAll() as $row) {
            if (!is_array($row)) {
                continue;
            }
            $file = $this->files->getFileById((string) ($row['file_id'] ?? ''));
            if (!is_array($file)) {
                continue;
            }

            $row['file'] = $file;
            $row['file_url'] = (string) ($file['file_url'] ?? '');
            $row['download_url'] = (string) ($file['download_url'] ?? '');
            $row['file_size_label'] = (string) ($file['file_size_label'] ?? '');
            $row['original_filename'] = (string) ($file['original_filename'] ?? '');
            $renditions[] = $row;
        }

        return $renditions;
    }

    private function saveRendition(string $assetId, string $fileId, string $quality): void
    {
        $normalizedQuality = $this->normalizeQuality($quality);
        $file = $this->files->getFileById($fileId);
        if (!is_array($file) || (string) ($file['media_class'] ?? '') !== 'video') {
            throw new RuntimeException('Video renditions must point to uploaded video files.');
        }

        $now = myronNow();
        $existing = $this->pdo->prepare(
            'SELECT id FROM video_asset_renditions
             WHERE video_asset_id = :video_asset_id AND quality_label = :quality_label
             LIMIT 1'
        );
        $existing->execute([
            ':video_asset_id' => $assetId,
            ':quality_label' => $normalizedQuality,
        ]);
        $existingRow = $existing->fetch();

        if (is_array($existingRow)) {
            $statement = $this->pdo->prepare(
                'UPDATE video_asset_renditions
                 SET file_id = :file_id, updated_at = :updated_at
                 WHERE id = :id'
            );
            $statement->execute([
                ':file_id' => $fileId,
                ':updated_at' => $now,
                ':id' => (string) ($existingRow['id'] ?? ''),
            ]);

            return;
        }

        $statement = $this->pdo->prepare(
            'INSERT INTO video_asset_renditions (
                id,
                video_asset_id,
                quality_label,
                file_id,
                created_at,
                updated_at
             ) VALUES (
                :id,
                :video_asset_id,
                :quality_label,
                :file_id,
                :created_at,
                :updated_at
             )'
        );
        $statement->execute([
            ':id' => myronGenerateId('vidrend'),
            ':video_asset_id' => $assetId,
            ':quality_label' => $normalizedQuality,
            ':file_id' => $fileId,
            ':created_at' => $now,
            ':updated_at' => $now,
        ]);
    }

    private function normalizeQuality(string $quality): string
    {
        $normalized = strtolower(trim($quality));
        if (!str_ends_with($normalized, 'p')) {
            $normalized .= 'p';
        }
        if (!in_array($normalized, self::QUALITIES, true)) {
            throw new RuntimeException('Unsupported video rendition quality.');
        }

        return $normalized;
    }

    private function normalizeNullableId(string $value): ?string
    {
        $trimmed = trim($value);

        return $trimmed === '' ? null : $trimmed;
    }

    private function attachments(): MyronMediaAccessibilityAttachmentRepository
    {
        if (!$this->attachments instanceof MyronMediaAccessibilityAttachmentRepository) {
            $this->attachments = new MyronMediaAccessibilityAttachmentRepository($this->pdo, $this->files);
        }

        return $this->attachments;
    }
}
