<?php

namespace App\Services;

use App\Models\Service;
use App\Models\ServiceAgendaItem;
use App\Models\Setting;
use App\Models\Slide;
use Illuminate\Support\Facades\Storage;
use InvalidArgumentException;
use ProPresenter\Parser\PresentationBundle;
use ProPresenter\Parser\ProBundleWriter;
use ProPresenter\Parser\ProFileGenerator;
use RuntimeException;

class ProBundleExportService
{
    private const ALLOWED_BLOCK_TYPES = ['information', 'moderation', 'sermon'];

    public function __construct(
        private readonly MacroResolutionService $macroResolutionService,
        private readonly ServiceImageResolver $imageResolver,
        private readonly SlideMediaElementBuilder $slideMediaElementBuilder,
    ) {}

    public function generateBundle(Service $service, string $blockType): string
    {
        if (! in_array($blockType, self::ALLOWED_BLOCK_TYPES, true)) {
            throw new InvalidArgumentException('Ungültiger Blocktyp für .probundle Export.');
        }

        $slides = $service->slides()
            ->where('type', $blockType)
            ->orderBy('sort_order')
            ->get();

        $groupName = ucfirst($blockType);

        return $this->buildBundleFromSlides($slides, $groupName, $service, $blockType, $blockType);
    }

    public function generateAgendaItemBundle(ServiceAgendaItem $agendaItem): string
    {
        $agendaItem->loadMissing([
            'service',
            'slides',
            'serviceSong.song.arrangements.arrangementSections.section.slides',
            'serviceSong.song.arrangements.arrangementSections.section.label',
            'serviceSong.arrangement.arrangementSections.section.slides',
            'serviceSong.arrangement.arrangementSections.section.label',
        ]);

        $title = $agendaItem->title ?: 'Ablauf-Element';

        if ($agendaItem->serviceSong?->song_id && $agendaItem->serviceSong->song) {
            $song = $agendaItem->serviceSong->song;

            if ($this->countSongContentSlides($song) === 0) {
                throw new RuntimeException('Lied "'.$song->title.'" übersprungen: keine Inhaltsfolien.');
            }

            $selected = $agendaItem->serviceSong->arrangement;
            $parserSong = app(ProExportService::class)->generateParserSong($song, $agendaItem->service, $selected);
            $proFilename = self::safeFilename($song->title).'.pro';

            $songMediaFiles = [];
            $this->embedBackground($agendaItem->service, $songMediaFiles);

            $bundle = new PresentationBundle($parserSong, $proFilename, $songMediaFiles);
            $bundlePath = sys_get_temp_dir().'/'.uniqid('agenda-song-').'.probundle';
            ProBundleWriter::write($bundle, $bundlePath);

            return $bundlePath;
        }

        $slides = $agendaItem->slides()
            ->whereNull('deleted_at')
            ->orderBy('sort_order')
            ->get();

        return $this->buildBundleFromSlides(
            $slides,
            $title,
            $agendaItem->service,
            'agenda_item',
            $this->backgroundPartTypeForAgendaItem($agendaItem),
        );
    }

    /** @param  \Illuminate\Database\Eloquent\Collection<int, \App\Models\Slide>  $slides */
    private function buildBundleFromSlides(
        $slides,
        string $groupName,
        ?Service $service = null,
        ?string $partType = null,
        ?string $backgroundPartType = null,
    ): string {
        $slideData = [];
        $mediaFiles = [];
        $background = $this->backgroundData($service);
        $backgroundAttached = false;

        foreach ($slides as $slide) {
            $sourcePath = Storage::disk('public')->path($slide->stored_filename);
            if (! file_exists($sourcePath)) {
                continue;
            }

            $imageFilename = basename($slide->stored_filename);
            $imageContent = file_get_contents($sourcePath);
            if ($imageContent === false) {
                continue;
            }

            $mediaFiles[$imageFilename] = $imageContent;

            $singleSlideData = [
                'media' => $imageFilename,
                'format' => 'JPG',
                'label' => $slide->original_filename,
                'bundleRelative' => true,
            ];

            if ($this->shouldAttachBackground($slide, $backgroundPartType, $background)) {
                $singleSlideData['background'] = $background;
                $backgroundAttached = true;
            }

            $slideData[] = $singleSlideData;
        }

        // Second pass: position-aware macros over the ACTUALLY EMITTED slides so
        // first_slide/last_slide anchor to the true first/last visible slide even
        // when some source files were skipped (missing on disk). Using the raw
        // $slides->count() as total would make last_slide never match once a slide
        // is skipped.
        if ($service !== null && $partType !== null) {
            $total = count($slideData);
            foreach ($slideData as $i => &$sd) {
                $macros = $this->macroResolutionService->macrosForSlide(
                    $service,
                    $partType,
                    ['index' => $i, 'total' => $total, 'label_id' => null],
                );

                if (! empty($macros)) {
                    // ProPresenter parser currently supports one `macro` entry per slide
                    $sd['macro'] = $macros[0];
                }
            }
            unset($sd);
        }

        if ($backgroundAttached) {
            $this->embedBackground($service, $mediaFiles);
        }

        $groups = [
            [
                'name' => $groupName,
                'color' => [0, 0, 0, 1],
                'slides' => $slideData,
            ],
        ];

        $arrangements = [
            [
                'name' => 'normal',
                'groupNames' => [$groupName],
            ],
        ];

        $song = ProFileGenerator::generate($groupName, $groups, $arrangements);

        // Convert FOREGROUND (uploaded) image media actions into content fill.media
        // ELEMENTS; the BACKGROUND service-image action is left untouched as a
        // background-layer media action beneath the uploaded content.
        $this->slideMediaElementBuilder->replaceImageActionsWithElements($song);

        $proFilename = self::safeFilename($groupName).'.pro';

        $bundle = new PresentationBundle($song, $proFilename, $mediaFiles);
        $bundlePath = sys_get_temp_dir().'/'.uniqid('bundle-').'.probundle';
        ProBundleWriter::write($bundle, $bundlePath);

        return $bundlePath;
    }

