<?php

namespace App\Services\Customer;

final class CustomerQualitySettingsNormalizer
{
    public const APPROVED_STATUS_MAPPINGS = [
        'delivered' => ['delivered', 'completed', 'delivered_approval_pending', 'partial_delivered', 'partial_delivered_approval_pending'],
        'returned' => ['returned', 'return_to_sender', 'return_to_merchant', 'returned_to_merchant', 'return_to_origin'],
        'cancelled' => ['canceled', 'cancelled', 'cancelled_approval_pending', 'failed'],
    ];

    public const APPROVED_INTERNAL_KEYWORDS = ['test', 'dummy', 'demo', 'internal', 'staff'];

    public function normalizeSettingsLists(array $settings, bool $fallbackInvalidMappings = true): array
    {
        $settings['status_mappings'] = $this->normalizeStatusMappings(
            data_get($settings, 'status_mappings', []),
            $fallbackInvalidMappings
        );
        $settings['auto_exclusions']['keywords'] = $this->normalizeKeywords(
            data_get($settings, 'auto_exclusions.keywords', [])
        );
        $settings['auto_exclusions']['emails'] = $this->normalizeEmails(
            data_get($settings, 'auto_exclusions.emails', [])
        );
        $settings['auto_exclusions']['phones'] = $this->splitList(
            data_get($settings, 'auto_exclusions.phones', [])
        );

        return $settings;
    }

    public function normalizeStatusMappings(mixed $mappings, bool $fallbackInvalid = true): array
    {
        $mappings = is_array($mappings) ? $mappings : [];
        $normalized = [];

        foreach (array_keys(self::APPROVED_STATUS_MAPPINGS) as $group) {
            $normalized[$group] = collect($this->splitList(data_get($mappings, $group, [])))
                ->map(fn ($status) => $this->normalizeStatus($status))
                ->filter()
                ->unique()
                ->values()
                ->all();
        }

        if ($fallbackInvalid && $this->statusMappingErrorsFromNormalized($normalized) !== []) {
            return self::APPROVED_STATUS_MAPPINGS;
        }

        return $normalized;
    }

    public function statusMappingErrors(mixed $mappings): array
    {
        return $this->statusMappingErrorsFromNormalized(
            $this->normalizeStatusMappings($mappings, false)
        );
    }

    public function normalizeKeywords(mixed $keywords): array
    {
        return collect($this->splitList($keywords))
            ->map(fn ($keyword) => mb_strtolower($keyword))
            ->filter()
            ->unique()
            ->values()
            ->all();
    }

    public function normalizeEmails(mixed $emails): array
    {
        return collect($this->splitList($emails))
            ->map(fn ($email) => strtolower($email))
            ->filter(fn ($email) => filter_var($email, FILTER_VALIDATE_EMAIL) !== false)
            ->unique()
            ->values()
            ->all();
    }

    public function splitList(mixed $value): array
    {
        $values = is_array($value) ? $value : [$value];

        return collect($values)
            ->flatMap(function ($entry) {
                if (is_array($entry)) {
                    return $entry;
                }

                return preg_split('/[\r\n,]+/u', (string) $entry) ?: [];
            })
            ->map(fn ($entry) => $this->cleanListEntry((string) $entry))
            ->filter()
            ->unique()
            ->values()
            ->all();
    }

    private function statusMappingErrorsFromNormalized(array $mappings): array
    {
        $errors = [];

        foreach (array_keys(self::APPROVED_STATUS_MAPPINGS) as $group) {
            if (($mappings[$group] ?? []) === []) {
                $errors[] = ucfirst($group).' status mapping must contain at least one value.';
            }
        }

        $owners = [];
        foreach ($mappings as $group => $statuses) {
            foreach ($statuses as $status) {
                if (isset($owners[$status]) && $owners[$status] !== $group) {
                    $errors[] = sprintf(
                        'Status "%s" cannot be mapped to both %s and %s.',
                        $status,
                        $owners[$status],
                        $group
                    );

                    continue;
                }

                $owners[$status] = $group;
            }
        }

        return array_values(array_unique($errors));
    }

    private function cleanListEntry(string $value): string
    {
        $value = trim($value);
        $value = preg_replace('/^(?:(?:[-*\x{2022}\x{25CF}\x{25AA}\x{2023}]+|_{2,})\s*)+/u', '', $value) ?? $value;

        return trim($value);
    }

    private function normalizeStatus(string $status): string
    {
        $status = mb_strtolower($this->cleanListEntry($status));
        $status = preg_replace('/[\s-]+/u', '_', $status) ?? $status;

        return trim($status, "_ \t\n\r\0\x0B");
    }
}
