<?php

namespace App\Traits;

use App\Facades\Settings;
use App\ImportConditionRepository;
use App\Models\ImportCondition;
use App\Models\ImportConditionRule;
use Carbon\Carbon;
use Closure;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Mtc\MercuryDataModels\Contracts\HasMediaChecksums;
use Mtc\MercuryDataModels\Vehicle;

trait ImportChecksConditions
{
    protected Collection $conditions;

    public function shouldBePublished(
        Closure $integrationPublishLogic,
        string $provider,
        Vehicle $vehicle,
        ?int $importMap = null
    ): ?bool {
        $conditions = $this->getConditionsForProvider($provider, $importMap)
            ->filter(fn (ImportCondition $condition) => $condition->do_not_publish);
        if ($conditions->isEmpty()) {
            return $integrationPublishLogic();
        }

        $conditionsForbidPublish = $conditions
           ->filter(fn (ImportCondition $condition) => $this->conditionAppliesToVehicle($condition, $vehicle))
           ->isNotEmpty();

        return $conditionsForbidPublish
            ? false
            : $integrationPublishLogic();
    }

    public function skipImporting(
        string $provider,
        array $data,
        ?int $importMap = null
    ): ?bool {
        $conditions = $this->getConditionsForProvider($provider, $importMap)
            ->filter(fn (ImportCondition $condition) => $condition->do_not_import);
        if ($conditions->isEmpty()) {
            return false;
        }

        return $conditions
           ->filter(fn (ImportCondition $condition) => $this->conditionAppliesToVehicleData($condition, $data))
           ->isNotEmpty();
    }

    public function imageChecksumMismatch(array $images, HasMediaChecksums $model): bool
    {
        $current_checksum = $model[$model->imageChecksumAttribute()];
        array_unshift($images, count($images));
        return dechex(crc32(json_encode($images))) !== $current_checksum;
    }

    private function getConditionsForProvider(?string $provider, ?int $importMap): Collection
    {
        if (!isset($this->conditions)) {
            $cacheKey = sprintf(
                'import-conditions:%s:%s:%s',
                tenant('id') ?? 'none',
                $provider ?? 'none',
                $importMap ?? 'none'
            );

            // Cached briefly so a high-volume drain doesn't reload conditions for
            // every notification; a fresh instance within the TTL reuses the set.
            $this->conditions = Cache::remember(
                $cacheKey,
                now()->addMinute(),
                fn () => ImportCondition::query()
                    ->with('rules')
                    ->where('active', 1)
                    ->where(fn ($query) => $query->whereNull('provider')
                        ->orWhere(fn ($providerQuery) => $providerQuery
                            ->where('provider', $provider)
                            ->when($importMap, fn ($importQuery) => $importQuery->where('import_map_id', $importMap))))
                    ->get()
            );
        }
        return $this->conditions;
    }

    public function conditionMatchesVehicle(ImportCondition $condition, Vehicle $vehicle): bool
    {
        return $this->conditionAppliesToVehicle($condition, $vehicle);
    }

    private function conditionAppliesToVehicle(ImportCondition $condition, Vehicle $vehicle): bool
    {
        if ($condition->rules->isEmpty()) {
            return true;
        }

        return $condition->rules
            ->reject(fn (ImportConditionRule $rule) => $this->ruleApplies($rule, $vehicle))
            ->isEmpty();
    }
    private function conditionAppliesToVehicleData(ImportCondition $condition, array $data): bool
    {
        if ($condition->rules->isEmpty()) {
            return true;
        }

        // Make sure that vrm_condensed is available for checks
        if (!empty($data['registration_number']) && empty($data['vrm_condensed'])) {
            $data['vrm_condensed'] = $data['registration_number'];
        }

        return $condition->rules
            ->reject(fn (ImportConditionRule $rule) => $this->ruleAppliesToData($rule, $data))
            ->isEmpty();
    }

    private function ruleApplies(ImportConditionRule $rule, Vehicle $vehicle): bool
    {
        $fieldValue = $this->getFieldValue($rule->field, $vehicle);

        return match ($rule->condition) {
            'exists' => Vehicle::query()->where($rule->field, $fieldValue)->exists(),
            '=' => $rule->value == $fieldValue,
            '!=' => $rule->value != $fieldValue,
            '>' => $fieldValue > $rule->value,
            '>=' => $fieldValue >= $rule->value,
            '<' => $fieldValue < $rule->value,
            '<=' => $fieldValue <= $rule->value,
            'is_null' => empty($fieldValue),
            'in' => in_array($fieldValue, $this->parseMultipleValues($rule->value)),
            'not_in' => !in_array($fieldValue, $this->parseMultipleValues($rule->value)),
            default => throw new \Exception('Unknown condition check: ' . $rule->condition . json_encode([
                'tenant' => tenant('id'),
                'rule' => $rule,
            ])),
        };
    }

    /**
     * Get field value from vehicle, including support for calculated/virtual fields.
     */
    private function getFieldValue(string $field, Vehicle $vehicle): mixed
    {
        return match ($field) {
            // Age in years calculated from first_registration_date
            'age' => $this->calculateAgeInYears($vehicle->first_registration_date),
            // Age in months calculated from first_registration_date
            'age_months' => $this->calculateAgeInMonths($vehicle->first_registration_date),
            // Odometer value based on distance_measurement setting (mi or km)
            'odometer' => $this->getOdometerValue($vehicle),
            default => ImportConditionRepository::isAttributeField($field)
                ? $this->getVehicleAttributeValue($vehicle, $field)
                : $vehicle->getAttribute($field),
        };
    }

