<?php

namespace App\VehicleSpec\Services;

use App\VehicleSpec\Config\AutoTraderConfig;
use App\Services\AutoTraderApi;
use App\VehicleSpec\Contracts\VehicleSpecData;
use App\VehicleSpec\Contracts\VehicleSpecProvider;
use App\VehicleSpec\Contracts\VehicleStandardEquipmentItem;
use App\VehicleSpec\Contracts\VehicleTechnicalDataItem;
use Exception;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Mtc\MercuryDataModels\Vehicle;

class AutoTraderSpecProvider implements VehicleSpecProvider
{
    public function __construct(
        protected readonly AutoTraderConfig $config,
        protected readonly AutoTraderApi $api,
    ) {
    }

    /**
     * Get specs from API
     *
     * @param Vehicle $vehicle
     * @return VehicleSpecData
     * @throws Exception
     */
    public function getSpec(Vehicle $vehicle): VehicleSpecData
    {
        $spec = new VehicleSpecData();

        try {
            if (empty($vehicle_data = $this->getVehicleData($vehicle))) {
                return $spec;
            }

            if (empty($derivative_id = $this->getVehicleDataAttribute('derivativeId', $vehicle_data))) {
                return $spec;
            }

            $spec->standard_equipment = $this->getStandardEquipment(
                $derivative_id,
                $this->getVehicleDataAttribute('firstRegistrationDate', $vehicle_data),
                $this->getAdvertiserId($vehicle)
            );

            $spec->technical_data = $this->getTechnicalData(
                $derivative_id,
                $this->getAdvertiserId($vehicle)
            );
        } catch (Exception $exception) {
            Log::error('Failed to retrieve AutoTrader specs for Vehicle with error: ' . $exception->getMessage(), [
                'vehicle' => $vehicle,
            ]);
        }

        return $spec;
    }

    /**
     * @param Vehicle $vehicle
     * @return array|mixed
     */
    protected function getVehicleData(Vehicle $vehicle): ?array
    {
        $url = 'vehicles?' . http_build_query([
                'registration' => $vehicle->registration_number,
                'advertiserId' => $this->getAdvertiserId($vehicle),
            ]);

        return $this->api->get($url);
    }

    /**
     * Get the advertiser ID from vehicle or fallback from config
     *
     * @param Vehicle $vehicle
     * @return string
     */
    private function getAdvertiserId(Vehicle $vehicle): string
    {
        if (!empty($vehicle->dealership->data['auto-trader-location-id'])) {
            return $vehicle->dealership->data['auto-trader-location-id'];
        }

        // global advertiser id
        return $this->config->advertiserId() ?? '';
    }

    /**
     * Find derivative ID from vehicle data
     *
     * @param string $attribute
     * @param array $vehicle_data
     * @return string|bool
     */
    private function getVehicleDataAttribute(string $attribute, array $vehicle_data = []): string|bool
    {
        if (
            empty($vehicle_data)
            || !is_array($vehicle_data)
            || !array_key_exists('vehicle', $vehicle_data)
            || !array_key_exists($attribute, $vehicle_data['vehicle'])
        ) {
            return false;
        }

        return (string)$vehicle_data['vehicle'][$attribute];
    }

    /**
     * Get standard equipment data
     *
     * @param string $derivative_id
     * @param string $effective_date
     * @param string $advertiser_id
     * @return Collection
     * @throws Exception
     */
    private function getStandardEquipment(
        string $derivative_id = '',
        string $effective_date = '',
        string $advertiser_id = ''
    ): Collection {
        $url = 'taxonomy/features?' . http_build_query([
                'derivativeId' => $derivative_id,
                'effectiveDate' => $effective_date,
                'advertiserId' => $advertiser_id
            ]);

        $data = $this->api->get($url);

        return isset($data['features'])
            ? $this->processStandardEquipmentResponse($data['features'])
            : collect([]);
    }

    /**
     * Get Technical data for vehicle
     *
     * @param string $derivative_id
     * @param string $advertiser_id
     * @return Collection
     */
    private function getTechnicalData(string $derivative_id = '', string $advertiser_id = ''): Collection
    {
        $url = 'taxonomy/derivatives/' . $derivative_id . '?' . http_build_query([
                'advertiserId' => $advertiser_id
            ]);

        $result = $this->api->get($url);
        return !empty($result)
            ? $this->processTechnicalDataResponse($result)
            : collect([]);
    }

    /**
     * Map standard equipment features
     *
     * @param array $features
     * @return Collection
     */
    protected function processStandardEquipmentResponse(array $features): Collection
    {
        return collect($features)->map(fn ($item) => new VehicleStandardEquipmentItem(
            '',
            $item['name'],
            $item['category'],
        ));
    }

    /**
     * Process technical data response
     *
     * @param array $tech_data
     * @return Collection
     */
    protected function processTechnicalDataResponse(array $tech_data): Collection
    {
        // TODO: check if we want to allow all tech data items or restrict by key.
        // TODO: add more keys
        $allowed_keys = [
            'drivetrain',
            'fuelDelivery',
            'valves',
            'enginePowerPS',
            'cylinders'
        ];

        return collect($tech_data)
            ->reject(fn ($value) => is_array($value))
            ->filter(fn ($value, $key) => in_array($key, $allowed_keys, false))
            ->map(fn ($value, $key) => new VehicleTechnicalDataItem('', '', $key, $value ?? ''));
    }
}
