<?php

namespace App\Services;

use App\TaxonomyMap;
use App\TaxonomyMapable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use Mtc\MercuryDataModels\NewCar;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleOffer;

class TaxonomyMappingService
{
    private string $loadedProvider = '';
    private array $unmappedTaxonomies = [];

    public function getMappedTaxonomy(
        string $provider,
        string $taxonomy,
        $value,
        ?array $vehicle_data = null,
        ?int $make_id = null
    ) {
        if (empty($value) || empty(trim((string) $value))) {
            return null;
        }

        if ($this->loadedProvider !== $provider) {
            $this->eagerLoadTaxonomyMapping($provider);
        }

        return match ($taxonomy) {
            'model' => $this->getModelMappedTaxonomy($provider, $taxonomy, $value, $vehicle_data, $make_id),
            default => $this->getDefaultMappedTaxonomy($provider, $taxonomy, $value, $vehicle_data),
        };
    }

    public function storeUnmappedTaxonomies(Vehicle|VehicleOffer|NewCar $vehicle): void
    {
        foreach ($this->unmappedTaxonomies as $unmappedTaxonomy) {
            $existingRecord = TaxonomyMapable::query()
                ->where('taxonomy_map_id', $unmappedTaxonomy->id)
                ->where('mappable_type', $vehicle->getMorphClass())
                ->where('mappable_id', $vehicle->id)
                ->where('tenant', tenant('id'))
                ->first();

            if (!$existingRecord) {
                TaxonomyMapable::query()
                    ->create([
                        'tenant' => tenant('id'),
                        'taxonomy_map_id' => $unmappedTaxonomy->id,
                        'mappable_type' => $vehicle->getMorphClass(),
                        'mappable_id' => $vehicle->id,
                    ]);
            }
        }

        $this->unmappedTaxonomies = [];
    }

    private function getDefaultMappedTaxonomy(string $provider, string $taxonomy, $value, ?array $vehicle_data = null)
    {
        if (!isset($this->{$taxonomy}[$value])) {
            $this->{$taxonomy}[$value] = $this->search($provider, $taxonomy, $value, $vehicle_data);
        }

        if (!$this->{$taxonomy}[$value]) {
            $this->unmappedTaxonomies[] = $this->getTaxonomy($provider, $taxonomy, $value, $vehicle_data);
        }

        return $this->{$taxonomy}[$value];
    }

    private function getModelMappedTaxonomy(
        string $provider,
        string $taxonomy,
        $value,
        ?array $vehicle_data = null,
        ?int $make_id = null
    ) {
        if (empty($make_id)) {
            $make_id = 0;
        }

        if (!isset($this->{$taxonomy}[$make_id][$value])) {
            $this->{$taxonomy}[$make_id][$value] = $this->search($provider, $taxonomy, $value, $vehicle_data, $make_id);
        }

        if (!$this->{$taxonomy}[$make_id][$value]) {
            $this->unmappedTaxonomies[] = $this->getTaxonomy(
                $provider,
                $taxonomy,
                $value,
                $vehicle_data,
                $make_id
            );
        }
        return $this->{$taxonomy}[$make_id][$value];
    }

    private function eagerLoadTaxonomyMapping(string $provider): void
    {
        TaxonomyMap::query()
            ->where('provider', $provider)
            ->whereNotIn('taxonomy_type', ['model', 'master-model'])
            ->get()
            ->groupBy('taxonomy_type')
            ->each(function (Collection $group, $taxonomy) {
                $this->{$taxonomy} = $group->keyBy('term')
                    ->map(fn($entry) => $entry->taxonomy_id)
                    ->toArray();
            });

        $this->loadedProvider = $provider;
    }

    protected function getTaxonomy(
        string $provider,
        string $type,
        string $term,
        array $details = [],
        ?int $make_id = null
    ): Model {
        if (!str_contains($type, 'master-')) {
            $type = 'master-' . $type;
        }

        $search = [
            'provider' => $provider,
            'taxonomy_type' => $type,
            'term' => $term,
        ];

        if (in_array($type, ['model', 'master-model'])) {
            $search['parent_id'] = $make_id;
        }

        return TaxonomyMap::query()->firstOrCreate($search, ['details' => $details]);
    }

    public function search(
        string $provider,
        string $type,
        string $term,
        array $details = [],
        ?int $make_id = null
    ): ?int {
        if (empty($term)) {
            return null;
        }
        if (!str_contains($type, 'master-')) {
            $type = 'master-' . $type;
        }

        /** @var TaxonomyMap $taxonomy */
        $taxonomy = $this->getTaxonomy($provider, $type, $term, $details, $make_id);

        if ($taxonomy->taxonomy_id) {
            // Taxonomy was already mapped for this provider
            return $taxonomy->taxonomy_id;
        }

        $class_name = Relation::getMorphedModel($type);
        if (!$class_name) {
            Log::error("Unrecognized type looking for taxonomy mapping: $type");
            return $taxonomy->taxonomy_id;
        }

        $for_model = in_array($type, ['master-model', 'model']);
        $term_match = App::make($class_name)->newQuery()
            ->when($for_model && $make_id, fn($query) => $query->where('make_id', $make_id))
            ->where('name', $term)
            ->first();

        // Exact match of the term
        if ($term_match) {
            $taxonomy->update(['taxonomy_id' => $term_match->id]);
            return $term_match->id;
        }

        // We couldn't find the taxonomy term for an existing model (make, fuel etc).
        // Check if we have this taxonomy term mapped for a different provider.
        $mapped_taxonomy = TaxonomyMap::query()
            ->whereNotNull('taxonomy_id')
            ->where('provider', '!=', $provider)
            ->where('taxonomy_type', '=', $type)
            ->where('term', '=', $term)
            ->when($for_model, fn($query) => $query->where('parent_id', $make_id))
            ->first();

        if ($mapped_taxonomy) {
            $taxonomy->taxonomy_id = $mapped_taxonomy->taxonomy_id;
            $taxonomy->save();
        }
        return $taxonomy->taxonomy_id;
    }
}
