<?php

namespace App\Observers;

use App\Facades\Labels;
use App\Facades\Settings;
use App\Jobs\ExportVehicleToAutoTrader;
use App\Jobs\RemoveAutoTraderReferences;
use App\Services\MeilisearchService;
use Carbon\Carbon;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Str;
use App\VehicleRepository;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Mtc\MercuryDataModels\Country;
use Mtc\MercuryDataModels\Label;
use Mtc\MercuryDataModels\TenantType;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\Stemmer\Stemmer;

class VehicleObserver
{
    use DispatchesJobs;

    private static $labels = [];

    public function __construct(
        private readonly VehicleRepository $repository,
        private readonly MeilisearchService $meilisearchService,
    ) {
    }

    /**
     * Handle the Vehicle "created" event.
     *
     * @param Vehicle $vehicle
     * @return void
     */
    public function creating(Vehicle $vehicle): void
    {
        if (empty($vehicle->getAttribute('slug'))) {
            $this->createUniqueSlug($vehicle);
        }

        $this->setOrderValue($vehicle);
        $this->syncLabels($vehicle);
    }

    public function saving(Vehicle $vehicle): void
    {
        $this->normalizeRegistrationNumber($vehicle);

        if ($this->shouldUpdateTitle($vehicle)) {
            $vehicle->title = $this->generateVehicleTitle($vehicle);
        }

        $this->checkStatusTimestampUpdates($vehicle);

        if ($this->shouldUpdateSlug($vehicle)) {
            $this->createUniqueSlug($vehicle);
        }

        if ($this->updateSearchContent()) {
            $this->setSearchableContent($vehicle);
        }
    }

    public function saved(Vehicle $vehicle): void
    {
        $this->syncLabels($vehicle);

        $this->trackPriceChanges($vehicle);

        if (empty($vehicle->stock_provider)) {
            $this->meilisearchService->indexVehicle($vehicle);
        }
    }

    public function updating(Vehicle $vehicle): void
    {
        $this->syncLabels($vehicle);
    }

    public function deleting(Vehicle $vehicle): void
    {
        if (Settings::get('sales-channels-auto-trader-enabled') && Settings::get('auto-trader-hub-token')) {
            Bus::chain([
                new ExportVehicleToAutoTrader($vehicle),
                new RemoveAutoTraderReferences($vehicle)
            ])->dispatch();
        }

        if (empty($vehicle->stock_provider)) {
            $this->meilisearchService->deleteVehicle($vehicle->id);
        }
    }

    private function generateVehicleTitle(Vehicle $vehicle): string
    {
        return trim($vehicle->make()->first()?->name . ' ' . $vehicle->model()->first()?->name);
    }

    /**
     *
     * @param Vehicle $vehicle
     * @return void
     */
    private function normalizeRegistrationNumber(Vehicle $vehicle)
    {
        if (!empty($vehicle->registration_number)) {
            $country = Settings::get('app-details-country') ?? config('app.default_country');
            $vehicle->registration_number = Country::normalizeNumberPlate(
                $country,
                $vehicle->registration_number,
                $vehicle->personalized_number_plate ?? false
            );
            $vehicle->vrm_condensed = str_replace(' ', '', $vehicle->registration_number);
        }
    }

    /**
     * Create a unique slug for the vehicle
     *
     * @param Vehicle $vehicle
     * @return void
     */
    private function createUniqueSlug(Vehicle $vehicle): void
    {
        $slug = $vehicle->slug = $this->repository->buildSlug($vehicle);
        // Protection for title/reg-make-model flows where data is missing to use uuid
        if (empty(trim($slug, '-')) && !empty($vehicle->uuid)) {
            $slug = $this->repository->buildSlug($vehicle, 'uuid');
        }
        while (
            Vehicle::query()
                ->where('slug', $vehicle->slug)
                ->when($vehicle->exists, fn ($q) => $q->whereKeyNot($vehicle->getKey()))
                ->exists()
        ) {
            $vehicle->slug = $slug . '-' . strtolower(Str::random(12));
        }
    }


    /**
     * Set the search_content column value based on data
     *
     * @param Vehicle $vehicle
     * @return void
     */
    public function setSearchableContent(Vehicle $vehicle, bool $from_event = false): void
    {
        $fields = [
            'make_id',
            'model_id',
            'title',
            'derivative',
            'body_style_id',
            'fuel_type_id',
            'registration_number',
            'vrm_condensed',
            'vin',
            'uuid',
        ];

        if ($vehicle->isDirty($fields) === false && !$from_event) {
            return;
        }

        $searchContent = collect([
            'make_id' => $vehicle->make?->name,
            'model_id' => $vehicle->model?->name,
            'title' => $vehicle->title,
            'derivative' => $vehicle->derivative,
            'body_style_id' => $vehicle->bodyStyle?->name,
            'fuel_type_id' => $vehicle->fuelType?->name,
        ])->filter()
            ->map(fn (string $value) => Stemmer::multiStem($value))
            ->push($vehicle->slug)
            ->push($vehicle->vrm_condensed)
            ->push($this->stemIfDifferent($vehicle->vrm_condensed))
            ->push($vehicle->registration_number)
            ->push($vehicle->vin)
            ->push($vehicle->uuid);

        if (Settings::get('search-include-vehicle-attributes', false)) {
            $this->appendAttributeValues($vehicle, $searchContent);
        }

        $vehicle->search_content = $searchContent
            ->filter()
            ->flatten()
            ->implode(' ');
    }

