<?php

namespace App;

use App\Facades\Settings;
use App\Http\Resources\VehicleList;
use App\Http\Resources\VehiclesCompareResource;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Mtc\MercuryDataModels\BodyStyleType;
use Mtc\MercuryDataModels\FuelType;
use Mtc\MercuryDataModels\Services\LocatingService;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleMake;

class VehicleRepository
{
    public function topMakes(?int $limit, bool $split_new = false): Collection
    {
        $vehicles = Vehicle::query()
            ->active()
            ->when($split_new, fn($query) => $query->used())
            ->groupBy('make_id')
            ->whereNotNull('make_id')
            ->addSelect([
                'make_id',
                DB::raw('MIN(price) as cheapest_price'),
                DB::raw('count(id) as vehicle_count')
            ])
            ->orderByDesc('vehicle_count')
            ->take($limit ?? 8)
            ->get()
            ->keyBy('make_id');

        if ($split_new) {
            $new_vehicles = Vehicle::query()
                ->active()
                ->new()
                ->groupBy('make_id')
                ->whereNotNull('make_id')
                ->addSelect([
                    'make_id',
                    DB::raw('MIN(price) as cheapest_price'),
                    DB::raw('count(id) as vehicle_count')
                ])
                ->orderByDesc('vehicle_count')
                ->take($limit ?? 8)
                ->get()
                ->keyBy('make_id');
        } else {
            $new_vehicles = [];
        }

        return VehicleMake::query()
            ->whereIn('id', $vehicles->pluck('make_id'))
            ->get()
            ->map(fn(VehicleMake $make) => $this->mapTopTaxonomyDetails($make, $vehicles, $split_new, $new_vehicles))
            ->sortByDesc(fn($item) => $item['count'])
            ->values();
    }

    public function topBodyTypes(?int $limit, bool $split_new = false): Collection
    {
        $vehicles = Vehicle::query()
            ->active()
            ->when($split_new, fn($query) => $query->used())
            ->groupBy('body_style_id')
            ->whereNotNull('body_style_id')
            ->addSelect([
                'body_style_id',
                DB::raw('MIN(price) as cheapest_price'),
                DB::raw('count(id) as vehicle_count')
            ])
            ->orderByDesc('vehicle_count')
            ->take($limit ?? 8)
            ->get()
            ->keyBy('body_style_id');

        if ($split_new) {
            $new_vehicles = Vehicle::query()
                ->active()
                ->new()
                ->groupBy('body_style_id')
                ->whereNotNull('body_style_id')
                ->addSelect([
                    'body_style_id',
                    DB::raw('MIN(price) as cheapest_price'),
                    DB::raw('count(id) as vehicle_count')
                ])
                ->orderByDesc('vehicle_count')
                ->take($limit ?? 8)
                ->get()
                ->keyBy('body_style_id');
        } else {
            $new_vehicles = [];
        }

        return BodyStyleType::query()
            ->whereIn('id', $vehicles->pluck('body_style_id'))
            ->get()
            ->map(fn(BodyStyleType $type) => $this->mapTopTaxonomyDetails($type, $vehicles, $split_new, $new_vehicles))
            ->sortByDesc(fn($item) => $item['count'])
            ->values();
    }

    public function topFuelTypes(?int $limit, bool $split_new = false): Collection
    {
        $vehicles = Vehicle::query()
            ->active()
            ->when($split_new, fn($query) => $query->used())
            ->groupBy('fuel_type_id')
            ->whereNotNull('fuel_type_id')
            ->addSelect([
                'fuel_type_id',
                DB::raw('MIN(price) as cheapest_price'),
                DB::raw('count(id) as vehicle_count')
            ])
            ->orderByDesc('vehicle_count')
            ->take($limit ?? 8)
            ->get()
            ->keyBy('fuel_type_id');

        if ($split_new) {
            $new_vehicles = Vehicle::query()
                ->active()
                ->new()
                ->groupBy('fuel_type_id')
                ->whereNotNull('fuel_type_id')
                ->addSelect([
                    'fuel_type_id',
                    DB::raw('MIN(price) as cheapest_price'),
                    DB::raw('count(id) as vehicle_count')
                ])
                ->orderByDesc('vehicle_count')
                ->take($limit ?? 8)
                ->get()
                ->keyBy('fuel_type_id');
        } else {
            $new_vehicles = [];
        }
        return FuelType::query()
            ->whereIn('id', $vehicles->pluck('fuel_type_id'))
            ->get()
            ->map(fn(FuelType $type) => $this->mapTopTaxonomyDetails($type, $vehicles, $split_new, $new_vehicles))
            ->sortByDesc(fn($item) => $item['count'])
            ->values();
    }

