<?php

namespace App\Http\Resources;

use App\Facades\Settings;
use App\Traits\MapElementBasedContent;
use App\Traits\RetrievesSeoData;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Mtc\MercuryDataModels\CarConfiguratorModel;
use Mtc\MercuryDataModels\CarConfiguratorRestriction;
use Mtc\MercuryDataModels\CarConfiguratorSection;
use Mtc\MercuryDataModels\Form;
use Mtc\MercuryDataModels\VehicleOffer;

class ConfiguratorView extends JsonResource
{
    use MapElementBasedContent;
    use RetrievesSeoData;

    private readonly Request $request;
    private array $sections = [];
    private array $saved_selections = [];
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    #[\Override]
    public function toArray(Request $request): array
    {
        $this->resource->setHidden(['id', 'make_id', 'model_id', 'created_at', 'updated_at', 'active']);
        $this->request = $request;
        $this->resource->load([
            'make',
            'model',
            'activeSections',
        ]);
        $this->resource->make?->setVisible(['id', 'slug', 'name', 'logo', 'monochrome_logo']);
        $this->resource->model?->setVisible(['id', 'slug', 'name']);
        $this->resource->load($this->sectionsToLoad());
        if (!empty($this->saved_selections)) {
            $this->assignSelections();
        }

        $resource = $this->resource->toArray();
        $resource['sections'] = $this->getResourceSections();
        $resource['media_uses'] = $this->getMediaForSelections($resource);
        $resource['total'] = $this->calculateTotalPrice($resource);
        $resource['seo'] = $this->fillSeo('configurator', request()->header('x-path'));
        unset($resource['data']);
        unset($resource['deleted_at']);

        $resource['content'] = collect($this->resource->data)
            ->map(fn($entry) => $this->mapContent(
                $this->resource,
                $entry ?? [],
                $request,
                $this->resource->getMorphClass()
            ))
            ->toArray();

        $resource['offers_from'] = $this->getCheapestOfferStartingPrice();
        $resource['actions'] = $this->actions();
        unset($resource['active_sections']);
        foreach (CarConfiguratorModel::$relationship_models as $relation) {
            if (isset($resource[$relation])) {
                unset($resource[$relation]);
            }
        }

        return $resource;
    }

    public function setSelectedSections(Collection $selections): self
    {
        $this->saved_selections = $selections->keyBy(fn($section) => Str::slug($section->section->name))
            ->map(fn($section) => ['selected' => collect($section->details)->pluck('id')->toArray()])
            ->toArray();
        return $this;
    }

    private function sectionsToLoad(): array
    {
        $additional = [];
        if ($this->resource->activeSections->search(fn($item) => $item->name === 'Wheels') !== false) {
            $additional[] = 'wheels.mediaUses.media';
        }
        if ($this->resource->activeSections->search(fn($item) => $item->name === 'Packages') !== false) {
            $additional[] = 'packages.mediaUses.media';
        }
        if ($this->resource->activeSections->search(fn($item) => $item->name === 'Extras') !== false) {
            $additional[] = 'extras.mediaUses.media';
        }
        if ($this->resource->activeSections->search(fn($item) => $item->name === 'Engines') !== false) {
            $additional[] = 'engines.fuelType';
            $additional[] = 'engines.drivetrain';
            $additional[] = 'engines.transmission';
        }
        return $this->resource->activeSections
            ->filter(fn($section) => $section->custom
                || in_array(strtolower((string) $section->name), CarConfiguratorModel::$relationship_models))
            ->map(fn($section) => $section->custom
                ? 'custom.restrictions'
                : strtolower((string) $section->name) . '.restrictions')
            ->merge($additional)
            ->toArray();
    }

