<?php

namespace App\Services;

use App\Traits\EnsuresVehicleAttribute;
use Illuminate\Support\Facades\DB;
use Mtc\MercuryDataModels\BodyStyleType;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\DrivetrainType;
use Mtc\MercuryDataModels\Form;
use Mtc\MercuryDataModels\Franchise;
use Mtc\MercuryDataModels\NewCar;
use Mtc\MercuryDataModels\Template;
use Mtc\MercuryDataModels\TransmissionType;
use Mtc\MercuryDataModels\VehicleAttribute;
use Mtc\MercuryDataModels\VehicleAttributeValue;
use Mtc\MercuryDataModels\VehicleMake;
use Mtc\MercuryDataModels\VehicleModel;

class NewCarBulkUpdateService
{
    use EnsuresVehicleAttribute;

    private const EXCLUDED_FIELDS = [
        'seo',
        'data',
        'order',
        'type_id',
    ];

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

    private const INTEGER_FIELDS = [
        'engine_size_cc',
    ];

    private const DECIMAL_FIELDS = [
        'price',
        'monthly_price',
        'deposit',
    ];

    private const DATE_FIELDS = [
        'published_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',
        ],
        'drivetrain_id' => [
            'label' => 'Drivetrain',
            'model' => DrivetrainType::class,
            'label_column' => 'name',
        ],
        'body_style_id' => [
            'label' => 'Body Style',
            'model' => BodyStyleType::class,
            'label_column' => 'name',
        ],
        'transmission_id' => [
            'label' => 'Transmission',
            'model' => TransmissionType::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'],
            ],
        ],
    ];

    private const FIELD_LABELS = [
        'published' => 'Published',
        'featured' => 'Featured',
        'name' => 'Name',
        'derivative' => 'Derivative',
        'vehicle_type' => 'Vehicle Type',
        'price' => 'Price',
        'monthly_price' => 'Monthly Price',
        'deposit' => 'Deposit',
        'published_at' => 'Published At',
        'engine_size_cc' => 'Engine Size (cc)',
        'brochure_url' => 'Brochure URL',
        'colour' => 'Colour',
        'excerpt' => 'Excerpt',
    ];

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

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

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

        // Add dynamic vehicle attribute fields
        $attributes = VehicleAttribute::query()->where('model', 'new-car')->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 getNewCarData(array $ids): array
    {
        $allowedFields = $this->getAllowedFields();
        $modelFields = array_filter($allowedFields, fn ($f) => !$this->isAttributeField($f));

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

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

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

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

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

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

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

        $newCars = NewCar::query()->whereIn('id', $ids)->get();

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

        return $deletedCount;
    }

    private function updateSameValue(array $ids, string $field, mixed $value, array $allowedFields): 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;
            }

            $newCars = NewCar::query()->whereIn('id', $ids)->get();
            foreach ($newCars as $newCar) {
                $this->updateAttributeValue($newCar, $attribute, $value);
            }

            return $newCars->count();
        }

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

        return NewCar::query()
            ->whereIn('id', $ids)
            ->update([$field => $value]);
    }

    private function updateDifferentValues(array $ids, array $updates, array $allowedFields): int
    {
        $updatedCount = 0;

        foreach ($updates as $newCarId => $fields) {
            if (!in_array((int) $newCarId, $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;

            if (!empty($modelFields)) {
                $result = NewCar::query()
                    ->where('id', $newCarId)
                    ->update($modelFields);
                if ($result) {
                    $updated = true;
                }
            }

            if (!empty($attributeFields)) {
                $newCar = NewCar::query()->find($newCarId);
                if ($newCar) {
                    foreach ($attributeFields as $field => $value) {
                        $attributeId = $this->getAttributeId($field);
                        $attribute = VehicleAttribute::query()->find($attributeId);
                        if ($attribute) {
                            $this->updateAttributeValue($newCar, $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(NewCar $newCar, VehicleAttribute $attribute, mixed $value): void
    {
        $column = $this->attributeValueColumn($attribute->toArray());

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

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