    private function appendAttributeValues(Vehicle $vehicle, \Illuminate\Support\Collection $searchContent): void
    {
        $slugs = Settings::get('search-vehicle-attribute-slugs', []);

        if (empty($slugs)) {
            return;
        }

        $vehicle->attributeValues
            ->whereIn('slug', (array) $slugs)
            ->map(fn ($attributeValue) => $attributeValue->getValue())
            ->filter()
            ->each(fn ($value) => $searchContent->push($value));
    }

    private function syncLabels(Vehicle $vehicle)
    {
        if (empty($vehicle->id)) {
            return;
        }

        if (!isset(self::$labels[tenant('id')])) {
            self::$labels[tenant('id')] = Label::query()
                ->where('active', 1)
                ->with('rules')
                ->whereNotNull('data')
                ->get();
        }

        $labels = self::$labels[tenant('id')] ?? collect();

        if ($labels->count() === 0) {
            return;
        }

        // Auto assign
        $labels->filter(fn (Label $label) => !empty($label->data['auto_assign']))
            ->filter(fn ($label) => Labels::shouldAssign($vehicle, $label))
            ->tap(fn ($labels) => $labels->count() && $vehicle->labels()->syncWithoutDetaching($labels->pluck('id')));

        // Auto revoke
        $labels->filter(fn (Label $label) => !empty($label->data['auto_revoke']))
            ->filter(fn ($label) => Labels::shouldRevoke($vehicle, $label))
            ->tap(fn ($labels) => $labels->count() && $vehicle->labels()->detach($labels->pluck('id')));
    }

    private function stemIfDifferent(?string $value): ?string
    {
        if (empty($value)) {
            return null;
        }

        $stemmed = implode(' ', Stemmer::multiStem($value));

        return $stemmed !== strtolower($value) ? $stemmed : null;
    }

    private function updateSearchContent(): bool
    {
        return Config::get('vehicles.search-content-observer-enabled', true);
    }

    private function shouldUpdateTitle(Vehicle $vehicle): bool
    {
        return Settings::get('auto-update-vehicle-title')
            && (
                empty($vehicle->title)
                || $vehicle->isDirty(['make_id', 'model_id'])
                || $vehicle->title != $this->generateVehicleTitle($vehicle)
            );
    }

    private function shouldUpdateSlug(Vehicle $vehicle): bool
    {
        return Settings::get('auto-update-vehicle-slug')
            && (
                $vehicle->isDirty(['make_id', 'model_id', 'registration_number', 'title'])
                || Vehicle::query()->where('slug', $vehicle->slug)->count() > 1
            );
    }

    private function trackPriceChanges(Vehicle $vehicle): void
    {
        if (is_null($vehicle->price)) {
            return;
        }

        // Compare as floats rounded to 2 decimal places to avoid type coercion issues
        // (e.g., string "10000" from CSV vs float 10000.00 from database)
        $currentPrice = round((float) $vehicle->price, 2);
        $originalPrice = round((float) $vehicle->getOriginal('price'), 2);

        if (!$vehicle->isDirty('price') || $currentPrice === $originalPrice) {
            return;
        }

        if (Settings::get('vehicles-price-change-updates-original-price')) {
            if (!is_null($vehicle->original_price) && $vehicle->price >= $vehicle->original_price) {
                // If price has gone above original price, reset original price to be hidden
                $vehicle->original_price = null;
            } elseif (
                $vehicle->price < $vehicle->getOriginal('price')
                && (is_null($vehicle->original_price) || $vehicle->original_price < $vehicle->getOriginal('price'))
            ) {
                // If price has gone down and original price is not set or value is lower than the price before change
                $vehicle->original_price = $vehicle->getOriginal('price');
            }
        }

        $vehicle->priceHistory()->create(['price' => $vehicle->price]);
    }

    private function checkStatusTimestampUpdates(Vehicle $vehicle): void
    {
        if ($vehicle->isDirty('is_published') && $vehicle->is_published && empty($vehicle->published_at)) {
            $vehicle->published_at = Carbon::now();
        }

        if ($vehicle->isDirty('is_sold') && $vehicle->is_sold && empty($vehicle->sold_at)) {
            $vehicle->sold_at = Carbon::now();
        }

        if ($vehicle->isDirty('is_reserved') && $vehicle->is_reserved && empty($vehicle->reserved_at)) {
            $vehicle->reserved_at = Carbon::now();
        }
    }

    private function setOrderValue(Vehicle $vehicle): void
    {
        if (!is_null($vehicle->order)) {
            return;
        }

        if (tenant()->type === TenantType::LISTING->value) {
            $vehicle->order = 999999;
            return;
        }

        $maxOrder = Vehicle::max('order');
        $vehicle->order = is_null($maxOrder) ? 0 : $maxOrder + 1;
    }
}
