<?php

namespace App\VehicleSpec\Services;

use App\VehicleSpec\Config\HPIConfig;
use App\VehicleSpec\Contracts\VehicleSpecData;
use App\VehicleSpec\Contracts\VehicleSpecProvider;
use Mtc\VehicleLookup\Contracts\VehicleStandardEquipmentItem;
use Mtc\VehicleLookup\Contracts\VehicleTechnicalDataItem;
use App\VehicleSpec\Maps\HPITechnicalFieldMap;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Mtc\MercuryDataModels\ApiNotification;
use Mtc\MercuryDataModels\Vehicle;
use Throwable;

class HPISpecProvider implements VehicleSpecProvider
{
    protected static $endpoint_live = 'https://wss.hpi.co.uk/TradeSoap/services/SupplementaryEnquiryV1/';
    protected static $endpoint_test = 'http://pat-wss.hpi.co.uk/TradeSoap/services/SupplementaryEnquiryV1/';

    public function __construct(protected readonly HPIConfig $config)
    {
        //
    }

    public function getSpec(Vehicle $vehicle): VehicleSpecData
    {
        $spec = new VehicleSpecData();
        $spec->technical_data = collect();
        $spec->standard_equipment = collect();

        $request = $this->getVehicleRequest($vehicle);
        $responseBody = null;
        $httpStatus = null;
        $errorMessage = null;

        try {
            $response = $this->call($request);
            $httpStatus = $response->status();
            $responseBody = (string) $response->body();

            if ($response->failed()) {
                $errorMessage = "HPI returned HTTP {$httpStatus}";
                Log::error('HPI call failed', [
                    'endpoint' => $this->endpoint(),
                    'request_data' => $request,
                    'status_code' => $httpStatus,
                    'response' => $responseBody,
                ]);
            } else {
                $xml = @simplexml_load_string($responseBody);
                if ($xml === false) {
                    $errorMessage = 'Malformed XML in HPI response';
                } else {
                    $faultMessage = $this->extractSoapFault($xml);
                    if ($faultMessage !== null) {
                        $errorMessage = $faultMessage;
                    } else {
                        $results = $xml
                            ->children('http://schemas.xmlsoap.org/soap/envelope/')
                            ->Body
                            ->children('http://webservices.hpi.co.uk/SupplementaryEnquiryV1')
                            ->EnquiryResponse
                            ->RequestResults;

                        $spec->technical_data = $this->getTechnicalDataFromXml($results);
                        $spec->standard_equipment = $this->getStandardEquipmentFromXml($results);

                        // Only cache valid responses; the vehicle-lookup HPI driver reads this key
                        Cache::remember(
                            'HPI_stored_response_' . tenant()->id . '_' . $vehicle->vrm_condensed,
                            10,
                            static fn () => $response->body()
                        );
                    }
                }
            }
        } catch (Throwable $exception) {
            $errorMessage = $exception->getMessage();
            Log::error(self::class . '::' . __FUNCTION__ . '() ' . $exception->getMessage());
        }

        $this->logApiNotification($vehicle, $request, $responseBody, $httpStatus, $errorMessage, $spec);

        return $spec;
    }

    private function extractSoapFault(\SimpleXMLElement $xml): ?string
    {
        $soapNs = 'http://schemas.xmlsoap.org/soap/envelope/';
        $hpiNs = 'http://webservices.hpi.co.uk/SupplementaryEnquiryV1';

        $body = $xml->children($soapNs)->Body;
        if ($body->count() === 0) {
            return null;
        }

        $fault = $body->children($soapNs)->Fault;
        if ($fault->count() === 0) {
            return null;
        }

        $hpiError = $fault->children()->detail->children($hpiNs)->HpiSoapFault->Error ?? null;
        if ($hpiError !== null && $hpiError->count() > 0) {
            $code = (string) $hpiError->Code;
            $description = (string) $hpiError->Description;
            if ($description !== '') {
                return $code !== ''
                    ? "SOAP Fault {$code}: {$description}"
                    : "SOAP Fault: {$description}";
            }
        }

        $faultString = (string) $fault->children()->faultstring;
        return $faultString !== ''
            ? "SOAP Fault: {$faultString}"
            : 'SOAP Fault';
    }

    private function logApiNotification(
        Vehicle $vehicle,
        array $request,
        ?string $responseBody,
        ?int $httpStatus,
        ?string $errorMessage,
        VehicleSpecData $spec,
    ): void {
        // Redact password: api_notifications is admin-readable
        $request['subscriber_details']['password'] = '[ELIDED]';

        ApiNotification::query()->create([
            'provider' => 'hpi',
            'reference' => $vehicle->vrm_condensed,
            // HPI returns SOAP Faults inside HTTP 200 responses; HTTP success alone
            // does not indicate a usable result, so processed reflects data presence.
            'processed' => $spec->standard_equipment->isNotEmpty()
                || $spec->technical_data->isNotEmpty(),
            'data' => [
                'request' => $request,
                'result' => $responseBody,
                'http_status' => $httpStatus,
                'error' => $errorMessage,
                'equipment_count' => $spec->standard_equipment->count(),
                'technical_data_count' => $spec->technical_data->count(),
                'endpoint' => $this->endpoint(),
            ],
            'data_model' => Vehicle::class,
        ]);
    }

    protected function call(array $request_data)
    {
        return Http::withHeaders($this->getHeaders())
            ->timeout(20)
            ->withBody($this->getEnvelope($request_data), 'text/xml')
            ->post($this->endpoint());
    }

    protected function getEnvelope(array $request_data)
    {
        return view('hpi_enquiry_request', ['data' => $request_data])->render();
    }

    protected function endpoint()
    {
        return app()->environment() === 'production'
            ? self::$endpoint_live
            : self::$endpoint_test;
    }

    protected function getVehicleRequest(Vehicle $vehicle): array
    {
        return [
            'subscriber_details' => [
                'customer_code' => $this->config->customerCode(),
                'initials' => $this->config->initials(),
                'password' => $this->config->password(),
            ],
            'asset' => [
                'vrm' => $vehicle->vrm_condensed,
                'vin' => $vehicle->vin,
                'mileage' => $vehicle->odometer_mi,
                'reference' => '',
            ],
            'primary_product' => $this->config->primaryProductCode(),
            'supplementary_product' => $this->config->supplementaryProductCode(),
        ];
    }

    protected function getHeaders(): array
    {
        return [
            'Content-Type' => 'text/xml; charset=utf-8',
            'SOAPAction' => 'http://webservices.hpi.co.uk/SupplementaryEnquiryV1/enquire',
        ];
    }

    protected function getTechnicalDataFromXml(\SimpleXMLElement $element): Collection
    {
        return collect(HPITechnicalFieldMap::all())
            ->filter(fn ($field) => !empty(data_get($element, $field['value'])))
            ->map(fn ($field) => new VehicleTechnicalDataItem(
                data_get($element, $field['code'] ?? null),
                $field['category'],
                $field['description'],
                data_get($element, $field['value']),
            ));
    }

    protected function getStandardEquipmentFromXml(\SimpleXMLElement $element): Collection
    {
        $details = data_get(
            json_decode(json_encode($element), true),
            'Asset.SupplementaryAssetData.OptionListResponse.OptionList.ModelOptionDetail',
            []
        );

        return collect($details)
            ->map(fn ($item) => new VehicleStandardEquipmentItem(
                data_get($item, 'Code') ?: '',
                data_get($item, 'ShortDescription'),
                data_get($item, 'ShortDescription'),
                data_get($item, 'OptionKind')
            ));
    }
}