    private function getResourceSections(): array
    {
        // Need to loop first to retrieve available items
        foreach ($this->resource->activeSections as $section) {
            $section->items = $this->getSectionItems($section)->values();
            $this->sections[Str::slug($section->name)] = $section;
        }

        $conflicts = collect();
        // We need first loop to set the initial value
        // To allow filtering processing restrictions
        foreach ($this->sections as $key => $section) {
            $section->selected = $this->getSelection($key);
            $section->default = $this->firstApplicableOption($section);
        }
        foreach ($this->resource->activeSections as $section) {
            $section->items = $section->items
                ->filter(fn($item) => $this->isVisibleForConfiguration($item))
                ->values();
            $this->sections[Str::slug($section->name)] = $section;
        }

        // Then we loop through to map data
        // Need to loop twice to get the
        foreach ($this->sections as $key => $section) {
            $this->sections[$key] = [
                'id' => $section->id,
                'name' => $section->name,
                'slug' => Str::slug($section->name),
                'label' => $section->label,
                'selected' => $this->getSelection($key, $conflicts),
                'default' => $this->firstApplicableOption($section),
                'items' => $section->items->each(function (Model $item) {
                    if ($item->restrictions->where('condition', 'included')->isNotEmpty()) {
                        $at_no_cost = $item->restrictions->where('condition', 'included')
                            ->filter(function ($row) {
                                $value = $this->sections[$row->restriction_type]['selected']
                                    ?? $this->sections[$row->restriction_type]['default'];
                                return $value === $row->restriction_id;
                            })
                            ->isNotEmpty();
                        if ($at_no_cost) {
                            $item->price = 0;
                            return;
                        }
                    }
                    foreach ($item->data['prices'] ?? [] as $rule) {
                        $value = $this->sections[$rule['restriction_type']]['selected']
                            ?? $this->sections[$rule['restriction_type']]['default'];
                        if ($rule['restriction_id'] == $value) {
                            $item->price = $rule['price'];
                            return;
                        }
                    }
                }),
            ];
        }

        if ($conflicts->isNotEmpty()) {
            throw ValidationException::withMessages($conflicts->toArray());
        }
        return $this->sections;
    }

    private function getSectionItems(CarConfiguratorSection $section)
    {
        if ($section->custom) {
            return $this->resource->custom
                ->filter(fn($item) => $item->section_id === $section->id)
                ->each(function ($entry) {
                    $entry->setHidden(['model_id', 'restrictions', 'created_at', 'updated_at']);
                });
        }

        return $this->resource[Str::slug($section->name)]
            ->each(function (Model $entry) use ($section) {
                if ($entry->relationLoaded('mediaUses')) {
                    $entry->media_uses = $this->mapMedia($entry->mediaUses);
                }
                if ($entry->fuelType) {
                    $entry->fuel_type_name = $entry->fuelType->name;
                }
                if ($entry->drivetrain) {
                    $entry->drivetrain_name = $entry->drivetrain->name;
                }
                if ($entry->transmission) {
                    $entry->transmission_name = $entry->transmission->name;
                }
                $entry->setHidden([
                    'model_id',
                    'restrictions',
                    'created_at',
                    'updated_at',
                    'mediaUses',
                    'fuelType',
                    'fuel_type_id',
                    'drivetrain',
                    'drivetrain_id',
                    'transmission',
                    'transmission_id',
                ]);
            });
    }

    private function isVisibleForConfiguration(Model $item): bool
    {
        if ($item->restrictions->isEmpty()) {
            return true;
        }
        $groups_passing_check = $item->restrictions->groupBy('restriction_type')
            ->map(function ($group) {
                // Separate by condition type
                $hasIsAllowed = $group->contains(fn($restriction) => $restriction->condition === 'is_allowed');

                if ($hasIsAllowed) {
                    // For is_allowed, at least one restriction must match
                    return $group
                        ->filter(fn($restriction) => $restriction->condition === 'is_allowed')
                        ->contains(fn($restriction) => $this->restrictionDoesNotConflict($restriction));
                }

                // Otherwise (only not_allowed / included), all must not conflict
                return $group
                    ->filter(fn($restriction) => $restriction->condition === 'not_allowed')
                    ->every(fn($restriction) => $this->restrictionDoesNotConflict($restriction));
            })
            ->all();

        // Reject groups that pass, check that there are no groups remaining (as they would fail)
        return collect($groups_passing_check)->reject()->isEmpty();
    }

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

