<?php

namespace App\Services;

use App\OfferRepository;
use App\Traits\EnsuresVehicleAttribute;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Mtc\ContentManager\Models\Template;
use Mtc\MercuryDataModels\BodyStyleType;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\DrivetrainType;
use Mtc\MercuryDataModels\Form;
use Mtc\MercuryDataModels\Franchise;
use Mtc\MercuryDataModels\FuelType;
use Mtc\MercuryDataModels\OfferType;
use Mtc\MercuryDataModels\TransmissionType;
use Mtc\MercuryDataModels\VehicleAttribute;
use Mtc\MercuryDataModels\VehicleAttributeValue;
use Mtc\MercuryDataModels\VehicleMake;
use Mtc\MercuryDataModels\VehicleModel;
use Mtc\MercuryDataModels\VehicleOffer;

class OfferBulkUpdateService
{
    use EnsuresVehicleAttribute;

    /**
     * Final-step actions. When offer versioning is enabled, `publish` marks
     * the snapshot version active and `draft` ("Save as version") leaves it
     * inactive; both apply the field change live, mirroring the single-edit
     * save. No effect when versioning is disabled.
     */
    public const VERSION_ACTION_PUBLISH = 'publish';
    public const VERSION_ACTION_DRAFT = 'draft';

    private const EXCLUDED_FIELDS = [
        'slug',
        'uuid',
        'stock_provider',
        'vin',
        'data',
        'seo',
        'order',
        'key_features',
        'standard_spec',
        'technical_spec',
        'cap_id',
        'auto_trader_id',
        'motor_check_id',
        'new_car_id',
        'trim_id',
    ];

    private const BOOLEAN_FIELDS = [
        'published',
        'featured',
    ];

    private const INTEGER_FIELDS = [
        'engine_size_cc',
        'co2',
        'battery_range',
        'battery_capacity_kwh',
        'battery_usable_capacity_kwh',
        'battery_charge_time',
        'battery_quick_charge_time',
        'battery_quick_charge_level',
    ];

    private const DECIMAL_FIELDS = [
        'price',
        'full_price',
        'deposit',
        'mpg',
    ];

    private const DATE_FIELDS = [
        'published_at',
        'unpublished_at',
    ];

    private const RELATIONSHIP_FIELDS = [
        'make_id' => [
            'label' => 'Make',
            'model' => VehicleMake::class,
            'label_column' => 'name',
        ],
        'model_id' => [
            'label' => 'Model',
            'model' => VehicleModel::class,
            'label_column' => 'name',
        ],
        'dealership_id' => [
            'label' => 'Dealership',
            'model' => Dealership::class,
            'label_column' => 'name',
        ],
        'franchise_id' => [
            'label' => 'Franchise',
            'model' => Franchise::class,
            'label_column' => 'name',
        ],
        'fuel_type_id' => [
            'label' => 'Fuel Type',
            'model' => FuelType::class,
            'label_column' => 'name',
        ],
        'transmission_id' => [
            'label' => 'Transmission',
            'model' => TransmissionType::class,
            'label_column' => 'name',
        ],
        'body_style_id' => [
            'label' => 'Body Style',
            'model' => BodyStyleType::class,
            'label_column' => 'name',
        ],
        'drivetrain_id' => [
            'label' => 'Drivetrain',
            'model' => DrivetrainType::class,
            'label_column' => 'name',
        ],
        'type_id' => [
            'label' => 'Offer Type',
            'model' => OfferType::class,
            'label_column' => 'name',
        ],
        'form_id' => [
            'label' => 'Enquiry Form',
            'model' => Form::class,
            'label_column' => 'name',
        ],
        'test_drive_form_id' => [
            'label' => 'Test Drive Form',
            'model' => Form::class,
            'label_column' => 'name',
        ],
        'template_id' => [
            'label' => 'Template',
            'model' => Template::class,
            'label_column' => 'name',
        ],
    ];

    private const SELECT_FIELDS = [
        'vehicle_type' => [
            'label' => 'Vehicle Type',
            'options' => [
                ['value' => 'car', 'label' => 'Car'],
                ['value' => 'lcv', 'label' => 'LCV'],
                ['value' => 'motorcycle', 'label' => 'Motorcycle'],
            ],
        ],
        'new_car_type' => [
            'label' => 'New Car Type',
            'options' => [
                ['value' => 'new', 'label' => 'New'],
                ['value' => 'used', 'label' => 'Used'],
            ],
        ],
    ];

