<?php

namespace App\Http\Controllers;

use App\Models\Service;
use App\Models\ServiceAgendaItem;
use App\Models\ServiceMacroOverride;
use App\Models\Setting;
use App\Models\Slide;
use App\Models\Song;
use App\Services\AgendaMatcherService;
use App\Services\MacroResolutionService;
use App\Services\ProBundleExportService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
use Inertia\Response;
use RuntimeException;
use Symfony\Component\HttpFoundation\BinaryFileResponse;

class ServiceController extends Controller
{
    public function index(): Response
    {
        $archived = request()->boolean('archived');

        $query = Service::query();
        if ($archived) {
            $query->whereDate('date', '<', Carbon::today())
                ->orderByDesc('date');
        } else {
            $query->whereDate('date', '>=', Carbon::today())
                ->orderBy('date');
        }

        $sermonPatterns = Setting::get('agenda_sermon_matching');
        $matcher = $sermonPatterns ? app(AgendaMatcherService::class) : null;

        $services = $query
            ->withCount([
                'serviceSongs as songs_total_count',
                'serviceSongs as songs_mapped_count' => fn ($query) => $query->whereNotNull('song_id'),
                'serviceSongs as songs_arranged_count' => fn ($query) => $query->whereNotNull('song_arrangement_id'),
            ])
            ->addSelect([
                'has_sermon_slides' => Slide::query()
                    ->selectRaw('CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END')
                    ->whereColumn('slides.service_id', 'services.id')
                    ->where('slides.type', 'sermon'),
                'info_slides_count' => Slide::query()
                    ->selectRaw('COUNT(*)')
                    ->where('slides.type', 'information')
                    ->whereNull('slides.deleted_at')
                    ->where(function ($query) {
                        $query
                            ->whereNull('slides.service_id')
                            ->orWhereColumn('slides.service_id', 'services.id');
                    })
                    ->where(function ($query) {
                        $query->whereNull('slides.expire_date')
                            ->orWhereColumn('slides.expire_date', '>=', 'services.date');
                    })
                    ->when(
                        ! $archived,
                        fn ($q) => $q
                            ->whereColumn(DB::raw('DATE(slides.uploaded_at)'), '<=', 'services.date')
                    ),
                'agenda_slides_count' => ServiceAgendaItem::query()
                    ->selectRaw('COUNT(*)')
                    ->whereColumn('service_agenda_items.service_id', 'services.id')
                    ->whereNull('service_agenda_items.service_song_id')
                    ->whereExists(
                        Slide::query()
                            ->selectRaw('1')
                            ->whereColumn('slides.service_agenda_item_id', 'service_agenda_items.id')
                            ->whereNull('slides.deleted_at')
                    ),
            ])
            ->with(['agendaItems' => fn ($q) => $q->whereNull('service_song_id')])
            ->with(['agendaItems.slides' => fn ($q) => $q->whereNull('deleted_at')])
            ->get()
            ->map(function (Service $service) use ($matcher, $sermonPatterns) {
                // Determine sermon slides status
                $hasSermonSlides = (bool) $service->has_sermon_slides;
                if ($matcher && $sermonPatterns) {
                    $sermonItems = $service->agendaItems->filter(
                        fn (ServiceAgendaItem $item) => $matcher->matchesAny(
                            $item->title,
                            array_map('trim', explode(',', $sermonPatterns))
                        )
                    );
                    if ($sermonItems->isNotEmpty()) {
                        $hasSermonSlides = $sermonItems->contains(
                            fn (ServiceAgendaItem $item) => $item->slides->isNotEmpty()
                        );
                    }
                }

                return [
                    'id' => $service->id,
                    'cts_event_id' => $service->cts_event_id,
                    'title' => $service->title,
                    'date' => $service->date?->toDateString(),
                    'preacher_name' => $service->preacher_name,
                    'beamer_tech_name' => $service->beamer_tech_name,
                    'last_synced_at' => $service->last_synced_at?->toJSON(),
                    'updated_at' => $service->updated_at?->toJSON(),
                    'finalized_at' => $service->finalized_at?->toJSON(),
                    'songs_total_count' => (int) $service->songs_total_count,
                    'songs_mapped_count' => (int) $service->songs_mapped_count,
                    'songs_arranged_count' => (int) $service->songs_arranged_count,
                    'has_sermon_slides' => $hasSermonSlides,
                    'info_slides_count' => (int) $service->info_slides_count,
                    'agenda_slides_count' => (int) $service->agenda_slides_count,
                    'has_agenda' => (bool) $service->has_agenda,
                ];
            })
            ->values();

        return Inertia::render('Services/Index', [
            'services' => $services,
            'archived' => $archived,
        ]);
    }