    private function getSelection(mixed $restriction_type, ?Collection $conflicts = null): int|array|null
    {
        $input = $this->request->input("sections.$restriction_type.selected");
        if (empty($input)) {
            return null;
        }

        if (is_array($input)) {
            if (count($input) == 1) {
                $input = reset($input);
            } elseif (count($input) > 1 && in_array($restriction_type, ['packages', 'extras'])) {
                return $input;
            }
        }

        $exists = $this->sections[$restriction_type]['items']->where('id', $input)->isNotEmpty();
        if (!$exists) {
            $item = $this->resource[$restriction_type]?->where('id', $input)->first();
            if ($item) {
                $restriction = $item->restrictions
                    ->filter(function ($restriction) use ($item) {
                        $input_name = "sections.{$restriction->restriction_type}.selected";
                        return match ($restriction->condition) {
                            'not_allowed' => $this->request->input($input_name) === $restriction->restriction_id,
                            'included',
                            'is_allowed' => $this->request->input($input_name) !== $restriction->restriction_id,
                            default => false,
                        };
                    })
                    ->first();
                if ($restriction && $conflicts) {
                    $restriction_id = $restriction->condition === 'not_allowed'
                        ? $restriction->restriction_id
                        : $this->sections[$restriction->restriction_type]['selected'];
                    $conflicts->push([
                        $restriction_type => [
                            'message' => 'Selected value is not available',
                            'conflicting_item_type' => $restriction_type,
                            'conflicting_item' => $item->name,
                            'conflicting_item_id' => $item->id,
                            'restriction_type' => $restriction->condition,
                            'restricted_against_id' => $restriction_id,
                            'restricted_against_type' => $restriction->restriction_type,
                            'restricted_against' => $this->resource[$restriction->restriction_type]
                                ?->where('id', $restriction_id)
                                ?->first()
                                ?->name,
                        ]
                    ]);
                    return null;
                }
            }
            $conflicts?->push([
                $restriction_type => [
                    'message' => 'Selected value is not available',
                    'conflicting_item_type' => $restriction_type,
                    'conflicting_item' => $item?->name,
                    'conflicting_item_id' => $item?->id,
                ]
            ]);
        }

        return $input;
    }

    private function firstApplicableOption(CarConfiguratorSection $section): ?int
    {
        if (in_array($section->name, ['Packages', 'Extras'])) {
            return null;
        }
        return $section->items->first()?->id;
    }

    private function getSelectionTags(): array
    {
        $tags = [
            'exterior' => [],
            'interior' => [],
        ];
        foreach (CarConfiguratorModel::$relationship_models as $sectionName) {
            $selectionId = $this->request->input($sectionName);
            if (empty($selectionId)) {
                continue;
            }

            $selectedSectionItem = $this->sections[$sectionName]['items']->firstWhere('id', $selectionId);
            if ($selectedSectionItem !== null) {
                if ($selectedSectionItem->data['exterior'] ?? false) {
                    $tags['exterior'] = $selectedSectionItem->name;
                }
                if ($selectedSectionItem->data['interior'] ?? false) {
                    $tags['interior'] = $selectedSectionItem->name;
                }
            }
        }
        return $tags;
    }

    private function getMediaForSelections($resource): array
    {
        $exteriorSelections = $this->getSelectionsOnResource($resource);
        $interiorSelections = $this->getSelectionsOnResource($resource, 'interior');
        $query = $this->resource->mediaUses();
        $exteriorFlags = collect($exteriorSelections)
            ->map(fn($item) => Str::slug($item))
            ->prepend('exterior')
            ->implode('-');
        $query->where('flags', 'like', '%\"' . $exteriorFlags . '\"%');
        $exterior =  $query->get();

        $query = $this->resource->mediaUses();
        $interiorFlags = collect($interiorSelections)
            ->map(fn($item) => Str::slug($item))
            ->prepend('interior')
            ->implode('-');
        $query->where('flags', 'like', '%\"' . $interiorFlags . '\"%');
        $interior =  $query->get();

        return [
            'exterior' => $this->mapMedia($exterior),
            'interior' => $this->mapMedia($interior),
        ];
    }