    private const FIELD_LABELS = [
        'name' => 'Name',
        'derivative' => 'Derivative',
        'price' => 'Price',
        'full_price' => 'Full Price',
        'deposit' => 'Deposit',
        'mpg' => 'MPG',
        'published' => 'Published',
        'featured' => 'Featured',
        'published_at' => 'Published At',
        'unpublished_at' => 'Unpublished At',
        'colour' => 'Colour',
        'trim' => 'Trim',
        'engine_size_cc' => 'Engine Size (cc)',
        'co2' => 'CO2',
        'battery_range' => 'Battery Range',
        'battery_capacity_kwh' => 'Battery Capacity (kWh)',
        'battery_usable_capacity_kwh' => 'Usable Battery Capacity (kWh)',
        'battery_charge_time' => 'Battery Charge Time',
        'battery_quick_charge_time' => 'Quick Charge Time',
        'battery_quick_charge_level' => 'Quick Charge Level',
        'description' => 'Description',
        'excerpt' => 'Excerpt',
        'label' => 'Label',
        'youtube_video' => 'YouTube Video',
        't_and_cs' => 'Terms & Conditions',
        'cta_button1_label' => 'CTA Button 1 Label',
        'cta_button1_url' => 'CTA Button 1 URL',
        'cta_button2_label' => 'CTA Button 2 Label',
        'cta_button2_url' => 'CTA Button 2 URL',
        'footnote' => 'Footnote',
    ];

    public function getFields(): array
    {
        $offer = new VehicleOffer();
        $fillable = $offer->getFillable();
        $fields = [];

        foreach ($fillable as $field) {
            if (in_array($field, self::EXCLUDED_FIELDS)) {
                continue;
            }

            $fieldData = $this->getFieldMetadata($field);
            if ($fieldData) {
                $fields[] = $fieldData;
            }
        }

        $attributes = VehicleAttribute::query()->where('model', 'offer')->get();
        foreach ($attributes as $attribute) {
            $fields[] = $this->getAttributeFieldMetadata($attribute);
        }

        usort($fields, fn ($a, $b) => strcmp($a['label'], $b['label']));

        return $fields;
    }

    public function getAllowedFields(): array
    {
        return array_column($this->getFields(), 'name');
    }

    public function getOfferData(array $ids): array
    {
        $allowedFields = $this->getAllowedFields();
        $modelFields = array_filter($allowedFields, fn ($f) => !$this->isAttributeField($f));

        return VehicleOffer::query()
            ->whereIn('id', $ids)
            ->with('attributeValues')
            ->get()
            ->mapWithKeys(function (VehicleOffer $offer) use ($modelFields, $allowedFields) {
                $data = collect($offer->toArray())
                    ->only($modelFields)
                    ->toArray();

                foreach ($allowedFields as $field) {
                    if ($this->isAttributeField($field)) {
                        $attributeId = $this->getAttributeId($field);
                        $attributeValue = $offer->attributeValues
                            ->where('attribute_id', $attributeId)
                            ->first();
                        $data[$field] = $attributeValue?->getValue() ?? null;
                    }
                }

                return [$offer->id => $data];
            })
            ->toArray();
    }

    public function update(
        string $mode,
        array $ids,
        array $data,
        string $versionAction = self::VERSION_ACTION_PUBLISH
    ): int {
        $allowedFields = $this->getAllowedFields();

        return DB::transaction(function () use ($mode, $ids, $data, $allowedFields, $versionAction) {
            if ($mode === 'same') {
                return $this->updateSameValue($ids, $data['field'], $data['value'], $allowedFields, $versionAction);
            }

            return $this->updateDifferentValues($ids, $data['updates'], $allowedFields, $versionAction);
        });
    }

    /**
     * Whether offer versioning is enabled for the current tenant. Drives the
     * final-step "Save & publish" / "Save as version" choice in the UI.
     */
    public function versioningEnabled(): bool
    {
        return app(OfferRepository::class)->hasVersioning();
    }