    public function edit(Service $service, \App\Services\ServiceImageResolver $imageResolver): Response
    {
        $service->load([
            'serviceSongs' => fn ($query) => $query->orderBy('order'),
            'serviceSongs.song',
            'serviceSongs.song.sections.label',
            'serviceSongs.song.sections.slides',
            'serviceSongs.song.arrangements.arrangementSections.section.label',
            'serviceSongs.song.arrangements.arrangementSections.section.slides',
            'serviceSongs.arrangement',
            'slides',
            'agendaItems' => fn ($q) => $q->orderBy('sort_order'),
            'agendaItems.slides',
            'agendaItems.serviceSong.song.sections.label',
            'agendaItems.serviceSong.song.sections.slides',
            'agendaItems.serviceSong.song.arrangements.arrangementSections.section.slides',
            'agendaItems.serviceSong.song.arrangements.arrangementSections.section.label',
            'agendaItems.serviceSong.arrangement.arrangementSections.section.label',
        ]);

        $songsCatalog = Song::query()
            ->withCount(['sections as content_slides_count' => fn ($q) => $q->has('slides')])
            ->orderBy('title')
            ->get(['id', 'title', 'ccli_id', 'has_translation'])
            ->map(fn (Song $song) => [
                'id' => $song->id,
                'title' => $song->title,
                'ccli_id' => $song->ccli_id,
                'has_translation' => $song->has_translation,
                'has_content' => (int) $song->content_slides_count > 0,
            ])
            ->values();

        $informationSlides = Slide::query()
            ->where('type', 'information')
            ->whereNull('deleted_at')
            ->where(function ($query) use ($service) {
                $query
                    ->whereNull('service_id')
                    ->orWhere('service_id', $service->id);
            })
            ->when(
                $service->date,
                fn ($query) => $query
                    ->where(function ($q) use ($service) {
                        $q->whereNull('expire_date')
                            ->orWhereDate('expire_date', '>=', $service->date);
                    })
                    ->when(
                        $service->date->isFuture() || $service->date->isToday(),
                        fn ($q) => $q->whereDate('uploaded_at', '<=', $service->date)
                    )
            )
            ->orderByDesc('uploaded_at')
            ->get();

        $moderationSlides = $service->slides
            ->where('type', 'moderation')
            ->sortByDesc('uploaded_at')
            ->values();

        $sermonSlides = $service->slides
            ->where('type', 'sermon')
            ->sortByDesc('uploaded_at')
            ->values();

        $prevService = Service::where('date', '<', $service->date->toDateString())
            ->orderByDesc('date')
            ->first(['id', 'title', 'date']);

        $nextService = Service::where('date', '>', $service->date->toDateString())
            ->orderBy('date')
            ->first(['id', 'title', 'date']);

        // Load agenda settings
        $agendaSettings = [
            'start_title' => Setting::get('agenda_start_title'),
            'end_title' => Setting::get('agenda_end_title'),
            'announcement_position' => Setting::get('agenda_announcement_position'),
            'sermon_matching' => Setting::get('agenda_sermon_matching'),
        ];

        // Filter agenda items (exclude before-event, apply boundary filtering)
        $matcher = app(AgendaMatcherService::class);
        $agendaItemsArray = $service->agendaItems
            ->filter(fn ($i) => ! $i->is_before_event)
            ->values()
            ->all();
        $filteredItems = $matcher->filterBetween(
            $agendaItemsArray,
            $agendaSettings['start_title'],
            $agendaSettings['end_title']
        );

        // Add computed flags to each agenda item
        $announcementPatterns = $agendaSettings['announcement_position'];
        $sermonPatterns = $agendaSettings['sermon_matching'];
        $agendaItemsMapped = array_map(function ($item) use ($matcher, $announcementPatterns, $sermonPatterns) {
            $arr = $item->toArray();
            $arr['is_announcement_position'] = $announcementPatterns
                ? $matcher->matchesAny($item->title, array_map('trim', explode(',', $announcementPatterns)))
                : false;
            $arr['is_sermon'] = $sermonPatterns
                ? $matcher->matchesAny($item->title, array_map('trim', explode(',', $sermonPatterns)))
                : false;

            // Inject `groups` into the nested song so the frontend ArrangementDialog
            // can resolve all sections (including those not in the default arrangement).
            if (isset($arr['service_song']['song'])) {
                $song = $item->serviceSong?->song;
                if ($song !== null) {
                    $arr['service_song']['song']['groups'] = $this->collectSongLabels($song)->toArray();
                }
            }

            return $arr;
        }, $filteredItems);

        // Macro resolution per part type (for icons + Anpassen/Standard panel)
        $resolver = app(MacroResolutionService::class);
        $macros_per_part = [];
        foreach (['information', 'moderation', 'sermon', 'song', 'agenda_item', 'abspann'] as $partType) {
            $assignments = $resolver->resolveAssignmentsForPart($service, $partType);
            $isOverridden = ServiceMacroOverride::where('service_id', $service->id)
                ->where('part_type', $partType)
                ->exists();
            $hasWarning = $assignments->contains(
                fn ($a) => $a->macro?->isHidden() || ($a->position === 'by_label' && $a->label?->isHidden())
            );
            $macros_per_part[$partType] = [
                'count' => $assignments->count(),
                'is_overridden' => $isOverridden,
                'has_warning' => $hasWarning,
                'assignments' => $assignments->map(fn ($a) => [
                    'id' => $a->id,
                    'macro_id' => $a->macro_id,
                    'macro_name' => $a->macro?->name,
                    'macro_color' => $a->macro?->color,
                    'macro_hidden' => $a->macro?->isHidden(),
                    'position' => $a->position,
                    'label_id' => $a->label_id,
                    'label_name' => $a->label?->name,
                ])->values()->all(),
            ];
        }

        // Resolve key-visual/background live (per-service override → current global default → none),
        // so the panels always reflect the CURRENT default even if it changed after creation/sync.
        $resolvedKeyVisual = $imageResolver->keyVisualFor($service);
        $resolvedBackground = $imageResolver->backgroundFor($service);
        $keyVisualIsOwn = $service->key_visual_filename !== null && $resolvedKeyVisual === $service->key_visual_filename;
        $backgroundIsOwn = $service->background_filename !== null && $resolvedBackground === $service->background_filename;

        return Inertia::render('Services/Edit', [
            'service' => [
                'id' => $service->id,
                'title' => $service->title,
                'date' => $service->date?->toDateString(),
                'preacher_name' => $service->preacher_name,
                'beamer_tech_name' => $service->beamer_tech_name,
                'finalized_at' => $service->finalized_at?->toJSON(),
                'last_synced_at' => $service->last_synced_at?->toJSON(),
                'has_agenda' => $service->has_agenda,
                'key_visual_filename' => $resolvedKeyVisual,
                'background_filename' => $resolvedBackground,
                'key_visual_url' => $resolvedKeyVisual ? '/storage/'.$resolvedKeyVisual : null,
                'background_url' => $resolvedBackground ? '/storage/'.$resolvedBackground : null,
                'key_visual_is_own' => $keyVisualIsOwn,
                'background_is_own' => $backgroundIsOwn,
                'moderator_name' => $service->moderator_name,
                'preacher_name_override' => $service->preacher_name_override,
                'worship_leader_name' => app(\App\Services\NameTagResolver::class)->worshipLeaderFor($service),
            ],
            'serviceSongs' => $service->serviceSongs->map(fn ($ss) => [
                'id' => $ss->id,
                'order' => $ss->order,
                'cts_song_name' => $ss->cts_song_name,
                'cts_ccli_id' => $ss->cts_ccli_id,
                'use_translation' => $ss->use_translation,
                'song_id' => $ss->song_id,
                'song_arrangement_id' => $ss->song_arrangement_id,
                'matched_at' => $ss->matched_at?->toJSON(),
                'confirmed_at' => $ss->confirmed_at?->toJSON(),
                'request_sent_at' => $ss->request_sent_at?->toJSON(),
                'has_content_slides' => $ss->song ? $this->defaultArrangementHasContentSlides($ss->song) : null,
                'song' => $ss->song ? [
                    'id' => $ss->song->id,
                    'title' => $ss->song->title,
                    'ccli_id' => $ss->song->ccli_id,
                    'has_translation' => $ss->song->has_translation,
                    'groups' => $this->collectSongLabels($ss->song),
                    'arrangements' => $ss->song->arrangements
                        ->sortBy(fn ($arrangement) => $arrangement->is_default ? 0 : 1)
                        ->values()
                        ->map(fn ($arrangement) => [
                            'id' => $arrangement->id,
                            'name' => $arrangement->name,
                            'is_default' => $arrangement->is_default,
                            'groups' => $arrangement->arrangementSections
                                ->sortBy('order')
                                ->values()
                                ->map(fn ($arrangementSection) => [
                                    'id' => $arrangementSection->section?->label?->id,
                                    'section_id' => $arrangementSection->section?->id,
                                    'name' => $arrangementSection->section?->label?->name,
                                    'color' => $arrangementSection->section?->label?->color,
                                ])
                                ->filter(fn ($group) => $group['id'] !== null)
                                ->values(),
                        ]),
                ] : null,
                'arrangement' => $ss->arrangement ? [
                    'id' => $ss->arrangement->id,
                    'name' => $ss->arrangement->name,
                ] : null,
            ]),
            'informationSlides' => $informationSlides,
            'moderationSlides' => $moderationSlides,
            'sermonSlides' => $sermonSlides,
            'songsCatalog' => $songsCatalog,
            'agendaItems' => $agendaItemsMapped,
            'agendaSettings' => $agendaSettings,
            'prevService' => $prevService ? [
                'id' => $prevService->id,
                'title' => $prevService->title,
                'date' => $prevService->date?->toDateString(),
            ] : null,
            'nextService' => $nextService ? [
                'id' => $nextService->id,
                'title' => $nextService->title,
                'date' => $nextService->date?->toDateString(),
            ] : null,
            'macros_per_part' => $macros_per_part,
        ]);
    }

