<?php

namespace App\Services;

use App\DealershipRepository;
use App\Events\LoadDealershipDataFields;
use App\Facades\Country;
use App\Facades\Settings;
use App\Traits\EnsuresVehicleAttribute;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Mtc\ContentManager\Facades\Media;
use Mtc\ContentManager\Models\Template;
use Mtc\Crm\Models\Form;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\Franchise;
use Mtc\MercuryDataModels\VehicleAttribute;
use Mtc\MercuryDataModels\VehicleAttributeValue;

class DealershipBulkUpdateService
{
    use EnsuresVehicleAttribute;

    /**
     * Final-step actions. When dealership 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 MEDIA_FIELD = 'media';
    private const DATA_PREFIX = 'data::';
    private const ATTRIBUTE_PREFIX = 'attribute::';

    private const EXCLUDED_FIELDS = [
        'data',
        'coordinates',
        'lat',
        'lng',
        'open_times',
        'alt_open_times',
        'second_alt_open_times',
        'slug',
        'order',
    ];

    private const BOOLEAN_FIELDS = [
        'active',
    ];

    private const RELATIONSHIP_FIELDS = [
        'franchise_id' => [
            'label' => 'Franchise',
            'model' => Franchise::class,
            'label_column' => 'name',
        ],
        'contact_form_id' => [
            'label' => 'Contact Form',
            'model' => Form::class,
            'label_column' => 'name',
        ],
        'template_id' => [
            'label' => 'Template',
            'model' => Template::class,
            'label_column' => 'name',
        ],
    ];

    private const FIELD_LABELS = [
        'name' => 'Name',
        'email' => 'Email',
        'active' => 'Active',
        'contact_no' => 'Contact Number',
        'address1' => 'Address Line 1',
        'address2' => 'Address Line 2',
        'city' => 'City',
        'county' => 'County',
        'postcode' => 'Postcode',
        'country' => 'Country',
        'timezone' => 'Timezone',
        'location_stock' => 'Stock Location',
        'location_finance' => 'Finance Location',
        'external_showroom_url' => 'External Showroom URL',
        'notes' => 'Notes',
    ];

    public function getFields(): array
    {
        $fields = [];

        foreach ((new Dealership())->getFillable() as $field) {
            if (in_array($field, self::EXCLUDED_FIELDS, true)) {
                continue;
            }

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

        $fields[] = [
            'name' => self::MEDIA_FIELD,
            'label' => 'Image',
            'type' => 'image',
            'nullable' => true,
            'maxAllowed' => 1,
        ];

        foreach ($this->getDataFields() as $field) {
            $fields[] = $field;
        }

        $attributes = VehicleAttribute::query()->where('model', 'dealership')->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 getDealershipData(array $dealershipIds): array
    {
        $fields = $this->getFields();
        $modelFields = collect($fields)
            ->pluck('name')
            ->filter(fn ($name) => $this->isPlainModelField($name))
            ->toArray();

        return Dealership::query()
            ->whereIn('id', $dealershipIds)
            ->with(['attributeValues', 'mediaUses'])
            ->get()
            ->mapWithKeys(function (Dealership $dealership) use ($fields, $modelFields) {
                $data = collect($dealership->toArray())
                    ->only($modelFields)
                    ->toArray();

                foreach ($fields as $field) {
                    $name = $field['name'];

                    if ($name === self::MEDIA_FIELD) {
                        $data[$name] = $dealership->mediaUses->pluck('media_id')->values()->toArray();
                    } elseif (str_starts_with($name, self::DATA_PREFIX)) {
                        $slug = substr($name, strlen(self::DATA_PREFIX));
                        $data[$name] = $dealership->data[$slug] ?? null;
                    } elseif (str_starts_with($name, self::ATTRIBUTE_PREFIX)) {
                        $attributeId = (int) substr($name, strlen(self::ATTRIBUTE_PREFIX));
                        $data[$name] = $dealership->attributeValues
                            ->where('attribute_id', $attributeId)
                            ->first()?->getValue();
                    }
                }

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

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

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

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

    /**
     * Whether dealership 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(DealershipRepository::class)->hasVersioning();
    }

    /**
     * Retain a version snapshot for a dealership after its bulk field changes
     * have been applied (applyField saves per field). No-op when versioning
     * is disabled for the tenant.
     */
    private function snapshotDealership(Dealership $dealership, string $versionAction): void
    {
        if (!$this->versioningEnabled()) {
            return;
        }

        app(DealershipRepository::class)->saveBulkVersion(
            $dealership,
            (int) Auth::id(),
            $versionAction === self::VERSION_ACTION_PUBLISH
        );
    }

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