    /**
     * Persist a per-model field change and, when versioning is enabled, retain
     * a version snapshot. saveBulkVersion runs while the model is still dirty
     * so the snapshot's `data` captures the new values.
     */
    private function persistWithVersion(VehicleOffer $offer, string $versionAction): void
    {
        if ($this->versioningEnabled()) {
            app(OfferRepository::class)->saveBulkVersion(
                $offer,
                (int) Auth::id(),
                $versionAction === self::VERSION_ACTION_PUBLISH
            );
        }

        $offer->save();
    }

    public function deleteFinanceExamples(array $ids): int
    {
        $deletedCount = 0;

        $offers = VehicleOffer::query()->whereIn('id', $ids)->get();

        foreach ($offers as $offer) {
            $deleted = $offer->finance()->delete();
            if ($deleted > 0) {
                $deletedCount++;
            }
        }

        return $deletedCount;
    }

    /**
     * Soft-delete the selected offers via per-model delete() so SoftDeletes
     * events and audit rows are written. Returns the number of rows
     * that were actually archived (already-archived rows are skipped).
     */
    public function archive(array $ids): int
    {
        $archived = 0;
        $offers = VehicleOffer::query()->whereIn('id', $ids)->get();

        foreach ($offers as $offer) {
            if ($offer->trashed()) {
                continue;
            }
            $offer->delete();
            $archived++;
        }

        return $archived;
    }

    private function updateSameValue(
        array $ids,
        string $field,
        mixed $value,
        array $allowedFields,
        string $versionAction = self::VERSION_ACTION_PUBLISH
    ): int {
        if (!in_array($field, $allowedFields)) {
            throw new \InvalidArgumentException("Field '{$field}' is not allowed for bulk update.");
        }

        if ($this->isAttributeField($field)) {
            $attributeId = $this->getAttributeId($field);
            $attribute = VehicleAttribute::query()->find($attributeId);
            if (!$attribute) {
                return 0;
            }

            $offers = VehicleOffer::query()->whereIn('id', $ids)->get();
            foreach ($offers as $offer) {
                $this->updateAttributeValue($offer, $attribute, $value);
            }

            return $offers->count();
        }

        $value = $this->castValue($field, $value);

        // Fast path when versioning is off: single mass update, no per-model
        // events (unchanged behaviour for non-versioning tenants).
        if (!$this->versioningEnabled()) {
            return VehicleOffer::query()
                ->whereIn('id', $ids)
                ->update([$field => $value]);
        }

        $offers = VehicleOffer::query()->whereIn('id', $ids)->get();
        foreach ($offers as $offer) {
            $offer->{$field} = $value;
            $this->persistWithVersion($offer, $versionAction);
        }

        return $offers->count();
    }

    private function updateDifferentValues(
        array $ids,
        array $updates,
        array $allowedFields,
        string $versionAction = self::VERSION_ACTION_PUBLISH
    ): int {
        $updatedCount = 0;
        $versioning = $this->versioningEnabled();

        foreach ($updates as $offerId => $fields) {
            if (!in_array((int) $offerId, $ids)) {
                continue;
            }

            $modelFields = [];
            $attributeFields = [];

            foreach ($fields as $field => $value) {
                if (!in_array($field, $allowedFields)) {
                    continue;
                }

                if ($this->isAttributeField($field)) {
                    $attributeFields[$field] = $value;
                } else {
                    $modelFields[$field] = $this->castValue($field, $value);
                }
            }

            $updated = false;
            // Load the model once when versioning is on (needed for the
            // snapshot), or when there are attribute values to write.
            $offer = ($versioning || !empty($attributeFields))
                ? VehicleOffer::query()->find($offerId)
                : null;

            if (!empty($modelFields)) {
                if ($versioning && $offer) {
                    $offer->fill($modelFields);
                    $this->persistWithVersion($offer, $versionAction);
                    $updated = true;
                } else {
                    $result = VehicleOffer::query()
                        ->where('id', $offerId)
                        ->update($modelFields);
                    if ($result) {
                        $updated = true;
                    }
                }
            }

            if (!empty($attributeFields) && $offer) {
                foreach ($attributeFields as $field => $value) {
                    $attributeId = $this->getAttributeId($field);
                    $attribute = VehicleAttribute::query()->find($attributeId);
                    if ($attribute) {
                        $this->updateAttributeValue($offer, $attribute, $value);
                        $updated = true;
                    }
                }
            }

            if ($updated) {
                $updatedCount++;
            }
        }

        return $updatedCount;
    }

