<?php

namespace App\Services;

use App\Models\Label;
use App\Models\Song;
use App\Models\SongArrangement;
use App\Models\SongArrangementLabel;
use App\Support\MacroColorConverter;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use ProPresenter\Parser\ProFileReader;
use ProPresenter\Parser\Song as ProSong;
use ZipArchive;

class ProImportService
{
    /** @return Song[] */
    public function import(UploadedFile $file): array
    {
        $extension = strtolower($file->getClientOriginalExtension());

        if ($extension === 'zip') {
            return $this->importZip($file);
        }

        if ($extension === 'pro') {
            return [$this->importProFile($file->getRealPath())];
        }

        throw new \InvalidArgumentException('Nur .pro und .zip Dateien sind erlaubt.');
    }

    /** @return Song[] */
    private function importZip(UploadedFile $file): array
    {
        $zip = new ZipArchive;

        if ($zip->open($file->getRealPath()) !== true) {
            throw new \RuntimeException('ZIP-Datei konnte nicht geöffnet werden.');
        }

        $tempDir = sys_get_temp_dir().'/pro-import-'.uniqid();
        mkdir($tempDir, 0755, true);

        $songs = [];

        try {
            $zip->extractTo($tempDir);
            $zip->close();

            $proFiles = glob($tempDir.'/*.pro') ?: [];
            $proFilesNested = glob($tempDir.'/**/*.pro') ?: [];
            $allProFiles = array_unique(array_merge($proFiles, $proFilesNested));

            if (empty($allProFiles)) {
                throw new \RuntimeException('Keine .pro Dateien im ZIP-Archiv gefunden.');
            }

            foreach ($allProFiles as $proPath) {
                $songs[] = $this->importProFile($proPath);
            }
        } finally {
            $this->deleteDirectory($tempDir);
        }

        return $songs;
    }

    private function importProFile(string $filePath): Song
    {
        $proSong = ProFileReader::read($filePath);

        return DB::transaction(function () use ($proSong) {
            return $this->upsertSong($proSong);
        });
    }

    private function upsertSong(ProSong $proSong): Song
    {
        $ccliId = $proSong->getCcliSongNumber();

        $songData = [
            'title' => $proSong->getName(),
            'author' => $proSong->getCcliAuthor() ?: null,
            'copyright_text' => $proSong->getCcliPublisher() ?: null,
            'copyright_year' => $proSong->getCcliCopyrightYear() ?: null,
            'publisher' => $proSong->getCcliPublisher() ?: null,
        ];

        if ($ccliId) {
            $song = Song::withTrashed()->where('ccli_id', (string) $ccliId)->first();

            if ($song) {
                if ($song->trashed()) {
                    $song->restore();
                }
                $song->update($songData);
            } else {
                $song = Song::create(array_merge($songData, ['ccli_id' => (string) $ccliId]));
            }
        } else {
            $song = Song::create(array_merge($songData, ['ccli_id' => null]));
        }

        $song->arrangements()->each(function (SongArrangement $arr) {
            $arr->arrangementLabels()->delete();
        });
        $song->arrangements()->delete();

        $hasTranslation = false;
        $labelsByName = [];

        foreach ($proSong->getGroups() as $proGroup) {
            $groupName = $proGroup->getName();
            $existingLabel = Label::whereRaw('LOWER(name) = ?', [strtolower($groupName)])->first();

            if ($existingLabel === null) {
                $color = $proGroup->getColor();
                $hexColor = MacroColorConverter::fromRgba($color);

                $existingLabel = Label::create([
                    'name' => $groupName,
                    'color' => $hexColor,
                ]);
            }

            $labelsByName[$groupName] = $existingLabel;

            $existingLabel->songSlides()->delete();

            foreach ($proSong->getSlidesForGroup($proGroup) as $slidePosition => $proSlide) {
                $translatedText = null;

                if ($proSlide->hasTranslation()) {
                    $translatedText = $proSlide->getTranslation()->getPlainText();
                    $hasTranslation = true;
                }

                $existingLabel->songSlides()->create([
                    'order' => $slidePosition,
                    'text_content' => $proSlide->getPlainText(),
                    'text_content_translated' => $translatedText,
                ]);
            }
        }

        $song->update(['has_translation' => $hasTranslation]);

        foreach ($proSong->getArrangements() as $proArrangement) {
            $arrangement = $song->arrangements()->create([
                'name' => $proArrangement->getName(),
                'is_default' => strtolower($proArrangement->getName()) === 'normal',
            ]);

            $groupsInArrangement = $proSong->getGroupsForArrangement($proArrangement);

            foreach ($groupsInArrangement as $order => $proGroup) {
                $label = $labelsByName[$proGroup->getName()] ?? null;

                if ($label) {
                    SongArrangementLabel::create([
                        'song_arrangement_id' => $arrangement->id,
                        'label_id' => $label->id,
                        'order' => $order,
                    ]);
                }
            }
        }

        return $song->fresh(['arrangements.arrangementLabels.label.songSlides']);
    }

    public static function rgbaToHex(array $rgba): string
    {
        $r = (int) round(($rgba['r'] ?? 0) * 255);
        $g = (int) round(($rgba['g'] ?? 0) * 255);
        $b = (int) round(($rgba['b'] ?? 0) * 255);

        return sprintf('#%02X%02X%02X', $r, $g, $b);
    }

    public static function hexToRgba(string $hex): array
    {
        $hex = ltrim($hex, '#');

        $r = hexdec(substr($hex, 0, 2)) / 255;
        $g = hexdec(substr($hex, 2, 2)) / 255;
        $b = hexdec(substr($hex, 4, 2)) / 255;

        return [round($r, 4), round($g, 4), round($b, 4), 1.0];
    }

    private function deleteDirectory(string $dir): void
    {
        if (! is_dir($dir)) {
            return;
        }

        $items = scandir($dir);
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $path = $dir.'/'.$item;
            is_dir($path) ? $this->deleteDirectory($path) : unlink($path);
        }
        rmdir($dir);
    }
}
