<?php

namespace App\Services;

use App\Models\Service;
use App\Models\ServiceAgendaItem;
use App\Models\Setting;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;

class NameTagResolver
{
    public function __construct(
        private readonly AgendaMatcherService $agendaMatcherService,
    ) {}

    public function moderatorFor(Service $service): ?string
    {
        $override = $this->filledString($service->moderator_name);
        if ($override !== null) {
            return $override;
        }

        // Prefer the person whose responsible role is "Moderation".
        $byRole = $this->nameForRole($service, 'moderation');
        if ($byRole !== null) {
            return $byRole;
        }

        $firstAgendaItem = $service->agendaItems()
            ->where('is_before_event', false)
            ->orderBy('sort_order')
            ->orderBy('id')
            ->first();

        return $firstAgendaItem ? $this->namesFromResponsible($firstAgendaItem->responsible) : null;
    }

    public function worshipLeaderFor(Service $service): ?string
    {
        return $this->nameForRole($service, 'lobpreis');
    }

    public function preacherFor(Service $service): ?string
    {
        $override = $this->filledString($service->preacher_name_override);
        if ($override !== null) {
            return $override;
        }

        $preacherName = $this->filledString($service->preacher_name);
        if ($preacherName !== null) {
            return $preacherName;
        }

        // Prefer the person whose responsible role is "Predigt".
        $byRole = $this->nameForRole($service, 'predigt');
        if ($byRole !== null) {
            return $byRole;
        }

        $sermonItem = $service->agendaItems()
            ->where('is_before_event', false)
            ->whereNull('service_song_id')
            ->orderBy('sort_order')
            ->orderBy('id')
            ->get()
            ->first(fn (ServiceAgendaItem $item) => $this->isSermonItem($item));

        return $sermonItem ? $this->namesFromResponsible($sermonItem->responsible) : null;
    }

    /**
     * Find the first visible agenda item carrying a responsible person whose
     * role (persons[].service, e.g. "[Lobpreis]") matches $roleNeedle, and
     * return that person's display name.
     */
    private function nameForRole(Service $service, string $roleNeedle): ?string
    {
        $items = $service->agendaItems()
            ->where('is_before_event', false)
            ->orderBy('sort_order')
            ->orderBy('id')
            ->get();

        foreach ($items as $item) {
            $name = $this->nameFromResponsibleByRole($item->responsible, $roleNeedle);
            if ($name !== null) {
                return $name;
            }
        }

        return null;
    }

    private function filledString(?string $value): ?string
    {
        $trimmed = trim((string) $value);

        return $trimmed === '' ? null : $trimmed;
    }

    private function namesFromResponsible(mixed $responsible): ?string
    {
        if (! is_array($responsible) || $responsible === []) {
            return null;
        }

        // Real CTS shape: { "text": "...", "persons": [ { "service": "[Lobpreis]", "person": { "title": "...", ... } } ] }
        if (array_key_exists('persons', $responsible)) {
            $entries = $responsible['persons'];
        } elseif (Arr::isAssoc($responsible)) {
            $entries = [$responsible];
        } else {
            $entries = $responsible;
        }

        $names = collect($entries)
            ->map(fn (mixed $entry) => $this->nameFromResponsibleEntry($entry))
            ->filter()
            ->values()
            ->all();

        return $names === [] ? null : implode(', ', $names);
    }

    /**
     * Extract a person name from a responsible entry.
     * Handles both the real CTS shape (entry has a 'person' sub-key) and the
     * legacy/test shape (entry is a flat array with 'name'/'firstName'/'lastName' or a string).
     */
    private function nameFromResponsibleEntry(mixed $entry): ?string
    {
        if (is_string($entry)) {
            return $this->filledString($entry);
        }

        if (! is_array($entry)) {
            return null;
        }

        // Real CTS shape: { "service": "[Lobpreis]", "person": { "title": "Kornelius Weiß", ... } }
        if (isset($entry['person']) && is_array($entry['person'])) {
            return $this->nameFromPersonNode($entry['person']);
        }

        // Legacy / test shape: { "name": "Anna Müller" } or { "firstName": "...", "lastName": "..." }
        return $this->nameFromPersonNode($entry);
    }

    /** Extract display name from a person node (title preferred, then firstName+lastName). */
    private function nameFromPersonNode(mixed $node): ?string
    {
        if (! is_array($node)) {
            return null;
        }

        $title = $this->filledString($node['title'] ?? null);
        if ($title !== null) {
            return $title;
        }

        $name = $this->filledString($node['name'] ?? null);
        if ($name !== null) {
            return $name;
        }

        $domainAttrs = $node['domainAttributes'] ?? [];
        $firstName = $this->filledString(
            $node['firstName'] ?? $node['first_name'] ?? ($domainAttrs['firstName'] ?? null)
        ) ?? '';
        $lastName = $this->filledString(
            $node['lastName'] ?? $node['last_name'] ?? ($domainAttrs['lastName'] ?? null)
        ) ?? '';
        $fullName = trim($firstName.' '.$lastName);

        return $fullName === '' ? null : $fullName;
    }

    /**
     * Scan a responsible array for a person whose role (persons[].service)
     * contains $roleNeedle (e.g. 'lobpreis', 'predigt', 'moderation').
     * Returns the person's display name, or null if none found.
     */
    private function nameFromResponsibleByRole(mixed $responsible, string $roleNeedle): ?string
    {
        if (! is_array($responsible) || $responsible === []) {
            return null;
        }

        // Real CTS shape: { "persons": [ { "service": "[Lobpreis]", "person": { ... } } ] }
        $entries = array_key_exists('persons', $responsible) ? $responsible['persons'] : [];

        foreach ($entries as $entry) {
            if (! is_array($entry)) {
                continue;
            }

            $role = $this->normalizeRole($entry['service'] ?? '');
            if ($role !== '' && str_contains($role, $roleNeedle)) {
                $name = $this->nameFromResponsibleEntry($entry);
                if ($name !== null) {
                    return $name;
                }
            }
        }

        return null;
    }

    /** Strip brackets, whitespace, and lowercase a role string for comparison. */
    private function normalizeRole(string $role): string
    {
        return Str::lower(preg_replace('/[^a-zA-ZäöüÄÖÜß]/', '', $role) ?? '');
    }

    private function isSermonItem(ServiceAgendaItem $item): bool
    {
        $configuredPatterns = $this->patternsFromSetting(Setting::get('agenda_sermon_matching'));
        if ($configuredPatterns !== []) {
            return $this->agendaMatcherService->matchesAny($item->title, $configuredPatterns);
        }

        $title = Str::lower($item->title);
        $type = Str::lower($item->type ?? '');

        return str_contains($title, 'predigt')
            || str_contains($title, 'sermon')
            || str_contains($type, 'predigt')
            || str_contains($type, 'sermon');
    }

    /** @return array<int, string> */
    private function patternsFromSetting(?string $patterns): array
    {
        if ($patterns === null || trim($patterns) === '') {
            return [];
        }

        return array_values(array_filter(
            array_map(fn (string $pattern) => trim($pattern), explode(',', $patterns)),
            fn (string $pattern) => $pattern !== '',
        ));
    }
}
