<?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): Response
    {
        $service->load([
            'serviceSongs' => fn ($query) => $query->orderBy('order'),
            'serviceSongs.song',
            'serviceSongs.song.arrangements.arrangementLabels.label',
            'serviceSongs.arrangement',
            'slides',
            'agendaItems' => fn ($q) => $q->orderBy('sort_order'),
            'agendaItems.slides',
            'agendaItems.serviceSong.song.arrangements.arrangementLabels.label.songSlides',
            'agendaItems.serviceSong.arrangement.arrangementLabels.label',
        ]);

        $songsCatalog = Song::query()
            ->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,
            ])
            ->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;

            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'] 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(),
            ];
        }

        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,
            ],
            '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(),
                'request_sent_at' => $ss->request_sent_at?->toJSON(),
                '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->arrangementLabels
                                ->sortBy('order')
                                ->values()
                                ->map(fn ($arrangementLabel) => [
                                    'id' => $arrangementLabel->label?->id,
                                    'name' => $arrangementLabel->label?->name,
                                    'color' => $arrangementLabel->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(),
        ]);

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

    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 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
    {
        $defaultArr = $song->arrangements->firstWhere('is_default', true) ?? $song->arrangements->first();

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

        return $defaultArr->arrangementLabels
            ->sortBy('order')
            ->values()
            ->map(fn ($arrangementLabel) => [
                'id' => $arrangementLabel->label?->id,
                'name' => $arrangementLabel->label?->name,
                'color' => $arrangementLabel->label?->color,
                'order' => $arrangementLabel->order,
            ])
            ->filter(fn ($group) => $group['id'] !== null)
            ->values();
    }
}
