<?php

declare(strict_types=1);

final class MyronMediaAccessibilityAttachmentRepository
{
    private const TARGET_VIDEO_ASSET = 'video_asset';
    private const TARGET_AUDIO_FILE = 'audio_file';
    private const KIND_CAPTIONS = 'captions';
    private const KIND_TRANSCRIPT = 'transcript';

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

    /**
     * @return array<string, mixed>|null
     */
    public function getAttachment(string $targetType, string $targetId, string $attachmentKind): ?array
    {
        $row = $this->getRawAttachment(
            $this->normalizeTargetType($targetType),
            trim($targetId),
            $this->normalizeAttachmentKind($attachmentKind)
        );

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

    /**
     * @param array<string, mixed> $metadata
     * @return array<string, mixed>
     */
    public function attachOrReplace(
        string $targetType,
        string $targetId,
        string $attachmentKind,
        string $fileId,
        string $actorUserId,
        array $metadata = []
    ): array {
        $normalizedTargetType = $this->normalizeTargetType($targetType);
        $normalizedTargetId = trim($targetId);
        $normalizedKind = $this->normalizeAttachmentKind($attachmentKind);
        $normalizedFileId = trim($fileId);
        $normalizedActorId = trim($actorUserId);

        if ($normalizedActorId === '') {
            throw new RuntimeException('Accessibility attachment changes require a valid actor.');
        }

        $this->assertTargetExists($normalizedTargetType, $normalizedTargetId);
        $this->assertAttachmentFileIsSafe($normalizedFileId);

        $language = $this->normalizeLanguage((string) ($metadata['language'] ?? 'en'));
        $label = $this->normalizeLabel((string) ($metadata['label'] ?? $this->defaultLabelForKind($normalizedKind)));
        $now = myronNow();
        $previousFileId = null;

        $this->pdo->beginTransaction();
        try {
            $existing = $this->getRawAttachment($normalizedTargetType, $normalizedTargetId, $normalizedKind);
            if (is_array($existing)) {
                $previousFileId = (string) ($existing['file_id'] ?? '');
                $statement = $this->pdo->prepare(
                    'UPDATE media_accessibility_attachments
                     SET file_id = :file_id,
                         language = :language,
                         label = :label,
                         updated_by_user_id = :updated_by_user_id,
                         updated_at = :updated_at
                     WHERE id = :id'
                );
                $statement->execute([
                    ':file_id' => $normalizedFileId,
                    ':language' => $language,
                    ':label' => $label,
                    ':updated_by_user_id' => $normalizedActorId,
                    ':updated_at' => $now,
                    ':id' => (string) ($existing['id'] ?? ''),
                ]);
            } else {
                $statement = $this->pdo->prepare(
                    'INSERT INTO media_accessibility_attachments (
                        id,
                        target_type,
                        target_id,
                        attachment_kind,
                        file_id,
                        language,
                        label,
                        created_by_user_id,
                        updated_by_user_id,
                        created_at,
                        updated_at
                    ) VALUES (
                        :id,
                        :target_type,
                        :target_id,
                        :attachment_kind,
                        :file_id,
                        :language,
                        :label,
                        :created_by_user_id,
                        :updated_by_user_id,
                        :created_at,
                        :updated_at
                    )'
                );
                $statement->execute([
                    ':id' => myronGenerateId('mediaatt'),
                    ':target_type' => $normalizedTargetType,
                    ':target_id' => $normalizedTargetId,
                    ':attachment_kind' => $normalizedKind,
                    ':file_id' => $normalizedFileId,
                    ':language' => $language,
                    ':label' => $label,
                    ':created_by_user_id' => $normalizedActorId,
                    ':updated_by_user_id' => $normalizedActorId,
                    ':created_at' => $now,
                    ':updated_at' => $now,
                ]);
            }
            $this->pdo->commit();
        } catch (Throwable $exception) {
            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }
            throw $exception;
        }

        if ($previousFileId !== null && $previousFileId !== '' && $previousFileId !== $normalizedFileId) {
            $this->deleteFileIfUnreferenced($previousFileId);
        }

        $attachment = $this->getAttachment($normalizedTargetType, $normalizedTargetId, $normalizedKind);
        if (!is_array($attachment)) {
            throw new RuntimeException('Accessibility attachment could not be loaded after save.');
        }