    private function assignSelections()
    {
        $this->request->merge(['sections' => $this->saved_selections]);
    }

    private function getSelectionsOnResource($resource, string $type = 'exterior'): array
    {
        $relevant_sections = collect(array_filter(
            $resource['active_sections'],
            fn($section) => $section['active'] && !empty($section['data'][$type])
        ))->pluck('name');
        $sections_to_check = array_filter(
            $resource['sections'],
            fn($section) => $relevant_sections->search($section['name']) !== false
        );
        return array_filter(array_map(
            fn ($section) => $section['items']->where('id', $section['selected'])->first()?->name
                ?? $section['items']->where('id', $section['default'])->first()?->name,
            $sections_to_check
        ));
    }

    private function mapMedia(Collection $list): Collection
    {
        return $list
            ->map(fn($mediaUse) => [
                'id' => $mediaUse->id,
                'title' => $mediaUse->title,
                'alt_text' => $mediaUse->alt_text,
                'description' => $mediaUse->description,
                'caption' => $mediaUse->caption,
                'src' => $mediaUse->media->getOriginalUrlAttribute(),
                'sizes' => $this->allSizesForUse($this->resource, $mediaUse, $mediaUse->getOwnerType()),
                'type' => $mediaUse->media->type,
                'wheel' => $mediaUse->exterior,
                'hex1' => $mediaUse->media?->hex1,
                'hex2' => $mediaUse->media?->hex2,
            ]);
    }

    private function calculateTotalPrice($resource): float
    {
        return collect($resource['sections'])
            ->filter(fn($section) => $section['name'] !== 'Editions')
            ->map(function ($section) {
                if (is_array($section['selected'])) {
                    return $section['items']->filter(fn($item) => in_array($item['id'], $section['selected']))
                        ->sum('price');
                }

                $match = $section['items']
                    ->filter(fn($item) => $item['id'] == ($section['selected'] ?? $section['default']))
                    ->first();
                return $match['price'] ?? 0;
            })
            ->sum();
    }

    private function actions(): array
    {
        $enquireForm = $testDriveForm = null;
        if (Settings::get('configurator-enquiry-form')) {
            $enquiry = Form::query()
                ->with('sections.questions')
                ->where('is_active', 1)
                ->find(Settings::get('configurator-enquiry-form'));
            if ($enquiry) {
                $enquireForm = new FormViewResource($enquiry)->setValues([
                    'car_configuration_model_id' => $this->resource->id,
                ]);
            }
        }
        if (Settings::get('configurator-test-drive-form')) {
            $testDrive = Form::query()
                ->with('sections.questions')
                ->where('is_active', 1)
                ->find(Settings::get('configurator-test-drive-form'));
            if ($testDrive) {
                $testDriveForm = new FormViewResource($testDrive)->setValues([
                    'car_configuration_model_id' => $this->resource->id,
                ]);
            }
        }
        return [
            'enquire' => $enquireForm,
            'test_drive' => $testDriveForm,
        ];
    }

    private function getCheapestOfferStartingPrice(): ?float
    {
        return VehicleOffer::query()
            ->active()
            ->where('make_id', $this->resource->make_id)
            ->where('model_id', $this->resource->model_id)
            ->whereHas('cheapestFinance')
            ->whereHas('offerType', fn($typeQuery) => $typeQuery->where('slug', 'new')
                ->orWhereHas('parent', fn($childQuery) => $childQuery->where('slug', 'new')))
            ->orderBy('price')
            ->first()
            ?->cheapestFinance->monthly_price;
    }
}