    /**
     * Resolve a custom vehicle attribute value (stored in vehicle_attribute_values)
     * for an "attribute.{slug}" condition field.
     */
    private function getVehicleAttributeValue(Vehicle $vehicle, string $field): mixed
    {
        $slug = substr($field, strlen(ImportConditionRepository::ATTRIBUTE_FIELD_PREFIX));

        return $vehicle->attributeValues
            ->firstWhere('slug', $slug)
            ?->getValue();
    }

    /**
     * Calculate age in years from a date.
     */
    private function calculateAgeInYears(?Carbon $date): ?int
    {
        if (!$date) {
            return null;
        }

        return $date->diffInYears(Carbon::now(), true);
    }

    /**
     * Calculate age in months from a date.
     */
    private function calculateAgeInMonths(?Carbon $date): ?int
    {
        if (!$date) {
            return null;
        }

        return $date->diffInMonths(Carbon::now(), true);
    }

    /**
     * Get odometer value based on the tenant's distance_measurement setting.
     */
    private function getOdometerValue(Vehicle $vehicle): ?int
    {
        $unit = Settings::get('automotive-distance_measurement', 'mi');

        return $unit === 'mi' ? $vehicle->odometer_mi : $vehicle->odometer_km;
    }

    private function ruleAppliesToData(ImportConditionRule $rule, array $data): bool
    {
        $fieldValue = $this->getFieldValueFromData($rule->field, $data);

        if ($fieldValue === null && !in_array($rule->field, ['age', 'age_months', 'odometer'])) {
            return $rule->condition === 'is_null';
        }

        return match ($rule->condition) {
            'exists' => Vehicle::query()->where('stock_provider', '!=', $this->getProviderName())
                ->where($rule->field, $fieldValue)
                ->exists(),
            '=' => $rule->value == $fieldValue,
            '!=' => $rule->value != $fieldValue,
            '>' => $fieldValue > $rule->value,
            '>=' => $fieldValue >= $rule->value,
            '<' => $fieldValue < $rule->value,
            '<=' => $fieldValue <= $rule->value,
            'is_null' => empty($fieldValue),
            'in' => in_array($fieldValue, $this->parseMultipleValues($rule->value)),
            'not_in' => !in_array($fieldValue, $this->parseMultipleValues($rule->value)),
            default => throw new \Exception('Unknown condition check: ' . $rule->condition . json_encode([
                'tenant' => tenant('id'),
                'rule' => $rule,
            ])),
        };
    }

    /**
     * Get field value from raw data array, including support for calculated/virtual fields.
     */
    private function getFieldValueFromData(string $field, array $data): mixed
    {
        return match ($field) {
            // Age in years calculated from first_registration_date
            'age' => $this->calculateAgeFromData($data, 'years'),
            // Age in months calculated from first_registration_date
            'age_months' => $this->calculateAgeFromData($data, 'months'),
            // Odometer value based on distance_measurement setting (mi or km)
            'odometer' => $this->getOdometerValueFromData($data),
            default => $data[$field] ?? null,
        };
    }

    /**
     * Calculate age from raw data array.
     *
     * @param array $data
     * @param string $unit 'years' or 'months'
     */
    private function calculateAgeFromData(array $data, string $unit = 'years'): ?int
    {
        $dateValue = $data['first_registration_date'] ?? null;
        $yearValue = $data['manufacture_year'] ?? null;

        if (!$dateValue) {
            if ($yearValue) {
                return $unit === 'months'
                    ? Carbon::create($yearValue)->diffInMonths()
                    : Carbon::create($yearValue)->diffInYears();
            }
            return null;
        }

        try {
            $date = $dateValue instanceof Carbon ? $dateValue : Carbon::parse($dateValue);

            return $unit === 'months'
                ? $date->diffInMonths(Carbon::now())
                : $date->diffInYears(Carbon::now());
        } catch (\Exception) {
            return null;
        }
    }

    /**
     * Get odometer value from raw data based on the tenant's distance_measurement setting.
     */
    private function getOdometerValueFromData(array $data): ?int
    {
        $unit = Settings::get('automotive-distance_measurement', 'mi');

        return $unit === 'mi'
            ? ($data['odometer_mi'] ?? $data['odometer'] ?? null)
            : ($data['odometer_km'] ?? $data['odometer'] ?? null);
    }

    /**
     * Parse a JSON-encoded string of values into an array.
     */
    private function parseMultipleValues(mixed $value): array
    {
        if (is_array($value)) {
            return $value;
        }

        if (empty($value)) {
            return [];
        }

        $decoded = json_decode((string) $value, true);
        if (is_array($decoded)) {
            return $decoded;
        }

        // Free-text fields (e.g. attributes) accept comma-separated lists for in/not_in.
        return collect(explode(',', (string) $value))
            ->map(fn ($item) => trim($item))
            ->reject(fn ($item) => $item === '')
            ->values()
            ->all();
    }
}