    public function finalize(Service $service): JsonResponse
    {
        $status = $service->finalizationStatus();

        $confirmed = request()->boolean('confirmed');

        if (! $status['ready'] && ! $confirmed) {
            return response()->json([
                'needs_confirmation' => true,
                'warnings' => $status['warnings'],
            ]);
        }

        $service->update([
            'finalized_at' => now(),
        ]);

        $resolver = app(\App\Services\ServiceImageResolver::class);
        $keyVisual = $resolver->keyVisualFor($service);
        $background = $resolver->backgroundFor($service);
        $updates = [];
        if ($keyVisual !== null && $service->key_visual_filename === null) {
            $updates['key_visual_filename'] = $keyVisual;
        }
        if ($background !== null && $service->background_filename === null) {
            $updates['background_filename'] = $background;
        }
        if ($updates !== []) {
            $service->update($updates);
        }

        return response()->json([
            'needs_confirmation' => false,
            'success' => 'Service wurde abgeschlossen.',
        ]);
    }

    public function updateNameOverrides(Request $request, Service $service): RedirectResponse
    {
        $validated = $request->validate([
            'moderator_name' => ['nullable', 'string', 'max:255'],
            'preacher_name_override' => ['nullable', 'string', 'max:255'],
        ]);

        $service->update($validated);

        return back()->with('success', 'Namensangaben gespeichert.');
    }

