<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Mtc\MercuryDataModels\CarConfiguratorModel;
use Mtc\MercuryDataModels\CarConfiguratorRestriction;
use Mtc\MercuryDataModels\CarConfiguratorSection;

class CarConfigurationSelectionIsValid implements ValidationRule
{
    public function __construct(private readonly CarConfiguratorModel $model, private readonly array $all_sections = [])
    {
    }

    /**
     * Run the validation rule.
     *
     * @param  \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString  $fail
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $sectionModel = $this->model->activeSections->where('id', $value['id'] ?? null)->first();
        if ($this->valueDoesNotExist($sectionModel, $value)) {
            $fail('Incorrect selection for ' . $value['name']);
        }

        if ($this->valueConflictsWithSelections($sectionModel, $value)) {
            $fail('This :attribute is not allowed with current configuration.');
        }
    }

    private function valueDoesNotExist(CarConfiguratorSection $sectionModel, mixed $section): bool
    {
        if (is_null($section['selected']) && !empty($sectionModel->data['optional'])) {
            return false;
        }

        if (!is_array($section['selected'])) {
            $section['selected'] = [$section['selected']];
        }

        // Any of selected values is not found
        return collect($section['selected'])
            ->reject(fn ($selection) => $this->modelHas($sectionModel, $selection))
            ->isNotEmpty();
    }

    private function valueConflictsWithSelections(
        CarConfiguratorSection $section,
        mixed $value
    ): bool {
        if (empty($value['selected'])) {
            return false;
        }

        if ($section->custom) {
            $entry = $this->model->custom()
                ->where('section_id', $section->id)
                ->where('id', $value['selected'])
                ->first();
        } else {
            $relationName = Str::slug($section->name);
            $entry = $this->model->$relationName()->find($value['selected']);
        }

        if ($entry instanceof Collection) {
            return $entry->filter(function ($item) {
                if ($item?->restrictions?->isEmpty()) {
                    return false;
                }

                return $item->restrictions
                    ->reject(fn ($restriction) => $this->restrictionDoesNotConflict($restriction))
                    ->isNotEmpty();
            })->isNotEmpty();
        } elseif ($entry?->restrictions?->isEmpty()) {
            return false;
        }

        return $entry->restrictions
            ->reject(fn ($restriction) => $this->restrictionDoesNotConflict($restriction))
            ->isNotEmpty();
    }

    private function restrictionDoesNotConflict(CarConfiguratorRestriction $restriction): bool
    {
        $selected_value = $this->all_sections[$restriction->restriction_type]['selected'];
        return match ($restriction->condition) {
            'not_allowed' => $selected_value !== $restriction->restriction_id,
            'included',
            'is_allowed' => $selected_value === $restriction->restriction_id,
            default => false,
        };
    }


    private function modelHas(CarConfiguratorSection $section, $value): bool
    {
        if ($section->custom) {
            return $this->model->custom()->where('section_id', $section->id)->where('id', $value)->exists();
        }
        $relationName = Str::slug($section->name);
        if ($relationName === 'extras') {
            return $this->model->whereHas($relationName, fn ($query) => $query->where('extra_id', $value))->exists();
        }
        if ($relationName === 'packages') {
            return $this->model->whereHas($relationName, fn ($query) => $query->where('package_id', $value))->exists();
        }
        return $this->model->whereHas($relationName, fn ($query) => $query->where('id', $value))->exists();
    }
}