        $dealerships = Dealership::query()->whereIn('id', $dealershipIds)->get();

        foreach ($dealerships as $dealership) {
            $this->applyField($dealership, $field, $value);
            $this->snapshotDealership($dealership, $versionAction);
        }

        return $dealerships->count();
    }

    private function updateDifferentValues(
        array $dealershipIds,
        array $updates,
        array $allowedFields,
        string $versionAction = self::VERSION_ACTION_PUBLISH
    ): int {
        $updatedCount = 0;

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

            $dealership = Dealership::query()->find($dealershipId);
            if (!$dealership) {
                continue;
            }

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

                $this->applyField($dealership, $field, $value);
                $applied = true;
            }

            if ($applied) {
                $this->snapshotDealership($dealership, $versionAction);
                $updatedCount++;
            }
        }

        return $updatedCount;
    }

    private function applyField(Dealership $dealership, string $field, mixed $value): void
    {
        if ($field === self::MEDIA_FIELD) {
            Media::setUsesForModel($this->extractMediaIds($value), $dealership, ['primary' => true], true);
            return;
        }

        if (str_starts_with($field, self::DATA_PREFIX)) {
            $this->applyDataField($dealership, substr($field, strlen(self::DATA_PREFIX)), $value);
            return;
        }

        if (str_starts_with($field, self::ATTRIBUTE_PREFIX)) {
            $this->applyAttribute($dealership, (int) substr($field, strlen(self::ATTRIBUTE_PREFIX)), $value);
            return;
        }

        if ($field === 'franchise_id' && Settings::get('dealerships-multiple-franchises')) {
            $dealership->franchises()->sync(array_filter([$value]));
            return;
        }

        $dealership->{$field} = $this->castValue($field, $value);
        $dealership->save();
    }

    private function applyDataField(Dealership $dealership, string $slug, mixed $value): void
    {
        $data = $dealership->data ?? [];
        $data[$slug] = $value;
        $dealership->data = $data;
        $dealership->save();
    }

    private function applyAttribute(Dealership $dealership, int $attributeId, mixed $value): void
    {
        $attribute = VehicleAttribute::query()->find($attributeId);
        if (!$attribute) {
            return;
        }

        if ($attribute->type === 'image') {
            $value = $this->extractMediaIds($value)[0] ?? null;
        }

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

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

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

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

        return $value;
    }

    private function extractMediaIds(mixed $value): array
    {
        return collect(is_array($value) ? $value : [$value])
            ->map(fn ($item) => is_array($item) ? ($item['id'] ?? null) : $item)
            ->filter(fn ($id) => is_numeric($id))
            ->map(fn ($id) => (int) $id)
            ->values()
            ->toArray();
    }

    private function getDataFields(): array
    {
        return collect(event(new LoadDealershipDataFields()))
            ->filter()
            ->mapWithKeys(fn ($field) => $field)
            ->map(fn ($field, $slug) => [
                'name' => self::DATA_PREFIX . $slug,
                'label' => $field['label'] ?? $this->generateLabel($slug),
                'type' => ($field['type'] ?? 'text') === 'checkbox' ? 'boolean' : 'string',
                'nullable' => ($field['type'] ?? 'text') !== 'checkbox',
            ])
            ->values()
            ->toArray();
    }

    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 ($field === 'country') {
            return array_merge($base, [
                'type' => 'select',
                'options' => Country::getAll()
                    ->map(fn ($country) => [
                        'value' => $country->code,
                        'label' => $country->name,
                    ])
                    ->toArray(),
            ]);
        }

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

        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',
            'image' => 'image',
            default => 'string',
        };

        $field = [
            'name' => self::ATTRIBUTE_PREFIX . $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 isPlainModelField(string $name): bool
    {
        return $name !== self::MEDIA_FIELD
            && !str_starts_with($name, self::DATA_PREFIX)
            && !str_starts_with($name, self::ATTRIBUTE_PREFIX);
    }

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