    /**
     * Number of content slides in the song's default arrangement.
     * Content = SongSlide rows belonging to NON-locked sections (locked false/null)
     * that have a label. Locked prefix/postfix sections are ignored even if non-empty.
     */
    private function countSongContentSlides(\App\Models\Song $song): int
    {
        $song->loadMissing('arrangements.arrangementSections.section.slides', 'arrangements.arrangementSections.section.label');

        $defaultArr = $song->arrangements->firstWhere('is_default', true)
            ?? $song->arrangements->first();

        if ($defaultArr === null) {
            return 0;
        }

        $count = 0;
        $seenSectionIds = [];

        foreach ($defaultArr->arrangementSections->sortBy('order') as $arrangementSection) {
            $section = $arrangementSection->section;

            if ($section === null || $section->label === null) {
                continue;
            }

            if ($section->locked === true) {
                continue;
            }

            if (in_array($section->id, $seenSectionIds, true)) {
                continue;
            }
            $seenSectionIds[] = $section->id;

            $count += $section->slides->count();
        }

        return $count;
    }

    private function backgroundData(?Service $service): ?array
    {
        if ($this->backgroundSourcePath($service) === null) {
            return null;
        }

        return [
            'path' => ServiceImageResolver::BACKGROUND_EXPORT_NAME,
            'format' => 'JPG',
            'width' => 1920,
            'height' => 1080,
            'bundleRelative' => true,
        ];
    }

    /** Absolute filesystem path of the resolved background image, or null. */
    private function backgroundSourcePath(?Service $service): ?string
    {
        if ($service === null) {
            return null;
        }

        $background = $this->imageResolver->backgroundFor($service);

        if ($background === null || ! Storage::disk('public')->exists($background)) {
            return null;
        }

        return Storage::disk('public')->path($background);
    }

    /** Embed the resolved background image bytes into the bundle under the fixed export name. */
    private function embedBackground(?Service $service, array &$mediaFiles): void
    {
        $sourcePath = $this->backgroundSourcePath($service);

        if ($sourcePath === null || isset($mediaFiles[ServiceImageResolver::BACKGROUND_EXPORT_NAME])) {
            return;
        }

        $contents = @file_get_contents($sourcePath);
        if ($contents !== false) {
            $mediaFiles[ServiceImageResolver::BACKGROUND_EXPORT_NAME] = $contents;
        }
    }

    private function shouldAttachBackground(Slide $slide, ?string $backgroundPartType, ?array $background): bool
    {
        if ($background === null) {
            return false;
        }

        if ($backgroundPartType !== 'sermon' && $slide->type !== 'sermon') {
            return false;
        }

        return $slide->cover_mode !== true;
    }

    private function backgroundPartTypeForAgendaItem(ServiceAgendaItem $agendaItem): string
    {
        if ($agendaItem->slides->contains(fn (Slide $slide) => $slide->type === 'sermon')) {
            return 'sermon';
        }

        $sermonPatterns = Setting::get('agenda_sermon_matching');
        if ($sermonPatterns === null) {
            return 'agenda_item';
        }

        $patterns = array_map('trim', explode(',', $sermonPatterns));

        return app(AgendaMatcherService::class)->matchesAny($agendaItem->title, $patterns)
            ? 'sermon'
            : 'agenda_item';
    }

    private static function safeFilename(string $name): string
    {
        return preg_replace('/[^a-zA-Z0-9äöüÄÖÜß\-_ ]/', '', $name) ?: 'presentation';
    }
}
