<?php

namespace App\Http\Controllers;

use App\Models\Label;
use App\Models\Song;
use App\Models\SongArrangement;
use App\Models\SongSection;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class ArrangementController extends Controller
{
    public function store(Request $request, Song $song): RedirectResponse
    {
        $data = $request->validate([
            'name' => ['required', 'string', 'max:255'],
        ]);

        DB::transaction(function () use ($song, $data): void {
            $arrangement = $song->arrangements()->create([
                'name' => $data['name'],
                'is_default' => false,
            ]);

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

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

            $arrangementSections = $defaultArr->arrangementSections()->orderBy('order')->get();

            $rows = $arrangementSections->values()->map(fn ($arrangementSection, $index) => [
                'song_arrangement_id' => $arrangement->id,
                'song_section_id' => $arrangementSection->song_section_id,
                'order' => $index + 1,
                'created_at' => now(),
                'updated_at' => now(),
            ])->all();

            if ($rows !== []) {
                $arrangement->arrangementSections()->insert($rows);
            }
        });

        return back()->with('success', 'Arrangement wurde hinzugefügt.');
    }

    public function clone(Request $request, SongArrangement $arrangement): RedirectResponse
    {
        $data = $request->validate([
            'name' => ['required', 'string', 'max:255'],
        ]);

        DB::transaction(function () use ($arrangement, $data): void {
            $arrangement->loadMissing('arrangementSections');

            $clone = $arrangement->song->arrangements()->create([
                'name' => $data['name'],
                'is_default' => false,
            ]);

            $this->cloneArrangementLabels($arrangement, $clone);
        });

        return back()->with('success', 'Arrangement wurde geklont.');
    }

    public function update(Request $request, SongArrangement $arrangement): RedirectResponse
    {
        $data = $request->validate([
            'groups' => ['array'],
            'groups.*.section_id' => ['nullable', 'integer', 'exists:song_sections,id'],
            'groups.*.label_id' => ['nullable', 'integer', 'exists:labels,id'],
            'groups.*.order' => ['required', 'integer', 'min:1'],
            'group_colors' => ['sometimes', 'array'],
            'group_colors.*' => ['required', 'string', 'regex:/^#[0-9A-Fa-f]{6}$/'],
        ]);

        $sectionIds = $this->sectionIdsForGroups($arrangement, $data['groups'] ?? []);

        DB::transaction(function () use ($arrangement, $sectionIds, $data): void {
            $arrangement->arrangementSections()->delete();

            $rows = $sectionIds
                ->values()
                ->map(fn (int $sectionId, int $index) => [
                    'song_arrangement_id' => $arrangement->id,
                    'song_section_id' => $sectionId,
                    'order' => $index + 1,
                    'created_at' => now(),
                    'updated_at' => now(),
                ])
                ->all();

            if ($rows !== []) {
                $arrangement->arrangementSections()->insert($rows);
            }

            if (! empty($data['group_colors'])) {
                $sections = SongSection::whereIn('id', collect(array_keys($data['group_colors']))->map(fn ($id) => (int) $id))
                    ->get()
                    ->keyBy('id');

                foreach ($data['group_colors'] as $id => $color) {
                    $section = $sections->get((int) $id);
                    $labelId = $section?->label_id ?? (int) $id;

                    Label::whereKey($labelId)->update(['color' => $color]);
                }
            }
        });

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

    public function destroy(SongArrangement $arrangement): RedirectResponse
    {
        $song = $arrangement->song;

        if ($song->arrangements()->count() <= 1) {
            return back()->with('error', 'Das letzte Arrangement kann nicht gelöscht werden.');
        }

        DB::transaction(function () use ($arrangement, $song): void {
            $deletedWasDefault = $arrangement->is_default;
            $arrangement->delete();

            if ($deletedWasDefault) {
                $song->arrangements()
                    ->orderBy('id')
                    ->limit(1)
                    ->update(['is_default' => true]);
            }
        });

        return back()->with('success', 'Arrangement wurde gelöscht.');
    }

    private function cloneArrangementLabels(?SongArrangement $source, SongArrangement $target): void
    {
        if ($source === null) {
            return;
        }

        $arrangementSections = $source->arrangementSections
            ->sortBy('order')
            ->values();

        $rows = $arrangementSections
            ->map(fn ($arrangementSection) => [
                'song_arrangement_id' => $target->id,
                'song_section_id' => $arrangementSection->song_section_id,
                'order' => $arrangementSection->order,
                'created_at' => now(),
                'updated_at' => now(),
            ])
            ->all();

        if ($rows !== []) {
            $target->arrangementSections()->insert($rows);
        }
    }

    private function sectionIdsForGroups(SongArrangement $arrangement, array $groups): \Illuminate\Support\Collection
    {
        $songId = $arrangement->song_id;
        $sectionIds = collect($groups)->map(function (array $group) use ($songId) {
            if (isset($group['section_id'])) {
                $section = SongSection::find((int) $group['section_id']);

                if ($section === null || (int) $section->song_id !== (int) $songId) {
                    throw ValidationException::withMessages([
                        'groups' => 'Diese Sektion gehört nicht zu diesem Song.',
                    ]);
                }

                return $section->id;
            }

            if (isset($group['label_id'])) {
                $section = SongSection::where('song_id', $songId)
                    ->where('label_id', (int) $group['label_id'])
                    ->first();

                if ($section !== null) {
                    return $section->id;
                }
            }

            throw ValidationException::withMessages([
                'groups' => 'Bitte wähle gültige Song-Sektionen aus.',
            ]);
        })->values();

        return $sectionIds;
    }
}
