<?php

namespace App\Jobs;

use App\Events\NewVehicleImported;
use App\Events\VehicleUpdatedFromImport;
use App\Facades\Settings;
use App\TaxonomyMap;
use App\Traits\EnsuresVehicleAttribute;
use App\Traits\MapsTaxonomies;
use Mtc\MercuryDataModels\VehicleType;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Mtc\ContentManager\Models\MediaSize;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\Media;
use Mtc\MercuryDataModels\TenantType;
use Mtc\MercuryDataModels\Vehicle;

class ImportMotorsVehicleBatch implements ShouldQueue
{
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;
    use MapsTaxonomies;
    use EnsuresVehicleAttribute;

    public const NAME = 'Motors';

    public $tries = 2;
    private Collection $batchStock;
    private array $vehicleAttributes;

    private array $allowed_sizes;

    private $startTime;

    /**
     * Create a new job instance.
     */
    public function __construct(
        private readonly Collection $batch,
        private readonly Collection $dealerships,
        private readonly string $filename,
        private readonly ?int $mediaFolderId = null,
    ) {
        $this->onQueue('motors-batch');
    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        $this->startTime = microtime(true);
        $this->retrieveRequiredVehicleAttributes();
        $this->ensureDealershipsForBatch();

        $this->batchStock = Vehicle::query()
            ->withTrashed()
            ->whereIn('uuid', $this->batch->pluck(['vehicleid']))
            ->where('stock_provider', $this->getProviderName())
            ->with('defaultFinanceExample')
            ->withAttributeCount()
            ->withImageCount()
            ->get();

        $this->batch->map(function (Collection $vehicle) {
            try {
                $this->syncVehicle($vehicle);
            } catch (\Exception $exception) {
                if (!str_contains($exception->getMessage(), 'Duplicate entry')) {
                    Log::error(
                        'Motors sync failed with exception: ' . $exception->getMessage(),
                        $exception->getTrace()
                    );
                }
            }
        });
    }

    /**
     * Collate unique dealers from this batch and ensure each has a dealership record.
     * Done once per batch (rather than per-vehicle) to reduce the race window across workers.
     */
    private function ensureDealershipsForBatch(): void
    {
        if (
            tenant()->type !== TenantType::LISTING->value
            || !Settings::get('stock-motors-auto-create-dealerships', false)
        ) {
            return;
        }

        $this->batch
            ->filter(fn (Collection $row) => !empty($row['dealerid']))
            ->unique(fn (Collection $row) => $row['dealerid'])
            ->each(function (Collection $row) {
                $locationId = $row['dealerid'];

                if ($this->dealerships->where('stock_location', $locationId)->isNotEmpty()) {
                    return;
                }

                $dealership = Dealership::firstOrCreate(
                    ['data->motors-location-id' => $locationId],
                    [
                        'name' => $row['location-name'] ?? 'Dealer ' . $locationId,
                        'postcode' => $row['postcode'] ?? null,
                        'active' => true,
                        'location_stock' => true,
                        'data' => ['motors-location-id' => $locationId],
                    ]
                );

                $this->dealerships->push((object) [
                    'id' => $dealership->id,
                    'stock_location' => $locationId,
                ]);
            });
    }

    private function syncVehicle(Collection $data): void
    {
        /** @var Vehicle $vehicle */
        $vehicle = $this->batchStock->where('uuid', $data['vehicleid'])->first() ?? new Vehicle([
            'stock_provider' => $this->getProviderName(),
            'uuid' => $data['vehicleid'],
        ]);

        if ($vehicle->trashed()) {
            $vehicle->restore();
        }

        $make_id = $this->getMappedTaxonomy(TaxonomyMap::MAKE, $data['make'], $data->toArray());
        // update all
        $vehicle->fill([
            'is_published' => !empty($data['imageurls']),
            'price' => $data['price'],
            'was_recently_synced' => true,
            'pending_stock_sync' => null,
            'description' => $data['description'],
            'make_id' => $make_id,
            'model_id' => $this->getMappedTaxonomy(TaxonomyMap::MODEL, $data['model'], $data->toArray(), $make_id),
            'dealership_id' => $this->findDealership($data['dealerid']),
        ]);

        // only new
        if ($vehicle->exists === false) {
            if ($data['mileage'] < 0) {
                $data['mileage'] = 0;
            }
            $regDate = $data['regyear'] . '-01-01';
            if (!empty($data['registration-date'])) {
                try {
                    $regDate = Carbon::createFromFormat('d/m/Y', $data['registration-date']);
                } catch (\Exception) {
                }
            }
            $vehicle->fill([
                'title' => $data['make'] . ' ' . $data['model'],
                'body_style_id' => $this->getMappedTaxonomy(
                    TaxonomyMap::BODY_STYLE,
                    $data['body-type'],
                    $data->toArray()
                ),
                'derivative' => $data['variant'],
                'odometer_mi' => $data['mileage'],
                'manufacture_year' => $data['regyear'],
                'transmission_id' => $this->getMappedTaxonomy(
                    TaxonomyMap::TRANSMISSION,
                    $data['transmission'],
                    $data->toArray()
                ),
                'colour' => $data['colour'],
                'fuel_type_id' => $this->getMappedTaxonomy(TaxonomyMap::FUEL_TYPE, $data['fueltype'], $data->toArray()),
                'registration_number' => $data['vrm'],
                'cap_id' => $data['capid'],
                'type' => in_array($data['vehicletype'], ['VAN', 'TRUCK'])
                    ? VehicleType::LCV->value
                    : VehicleType::CAR->value,
                'first_registration_date' => $regDate,
                'door_count' => (int)$data['doors'] ?: null,
                'seats' => (int)$data['seats'] ?: null,
                'co2' => (int)$data['co2'] ?: null,
                'engine_size_cc' => (int)$data['engine-size'] ?: null,
                'mpg' => (int)$data['mpg'] ?: null,
            ]);
        }

        $vehicle->save();

        if ($vehicle->attribute_count < 2) {
            $this->setVehicleAttributes(
                [
                    'postcode' => $data['postcode'] ?? null,
                    'insurance_group' => (int)$data['insurance-group'] ?: null,
                    'location_name' => $data['location-name'] ?? null,
                ],
                $vehicle
            );
        }

        if ($this->shouldSyncImages($vehicle, $data)) {
            $this->syncImages($vehicle, $data['imageurls']);
        }

        $this->storeUnmappedTaxonomy($vehicle);

        if ($vehicle->wasRecentlyCreated) {
            Event::dispatch(new NewVehicleImported($vehicle, $data->toArray(), $this->getProviderName()));
        } else {
            Event::dispatch(new VehicleUpdatedFromImport($vehicle, $data->toArray(), $this->getProviderName()));
        }
    }