    public function similar(Vehicle $vehicle, int $limit = 4, ?string $matchType = 'price'): VehicleList
    {
        return new VehicleList(
            Vehicle::query()
                ->active()
                ->withRelationshipsForCardView()
                ->similar($matchType ?? 'price', $vehicle)
                ->where('id', '!=', $vehicle->id)
                ->take($limit * 2)
                ->get()
                ->shuffle()
                ->take($limit)
        );
    }

    public function featured(?Vehicle $vehicle, int $limit = 4, ?array $filters = []): VehicleList
    {
        $query = Vehicle::query()
            ->where('featured', 1)
            ->active()
            ->withRelationshipsForCardView()
            ->withImageCount()
            ->when($vehicle, fn(Builder $query) => $query->where('id', 'not like', $vehicle?->id))
            ->inRandomOrder()
            ->take($limit);

        $this->applyFilters($query, $filters);

        return new VehicleList($query->get());
    }

    public function recentlyViewed(?Vehicle $vehicle, array $vehicleSlugs = [], int $limit = 4): VehicleList
    {
        return new VehicleList(
            Vehicle::query()
                ->whereIn('slug', $vehicleSlugs)
                ->active()
                ->withRelationshipsForCardView()
                ->withImageCount()
                ->when($vehicle, fn(Builder $query) => $query->where('id', 'not like', $vehicle?->id))
                ->inRandomOrder()
                ->take($limit)
                ->get()
        );
    }

    public function compare(array $vehicleSlugs = []): VehiclesCompareResource
    {
        return new VehiclesCompareResource(
            Vehicle::query()
                ->whereIn('slug', $vehicleSlugs)
                ->active()
                ->with(['features', 'specs'])
                ->withRelationshipsForCardView()
                ->get()
        );
    }

    public function recentlyAdded(int $limit = 4, array $filters = []): VehicleList
    {
        $query = Vehicle::query()
            ->active()
            ->withRelationshipsForCardView()
            ->withImageCount()
            ->latest()
            ->take($limit);

        $this->applyFilters($query, $filters);

        return new VehicleList($query->get());
    }

