<?php

namespace App\Observers;

use App\Facades\Labels;
use App\Facades\Settings;
use Carbon\Carbon;
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\Vehicle;
use Mtc\Stemmer\Stemmer;

class VehicleObserver
{
    use DispatchesJobs;

    private static $labels = [];

    public function __construct(private readonly VehicleRepository $repository)
    {
        //
    }

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

        $this->syncLabels($vehicle);

        $this->syncOriginalPrice($vehicle);
    }

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

        if ($this->shouldUpdateTitle($vehicle)) {
            $vehicle->title = $vehicle->make?->name . ' ' . $vehicle->model?->name;
        }

        $this->checkStatusTimestampUpdates($vehicle);

        if ($this->shouldUpdateSlug($vehicle)) {
            $vehicle->slug = $this->repository->buildSlug($vehicle);
        }

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

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

        $this->trackPriceChanges($vehicle);
    }

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

        $this->syncOriginalPrice($vehicle);
    }

    /**
     *
     * @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 slug for the vehicle
     *
     * @param Vehicle $vehicle
     * @return void
     */
    private function createSlug(Vehicle $vehicle): void
    {
        $i = 1;
        $slug = $vehicle->slug = $this->repository->buildSlug($vehicle);
        while (Vehicle::query()->where('slug', $vehicle->slug)->exists()) {
            $vehicle->slug = $slug . '-' . (++$i);
            if ($i > 100) {
                break;
            }
        }
    }


    /**
     * 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',
        ];

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

        $vehicle->search_content = 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($vehicle->registration_number)
            ->push($vehicle->vin)
            ->filter()
            ->flatten()
            ->implode(' ');
    }

    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 updateSearchContent(): bool
    {
        return Config::get('vehicles.search-content-observer-enabled', true);
    }

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

        if (Settings::get('auto-set-original-price') && is_null($vehicle->original_price)) {
            $vehicle->original_price = $vehicle->price;
        }
    }

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

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

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

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

        if (Settings::get('vehicles-price-change-updates-original-price')) {
            $vehicle->original_price = $vehicle->price < $vehicle->getOriginal('price')
                ? $vehicle->getOriginal('price')
                : null;
        }

        $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();
        }
    }
}