        return $attachment;
    }

    public function deleteAttachment(string $targetType, string $targetId, string $attachmentKind, bool $deleteStoredFile = true): void
    {
        $normalizedTargetType = $this->normalizeTargetType($targetType);
        $normalizedTargetId = trim($targetId);
        $normalizedKind = $this->normalizeAttachmentKind($attachmentKind);
        $existing = $this->getRawAttachment($normalizedTargetType, $normalizedTargetId, $normalizedKind);
        if (!is_array($existing)) {
            return;
        }

        $fileId = (string) ($existing['file_id'] ?? '');
        $statement = $this->pdo->prepare('DELETE FROM media_accessibility_attachments WHERE id = :id');
        $statement->execute([':id' => (string) ($existing['id'] ?? '')]);

        if ($deleteStoredFile && $fileId !== '') {
            $this->deleteFileIfUnreferenced($fileId);
        }
    }

    public function renderableUrlForTarget(string $targetType, string $targetId, string $attachmentKind): string
    {
        $attachment = $this->getAttachment($targetType, $targetId, $attachmentKind);
        if (!is_array($attachment)) {
            return '';
        }

        return (string) ($attachment['file_url'] ?? '');
    }

    /**
     * @return array<string, mixed>|null
     */
    private function getRawAttachment(string $targetType, string $targetId, string $attachmentKind): ?array
    {
        if ($targetId === '') {
            return null;
        }

        $statement = $this->pdo->prepare(
            'SELECT * FROM media_accessibility_attachments
             WHERE target_type = :target_type
               AND target_id = :target_id
               AND attachment_kind = :attachment_kind
             LIMIT 1'
        );
        $statement->execute([
            ':target_type' => $targetType,
            ':target_id' => $targetId,
            ':attachment_kind' => $attachmentKind,
        ]);
        $row = $statement->fetch(PDO::FETCH_ASSOC);

        return is_array($row) ? $row : null;
    }

    /**
     * @param array<string, mixed> $row
     * @return array<string, mixed>
     */
    private function decorateAttachment(array $row): array
    {
        $file = $this->files->getFileById((string) ($row['file_id'] ?? ''));
        $row['file'] = is_array($file) ? $file : null;
        $row['file_url'] = is_array($file) ? (string) ($file['file_url'] ?? '') : '';
        $row['download_url'] = is_array($file) ? (string) ($file['download_url'] ?? '') : '';
        $row['original_filename'] = is_array($file) ? (string) ($file['original_filename'] ?? '') : '';

        return $row;
    }

    private function assertTargetExists(string $targetType, string $targetId): void
    {
        if ($targetId === '') {
            throw new RuntimeException('Accessibility attachments require a target.');
        }

        if ($targetType === self::TARGET_VIDEO_ASSET) {
            $statement = $this->pdo->prepare('SELECT id FROM video_assets WHERE id = :id LIMIT 1');
            $statement->execute([':id' => $targetId]);
            if (!is_array($statement->fetch(PDO::FETCH_ASSOC))) {
                throw new RuntimeException('The selected video asset could not be found.');
            }

            return;
        }

        $file = $this->files->getFileById($targetId);
        if (!is_array($file) || (string) ($file['media_class'] ?? '') !== 'audio') {
            throw new RuntimeException('Audio accessibility attachments require an uploaded audio file target.');
        }
    }

    private function assertAttachmentFileIsSafe(string $fileId): void
    {
        if ($fileId === '') {
            throw new RuntimeException('Accessibility attachments require a file.');
        }

        $file = $this->files->getFileById($fileId);
        if (!is_array($file) || !$this->files->isAccessibilityAttachmentFile($file)) {
            throw new RuntimeException('Accessibility attachments must be .vtt, .srt, or .txt text files.');
        }
    }

    private function deleteFileIfUnreferenced(string $fileId): void
    {
        $statement = $this->pdo->prepare(
            'SELECT COUNT(*) FROM media_accessibility_attachments WHERE file_id = :file_id'
        );
        $statement->execute([':file_id' => $fileId]);
        if ((int) $statement->fetchColumn() > 0) {
            return;
        }

        if (is_array($this->files->getFileById($fileId))) {
            $this->files->deleteFile($fileId);
        }
    }

    private function normalizeTargetType(string $targetType): string
    {
        $normalized = strtolower(trim($targetType));
        $normalized = str_replace('-', '_', $normalized);

        return match ($normalized) {
            'video', self::TARGET_VIDEO_ASSET => self::TARGET_VIDEO_ASSET,
            'audio', 'file', self::TARGET_AUDIO_FILE => self::TARGET_AUDIO_FILE,
            default => throw new RuntimeException('Unsupported accessibility attachment target type: ' . $targetType),
        };
    }

    private function normalizeAttachmentKind(string $attachmentKind): string
    {
        $normalized = strtolower(trim($attachmentKind));
        $normalized = str_replace('-', '_', $normalized);

        return match ($normalized) {
            'caption', self::KIND_CAPTIONS => self::KIND_CAPTIONS,
            self::KIND_TRANSCRIPT, 'transcripts' => self::KIND_TRANSCRIPT,
            default => throw new RuntimeException('Unsupported accessibility attachment kind: ' . $attachmentKind),
        };
    }

    private function normalizeLanguage(string $language): string
    {
        $normalized = strtolower(trim($language));
        if ($normalized === '') {
            return 'en';
        }

        $normalized = preg_replace('/[^a-z0-9_-]+/', '', $normalized) ?? 'en';

        return substr($normalized === '' ? 'en' : $normalized, 0, 16);
    }

    private function normalizeLabel(string $label): string
    {
        $normalized = trim($label);

        return substr($normalized, 0, 120);
    }

    private function defaultLabelForKind(string $attachmentKind): string
    {
        return $attachmentKind === self::KIND_TRANSCRIPT ? 'Transcript' : 'Captions';
    }
}
