<?php

namespace App\Jobs;

use App\Mail\OrphanedTaxonomyRemovedMail;
use App\TaxonomyMap;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Broadcasting\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Mail;
use Mtc\MercuryDataModels\NewCar;
use Mtc\MercuryDataModels\Tenant;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleOffer;

class RemoveOrphanedTaxonomiesFromModelsJob implements ShouldQueue, ShouldBeUnique
{
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;

    private Collection $orphaned_taxonomy_makes;
    private Collection $orphaned_taxonomy_models;
    private Collection $affected_models;

    /**
     * Create a new job instance.
     */
    public function __construct(private Collection $orphaned_taxonomy_maps)
    {
        $this->orphaned_taxonomy_makes = $this->orphaned_taxonomy_maps
            ->filter(fn ($map) => in_array($map->taxonomy_type, ['make', 'master-make']));

        $this->orphaned_taxonomy_models = $this->orphaned_taxonomy_maps
            ->filter(fn ($map) => in_array($map->taxonomy_type, ['model', 'master-model']));
    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        if (
            $this->orphaned_taxonomy_makes->isNotEmpty()
            || $this->orphaned_taxonomy_models->isNotEmpty()
        ) {
            $affected_tenants = Tenant::query()->get()->mapWithKeys(fn ($tenant) => [
                $tenant->name => $this->processOrphanedMapsForTenant($tenant),
            ])->filter(fn ($records) => $records['vehicles']->isNotEmpty()
                || $records['vehicle_offers']->isNotEmpty()
                || $records['new_cars']->isNotEmpty());

            $this->sendNotification($affected_tenants);
        }

        $this->deleteOrphanedMaps();
    }

    private function processOrphanedMapsForTenant(Tenant $tenant): Collection
    {
        tenancy()->initialize($tenant);

        $affected_vehicles = $this->handleforModel(Vehicle::class);
        $affected_vehicle_offers = $this->handleforModel(VehicleOffer::class);
        $affected_new_cars = $this->handleforModel(NewCar::class);

        return $this->mapAffectedRecordData($affected_vehicles, $affected_vehicle_offers, $affected_new_cars);
    }

    private function mapAffectedRecordData(
        Collection $affected_vehicles,
        Collection $affected_vehicle_offers,
        Collection $affected_new_cars,
    ): Collection {
        return collect([
            'vehicles' => $affected_vehicles->map(fn ($vehicle) => [
                'id' => $vehicle->id,
                'name' => $vehicle->title,
                'slug' => $vehicle->slug,
                'vrm' => $vehicle->vrm_condensed,
            ]),
            'vehicle_offers' => $affected_vehicle_offers->map(fn ($offer) => [
                'id' => $offer->id,
                'name' => $offer->name,
                'slug' => $offer->slug,
            ]),
            'new_cars' => $affected_new_cars->map(fn ($new_car) => [
                'id' => $new_car->id,
                'name' => $new_car->name,
                'slug' => $new_car->slug,
            ]),
        ]);
    }

    /**
     * return the list of models affected by missing make and/or model taxonomy
     *
     * @param string $model_name
     * @return Collection
     */
    private function handleforModel(string $model_name): Collection
    {
        $affected_by_make = $this->setAffectedModelColumnToNull(
            $model_name,
            'make_id',
            $this->orphaned_taxonomy_makes
        );

        $affected_by_model = $this->setAffectedModelColumnToNull(
            $model_name,
            'model_id',
            $this->orphaned_taxonomy_models
        );

        return $affected_by_make->merge($affected_by_model);
    }

    private function setAffectedModelColumnToNull(
        string $model_name,
        string $column,
        Collection $orphaned_taxonomy_items
    ): Collection {
        return App::make($model_name)->query()
            ->whereIn($column, $orphaned_taxonomy_items->pluck('taxonomy_id'))
            ->get()
            ->mapWithKeys(function ($affected) use ($column) {
                $affected->update([
                    $column => null,
                ]);

                return [
                    $affected->id => $affected,
                ];
            });
    }

    private function deleteOrphanedMaps(): void
    {
        TaxonomyMap::whereIn('id', $this->orphaned_taxonomy_maps->pluck('id'))->delete();
    }

    private function sendNotification(Collection $affected_tenants): void
    {
        Mail::to(config('mail.developer_alerts'))->send(new OrphanedTaxonomyRemovedMail(
            $this->orphaned_taxonomy_makes,
            $this->orphaned_taxonomy_models,
            $affected_tenants,
        ));
    }
}