    public function reopen(Service $service): RedirectResponse
    {
        $service->update([
            'finalized_at' => null,
        ]);

        return redirect()
            ->route('services.index')
            ->with('success', 'Service wurde wieder geoeffnet.');
    }

    public function destroy(Service $service): RedirectResponse
    {
        $service->serviceSongs()->delete();
        $service->slides()->where('type', '!=', 'information')->delete();
        $service->delete();

        return redirect()
            ->route('services.index')
            ->with('success', 'Service wurde gelöscht und wird beim nächsten Sync neu erstellt.');
    }

    public function download(Service $service): JsonResponse|BinaryFileResponse
    {
        if (! $service->finalized_at) {
            abort(403, 'Nur abgeschlossene Services können heruntergeladen werden.');
        }

        try {
            $playlistService = app(\App\Services\PlaylistExportService::class);
            $result = $playlistService->generatePlaylist($service);

            $response = response()->download($result['path'], $result['filename']);

            if ($result['skipped'] > 0) {
                $response->headers->set('X-Skipped-Songs', (string) $result['skipped']);
            }

            return $response->deleteFileAfterSend(false);
        } catch (\RuntimeException $e) {
            return response()->json(['message' => $e->getMessage()], 422);
        }
    }

    public function downloadPreview(Service $service): JsonResponse|BinaryFileResponse
    {
        try {
            $playlistService = app(\App\Services\PlaylistExportService::class);
            $result = $playlistService->generatePlaylist($service, true);

            $response = response()->download($result['path'], $result['filename']);

            if ($result['skipped'] > 0) {
                $response->headers->set('X-Skipped-Songs', (string) $result['skipped']);
            }

            return $response->deleteFileAfterSend(false);
        } catch (\RuntimeException $e) {
            return response()->json(['message' => $e->getMessage()], 422);
        }
    }