    private function getFieldMetadata(string $field): ?array
    {
        $base = [
            'name' => $field,
            'label' => self::FIELD_LABELS[$field] ?? $this->generateLabel($field),
            'nullable' => true,
        ];

        if (array_key_exists($field, self::RELATIONSHIP_FIELDS)) {
            $config = self::RELATIONSHIP_FIELDS[$field];
            return array_merge($base, [
                'type' => 'select',
                'label' => $config['label'],
                'options' => $this->getRelationshipOptions($config['model'], $config['label_column']),
            ]);
        }

        if (array_key_exists($field, self::SELECT_FIELDS)) {
            $config = self::SELECT_FIELDS[$field];
            return array_merge($base, [
                'type' => 'select',
                'label' => $config['label'],
                'options' => $config['options'],
            ]);
        }

        if (in_array($field, self::BOOLEAN_FIELDS)) {
            return array_merge($base, [
                'type' => 'boolean',
                'nullable' => false,
            ]);
        }

        if (in_array($field, self::INTEGER_FIELDS)) {
            return array_merge($base, [
                'type' => 'integer',
            ]);
        }

        if (in_array($field, self::DECIMAL_FIELDS)) {
            return array_merge($base, [
                'type' => 'decimal',
            ]);
        }

        if (in_array($field, self::DATE_FIELDS)) {
            return array_merge($base, [
                'type' => 'date',
            ]);
        }

        return array_merge($base, [
            'type' => 'string',
        ]);
    }

    private function getAttributeFieldMetadata(VehicleAttribute $attribute): array
    {
        $type = match ($attribute->type) {
            'text', 'textarea', 'editor' => 'string',
            'number' => 'decimal',
            'integer' => 'integer',
            'boolean' => 'boolean',
            'datetime' => 'date',
            'select' => 'select',
            default => 'string',
        };

        $field = [
            'name' => "attribute::{$attribute->id}",
            'label' => $attribute->name,
            'type' => $type,
            'nullable' => true,
        ];

        if ($type === 'select' && !empty($attribute->data)) {
            $field['options'] = collect($attribute->data)->map(fn ($option) => [
                'value' => $option,
                'label' => $option,
            ])->toArray();
        }

        if ($type === 'boolean') {
            $field['nullable'] = false;
        }

        return $field;
    }

    private function getRelationshipOptions(string $modelClass, string $labelColumn): array
    {
        return $modelClass::query()
            ->select(['id', $labelColumn])
            ->orderBy($labelColumn)
            ->get()
            ->map(fn ($item) => [
                'value' => $item->id,
                'label' => $item->{$labelColumn},
            ])
            ->toArray();
    }

    private function generateLabel(string $field): string
    {
        return ucwords(str_replace('_', ' ', $field));
    }

    private function castValue(string $field, mixed $value): mixed
    {
        if ($value === null || $value === '') {
            return null;
        }

        if (in_array($field, self::BOOLEAN_FIELDS)) {
            return filter_var($value, FILTER_VALIDATE_BOOLEAN);
        }

        if (in_array($field, self::INTEGER_FIELDS)) {
            return (int) $value;
        }

        if (in_array($field, self::DECIMAL_FIELDS)) {
            return (float) $value;
        }

        return $value;
    }

    private function isAttributeField(string $field): bool
    {
        return str_starts_with($field, 'attribute::');
    }

    private function getAttributeId(string $field): int
    {
        return (int) str_replace('attribute::', '', $field);
    }

    private function updateAttributeValue(VehicleOffer $offer, VehicleAttribute $attribute, mixed $value): void
    {
        $column = $this->attributeValueColumn($attribute->toArray());

        if ($value === null || $value === '') {
            $offer->attributeValues()
                ->where('attribute_id', $attribute->id)
                ->delete();
            return;
        }

        VehicleAttributeValue::query()->updateOrCreate(
            [
                'attribute_id' => $attribute->id,
                'owner_id' => $offer->id,
                'owner_type' => $offer->getMorphClass(),
                'index' => 0,
            ],
            [
                'type' => $attribute->type,
                'slug' => $attribute->slug,
                $column => $value,
            ]
        );
    }
}