    private function applyFilters(&$query, $filters): void
    {
        $query
            ->when(
                !empty($filters['min_image_count']),
                fn(Builder $query) => $query->having('image_count', '>=', (int)$filters['min_image_count'])
            )
            ->when(
                $filters['make'] ?? null,
                fn(Builder $query) => $query->whereHas(
                    'make',
                    fn(Builder $indexQuery) => is_array($filters['make'])
                        ? $indexQuery->whereIn('slug', $filters['make'])
                        : $indexQuery->where('slug', $filters['make'])
                )
            )
            ->when(
                $filters['model'] ?? null,
                fn(Builder $query) => $query->whereHas(
                    'model',
                    fn(Builder $indexQuery) => $indexQuery->where('slug', $filters['model'])
                )
            )
            ->when(
                $filters['fuel_type'] ?? null,
                fn(Builder $query) => $query->whereHas(
                    'fuelType',
                    fn(Builder $indexQuery) => is_array($filters['fuel_type'])
                        ? $indexQuery->whereIn('slug', $filters['fuel_type'])
                        : $indexQuery->where('slug', $filters['fuel_type'])
                )
            )
            ->when(
                $filters['body_style'] ?? null,
                fn(Builder $query) => $query->whereHas(
                    'bodyStyle',
                    fn(Builder $indexQuery) => is_array($filters['body_style'])
                        ? $indexQuery->whereIn('slug', $filters['body_style'])
                        : $indexQuery->where('slug', $filters['body_style'])
                )
            )
            ->when(
                $filters['franchise'] ?? null,
                fn(Builder $query) => $query->whereHas(
                    'dealership.franchise',
                    fn(Builder $indexQuery) => $indexQuery->where('slug', $filters['franchise'])
                )
            )
            ->when(
                $filters['location'] ?? null,
                fn(Builder $query) => $query->whereHas(
                    'dealership',
                    fn(Builder $indexQuery) => $indexQuery->where('slug', $filters['location'])
                )
            )
            ->when(
                $filters['type'] ?? null,
                fn(Builder $query) => $query->where(
                    'type',
                    $filters['type']
                )
            )
            ->when(
                isset($filters['is_new']),
                fn(Builder $query) => $query->where(
                    'is_new',
                    filter_var($filters['is_new'], FILTER_VALIDATE_BOOLEAN)
                )
            )
            ->when(
                isset($filters['is_reserved']),
                fn(Builder $query) => $query->where(
                    'is_reserved',
                    filter_var($filters['is_reserved'], FILTER_VALIDATE_BOOLEAN)
                )
            )
            ->when(
                isset($filters['is_sold']),
                fn(Builder $query) => $query->where(
                    'is_sold',
                    filter_var($filters['is_sold'], FILTER_VALIDATE_BOOLEAN)
                )
            )
            ->when(
                isset($filters['is_demo']),
                fn(Builder $query) => $query->where(
                    'is_demo',
                    filter_var($filters['is_demo'], FILTER_VALIDATE_BOOLEAN)
                )
            )
            ->when(
                !empty($filters['min_price']),
                fn(Builder $query) => $query->where('price', '>=', (int)$filters['min_price'])
            )
            ->when(
                !empty($filters['max_price']),
                fn(Builder $query) => $query->where('price', '<=', (int)$filters['max_price'])
            )
            ->when(
                !empty($filters['min_monthly_price']),
                fn(Builder $query) => $query->where('monthly_price', '>=', (int)$filters['min_monthly_price'])
            )
            ->when(
                !empty($filters['max_monthly_price']),
                fn(Builder $query) => $query->where('monthly_price', '<=', (int)$filters['max_monthly_price'])
            )
            ->when(
                !empty($filters['postcode']),
                function (Builder $query) use ($filters) {
                    try {
                        $distance = !empty($filters['radius']) ? (int)$filters['radius'] : 200;
                        $locationService = app(LocatingService::class);
                        $location = $locationService->locate($filters['postcode']);

                        if (!empty($location)) {
                            $query->isWithinDistance($location->lat(), $location->lng(), $distance);
                        }
                    } catch (\Exception $exception) {
                        Log::debug("Error locating postcode {$filters['postcode']}: " . $exception->getMessage());
                    }
                }
            );

        foreach ($filters['vehicle_attributes'] ?? [] as $slug => $value) {
            if (strtolower($value) === 'yes') {
                $query->whereHas('attributeValues', function (Builder $attrQuery) use ($slug) {
                    $attrQuery->where('slug', $slug)
                        ->where('value_integer', 1);
                });
            } else {
                $query->whereDoesntHave('attributeValues', function (Builder $attrQuery) use ($slug) {
                    $attrQuery->where('slug', $slug)
                        ->where('value_integer', 1);
                });
            }
        }
    }

    /**
     * @param VehicleMake|BodyStyleType|FuelType $make
     * @param $vehicles
     * @param bool $split_new
     * @param $new_vehicles
     * @return array
     */
    public function mapTopTaxonomyDetails(Model $model, $vehicles, bool $split_new, $new_vehicles): array
    {
        $data = [
            'name' => $model->name,
            'count' => $vehicles[$model->id]?->vehicle_count,
            'from_price' => $vehicles[$model->id]?->cheapest_price,
            'slug' => $model->slug,
            'type' => $this->getTypeFromModel($model),
            'count_new' => null,
            'from_price_new' => null,
        ];
        if ($split_new) {
            $data['count_new'] = isset($new_vehicles[$model->id])
                ? $new_vehicles[$model->id]?->vehicle_count
                : 0;
            $data['from_price_new'] = isset($new_vehicles[$model->id])
                ? $new_vehicles[$model->id]?->cheapest_price
                : 0;
        }
        return $data;
    }

    /**
     * Optionally override the vehicle type of the model.
     * Example, we have body type 'SUV' as an LCV body type,
     * but the client wants this to be applied to cars.
     *
     * @param Model $model
     * @return string|null
     */
    private function getTypeFromModel(Model $model): ?string
    {
        if ($model::class == BodyStyleType::class) {
            $type = match (strtolower($model->name)) {
                'suv' => Settings::get('body-type-vehicle-type-override-suv'),
                'mpv' => Settings::get('body-type-vehicle-type-override-mpv'),
                default => $model->type,
            };

            if (!empty($type) && in_array($type, ['car', 'lcv'])) {
                return $type;
            }
        }

        return $model->type;
    }
}