    private function vehicleCanQuickUpdate(Vehicle $vehicle, $data): bool
    {
        $filledAttributes = collect([$data['postcode'], $data['insurance-group']])->filter()->count();
        return $vehicle->exists
            && $vehicle->attribute_count >= $filledAttributes
            && $this->shouldSyncImages($vehicle, $data) == false;
    }

    private function retrieveRequiredVehicleAttributes(): void
    {
        $this->vehicleAttributes = [
            'postcode' => $this->getVehicleAttribute('Postcode', 'text')->toArray(),
            'insurance_group' => $this->getVehicleAttribute('Insurance Group', 'number')->toArray(),
            'location_name' => $this->getVehicleAttribute('Location Name', 'text')->toArray(),
        ];
    }


    /**
     * Check if images should be synced for vehicles
     *
     * @param Vehicle $vehicle
     * @param Collection $data
     * @return bool
     */
    private function shouldSyncImages(Vehicle $vehicle, Collection $data): bool
    {
        if (empty($data['imageurls'])) {
            return false;
        }

        return !empty($data['imageurls'])
            && empty($vehicle->image_count);
    }

    private function syncImages(Vehicle $vehicle, ?string $imageListString): void
    {
        if (empty($imageListString)) {
            return;
        }

        $images = collect(explode(',', $imageListString))
            ->map(fn ($image) => [
                'src' => str_replace('https://origin-resizer.images.autoexposure.co.uk/', '', $image),
                'image_provider' => self::NAME,
                'type' => 'image',
                'external' => true,
                'folder_id' => $this->mediaFolderId,
                'path' => '',
                'upload_date' => Carbon::now()->format('Y-m'),
            ]);

        Media::query()->upsert(
            $images->toArray(),
            ['src', 'image_provider']
        );

        $mediaRecords = Media::query()
            ->whereIn('src', $images->pluck('src'))
            ->where('image_provider', self::NAME)
            ->select(['id', 'src'])
            ->get();

        $this->setUsesForModel($mediaRecords, $vehicle, ['primary' => true]);
    }

    protected function getDetailsForTaxonomyMap(array $record): array
    {
        return [
            'tenant_id' => tenant('id'),
            'registration_number' => $record['vrm'],
            'make' => $record['make'],
            'model' => $record['model'],
            'fuel_type' => $record['fueltype'],
            'transmission' => $record['transmission'],
        ];
    }

    /**
     * Look up the dealership id for a given Motors location.
     * Dealerships are pre-created in ensureDealershipsForBatch().
     */
    private function findDealership(?string $locationId): ?string
    {
        if (empty($locationId)) {
            return null;
        }

        return $this->dealerships
            ->where('stock_location', $locationId)
            ->first()
            ?->id;
    }

    protected function getProviderName(): string
    {
        return 'motors';
    }

    protected function setUsesForModel(Collection $media, Vehicle $model, array $meta): void
    {
        $data = $media->map(fn ($media, $index) => [
            'media_id' => $media->id,
            'owner_type' => $model->getMorphClass(),
            'owner_id' => $model->id,
            // update meta from passed with fallback of media object default
            'alt_text' => $meta['alt_text'] ?? $media->getAttribute('alt_text'),
            'title' => $meta['title'] ?? $media->getAttribute('title'),
            'caption' => $meta['caption'] ?? $media->getAttribute('caption'),
            'description' => $meta['description'] ?? $media->getAttribute('description'),
            'dimensions' => $meta['dimensions'] ?? null,
            'flags' => $meta['flags'] ?? null,
            'order' => $index,
            'primary' => $index == 0 && ($meta['primary'] ?? null),
            'secondary' => $meta['secondary'] ?? null,
            'allowed_sizes' => json_encode($this->getAllowedSizes($model)),
        ]);

        $model->mediaUses()->upsert($data->toArray(), ['media_id', 'owner_type', 'owner_id']);
    }

    protected function getAllowedSizes(Model $model): array
    {
        if (!isset($this->allowed_sizes)) {
            $model_dimensions = MediaSize::query()
                ->where('model', $model->getMorphClass())
                ->whereNotNull('width')
                ->whereNotNull('height')
                ->get()
                ->map(fn (MediaSize $size) => $size->width . 'x' . $size->height)
                ->toArray();

            $this->allowed_sizes = array_merge(
                config('media.thumbnail_sizes', []),
                $model_dimensions
            );
        }
        return $this->allowed_sizes;
    }
}