    public function downloadBundle(Request $request, Service $service, string $blockType): BinaryFileResponse
    {
        $request->merge(['blockType' => $blockType]);
        $request->validate([
            'blockType' => 'required|in:information,moderation,sermon',
        ]);

        $bundlePath = app(ProBundleExportService::class)->generateBundle($service, $blockType);

        return response()
            ->download(
                $bundlePath,
                "{$service->id}_{$blockType}.probundle",
                ['Content-Type' => 'application/zip']
            )
            ->deleteFileAfterSend(true);
    }

    public function downloadAgendaItem(Service $service, ServiceAgendaItem $agendaItem): JsonResponse|BinaryFileResponse
    {
        if ((int) $agendaItem->service_id !== (int) $service->id) {
            abort(404);
        }

        try {
            $bundlePath = app(ProBundleExportService::class)->generateAgendaItemBundle($agendaItem);
        } catch (RuntimeException $e) {
            return response()->json(['message' => $e->getMessage()], 422);
        }

        $safeTitle = preg_replace('/[^a-zA-Z0-9äöüÄÖÜß\-_ ]/', '', $agendaItem->title ?: 'element');

        return response()
            ->download(
                $bundlePath,
                $safeTitle.'.probundle',
                ['Content-Type' => 'application/zip']
            )
            ->deleteFileAfterSend(true);
    }

    private function collectSongLabels(Song $song): \Illuminate\Support\Collection
    {
        // Build availableGroups from ALL song sections (not just the default arrangement),
        // so every arrangement's referenced sections resolve to real slides on the frontend.
        return $song->sections
            ->filter(fn ($section) => $section->label !== null)
            ->map(fn ($section) => [
                'id' => $section->label->id,
                'section_id' => $section->id,
                'name' => $section->label->name,
                'color' => $section->label->color,
                'order' => $section->order,
                'locked' => (bool) ($section->locked ?? false),
                'slides' => $section->slides
                    ->sortBy('order')
                    ->values()
                    ->map(fn ($slide) => [
                        'id' => $slide->id,
                        'order' => $slide->order,
                        'text_content' => $slide->text_content,
                        'text_content_translated' => $slide->text_content_translated,
                        'notes' => $slide->notes ?? null,
                    ])->toArray(),
            ])
            ->values();
    }

    /**
     * Prueft, ob das Standard-Arrangement des Songs mindestens eine Inhaltsfolie hat.
     * Inhaltsfolie = SongSlide in einer NICHT gesperrten Section (locked null = nicht gesperrt),
     * die Teil des Standard-Arrangements ist. Relationen sind bereits eager-geladen.
     */
    private function defaultArrangementHasContentSlides(Song $song): bool
    {
        $defaultArrangement = $song->arrangements
            ->sortBy(fn ($arrangement) => $arrangement->is_default ? 0 : 1)
            ->first();

        if ($defaultArrangement === null) {
            return false;
        }

        return $defaultArrangement->arrangementSections->contains(function ($arrangementSection): bool {
            $section = $arrangementSection->section;

            if ($section === null || (bool) ($section->locked ?? false)) {
                return false;
            }

            return $section->slides->isNotEmpty();
        });
    }
}
