<?php

namespace App\Services;

use App\Crm\Config\SalesforceCrmConfig;
use App\Facades\Settings;
use App\Facades\Site;
use Carbon\Carbon;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Mtc\Crm\Contracts\EnquiryActionModel;
use Mtc\Crm\Contracts\EnquiryModel;
use Mtc\Crm\Models\FormQuestion;
use Mtc\MercuryDataModels\ApiNotification;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleOffer;
use Mtc\VehicleReservations\Reservation;

class SalesforceCrmApi
{
    /**
     * Stores the base URL to use for enquiries
     *
     * @var string|null
     */
    protected ?string $base_url = null;

    /**
     * Stores the response message.
     *
     * @var string|null
     */
    protected string $responseMessage = '';

    /**
     * @param SalesforceCrmConfig $config
     */
    public function __construct(protected readonly SalesforceCrmConfig $config)
    {
        //
    }

    public function sendLead(EnquiryModel $enquiry, EnquiryActionModel $action): bool
    {
        return $this->submitLead($this->convertEnquiryToParams($enquiry));
    }

    public function sendReservation(Reservation $reservation): bool
    {
        return $this->submitLead($this->convertReservationToParams($reservation));
    }

    protected function submitLead(array $data): bool
    {
        $success = false;

        try {
            $response = Http::withHeaders($this->getHeaders())
                ->post($this->endpoint('enquiry'), $this->mapData($data))
                ->onError(fn ($error) => Log::error('Salesforce API error', [$error]));

            ApiNotification::query()
                ->create([
                    'provider' => 'salesforce',
                    'reference' => null,
                    'processed' => $response->successful(),
                    'data' => [
                        'request' => $data,
                        'result' => $response->json(),
                    ],
                ]);

            $this->responseMessage = $this->getResponseMessageFromResponseData($response->json());
            $success = $response->successful();
        } catch (\Exception $exception) {
            Log::error('Exception: ' . $exception->getMessage());
        } finally {
            return $success;
        }
    }

    protected function getResponseMessageFromResponseData($json): string
    {
        try {
            if (is_array($json)) {
                $element = $json;

                // if $json is an array containing an array, get the array
                if (count($element) > 0 && is_array(reset($element))) {
                    $element = reset($element);
                }

                if (array_key_exists('errorMessage', $element)) {
                    return $element['errorMessage'] ?? '';
                }

                if (array_key_exists('message', $element)) {
                    return $element['message'] ?? '';
                }
            }
        } catch (\Exception $exception) {
            Log::error('Exception: ' . $exception->getMessage());
        }

        return 'Autonomy unable to find message in Salesforce response';
    }

    /**
     * Gets the response message from the last API call.
     *
     * @return string|null The response message or null if not set.
     */
    public function getResponseMessage(): ?string
    {
        return $this->responseMessage;
    }

    protected function mapData(array $params): array
    {
        $data = [
            'enquiryId' => null,
            'enquiry' => [
                'salesforceId' => null,
                'message' => $params['message'] ?? null,
                'leadType' => null,
                'leadMedium' => null,
                'website' => null,
                'enquirySource' => null,
                'feedSource' => $this->config->feedSource(), // REQUIRED
                'formType' => $params['formType'] ?? null, // REQUIRED
                'pageUrl' => $params['pageUrl'] ?? null,
                'dealerId' => $this->getDealerId($params) ?? null, // REQUIRED
                'enquiryAdditionalDetails' => null,
                'testDriveFormSpecificRequirements' => null,
                'testDriveRequestedDateTime' => $params['testDriveRequestedDateTime'] ?? null,
                'showroomAppointmentDate' => null,
                'reserveCarAmount' => null,
                'reserveCarReservationDate' => null,
                'reserveCarReservedUntilDate' => null,
                'serviceBookingServiceOption' => null,
                'serviceBookingRequestedDate' => null,
                'valuationAmount' => null,
            ],
            'customer' => [
                'firstName' => $params['firstName'] ?? null,
                'lastName' => $params['lastName'] ?? null, // REQUIRED
                'street' => null,
                'city' => null,
                'postCode' => $params['postcode'] ?? null,
                'email' => $params['email'] ?? null, // REQUIRED
                'phone' => $params['mobile'] ?? null,
                'mobile' => $params['mobile'] ?? null, // REQUIRED
                'commerceCustomerId' => null,
                'commerceCustomerNumber' => null,
                'salesforceAccountId' => null,
            ],
            'customerContactPreferences' => [
                'email' => null,
                'sms' => null,
                'phone' => null,
                'post' => null,
            ],
            'vehicleOfInterest' => $params['vehicleOfInterest'] ?? [],
            'vehicleTradeIn' => $params['vehicleTradeIn'] ?? [],
        ];

        $data['enquiry']['reserveCarAmount'] = $params['reserveCarAmount'] ?? null;
        $data['enquiry']['reserveCarReservationDate'] = $params['reserveCarReservationDate'] ?? null;
        $data['enquiry']['valuationAmount'] = $params['valuationAmount'] ?? null;

        return $data;
    }

    protected function getDealerId(array $params): ?string
    {
        $vehicle_dealership = $params['vehicleOfInterest']['dealer_id'] ?? null;

        if ($vehicle_dealership) {
            return $vehicle_dealership;
        }

        $selected_dealership = $params['selected_dealership'] ?? '';

        if ($selected_dealership) {
            return Dealership::find($selected_dealership)->data['salesforce-dealer-id'] ?? null;
        }

        return null;
    }

    protected function getVehicleOfInterest(EnquiryModel $enquiry): array
    {
        $vehicle_data = [
            'vehicleSfId' => null,
            'vehicleProductName' => null,
            'make' => null,
            'model' => null,
            'regNo' => null,
            'year' => null,
            'derivative' => null,
            'fueltype' => null,
            'vin' => null,
            'mileage' => null,
            'type' => null,
        ];

        if (
            $enquiry->reason_type === 'vehicle'
            || $enquiry->reason_type === 'reservation'
        ) {
            $vehicle = $enquiry->reason_type === 'vehicle'
                ? $enquiry->reason
                : Vehicle::find($enquiry->reason->vehicle_id);

            if ($vehicle) {
                $vehicle_data = [
                    'vehicleSfId' => $vehicle->uuid,
                    'vehicleProductName' => $vehicle->title,
                    'make' => $vehicle->make?->name ?? null,
                    'model' => $vehicle->model?->name ?? null,
                    'regNo' => $vehicle->vrm_condensed,
                    'year' => $vehicle->manufacture_year,
                    'derivative' => $vehicle->derivative,
                    'fueltype' => $vehicle->fuelType?->name ?? null,
                    'vin' => $vehicle->vin,
                    'mileage' => $vehicle->odometer_mi,
                    'type' => $this->getVehicleTypeFromVehicle($vehicle),
                    'dealer_id' => $vehicle->dealership?->data['salesforce-dealer-id'] ?? null,
                ];
            }
        }

        if ($enquiry->reason_type === 'offer') {
            $vehicle_data = [
                'vehicleSfId' => null,
                'vehicleProductName' => $enquiry->reason->name,
                'make' => $enquiry->reason->make?->name ?? null,
                'model' => $enquiry->reason->model?->name ?? null,
                'regNo' => null,
                'year' => null,
                'derivative' => $enquiry->reason->derivative,
                'fueltype' => $enquiry->reason->fuelType?->name ?? null,
                'vin' => null,
                'mileage' => null,
                'type' => $this->getVehicleTypeFromOffer($enquiry->reason),
                'dealer_id' => $enquiry->reason->dealership?->data['salesforce-dealer-id'] ?? null,
            ];
        }

        return $vehicle_data;
    }

    protected function getVehicleTypeFromVehicle(Vehicle $vehicle): string
    {
        if (strtolower($vehicle->type) == 'car') {
            return $vehicle->is_new
                ? 'new car'
                : 'used car';
        }

        // assume LCV
        return $vehicle->is_new
            ? 'new van'
            : 'used van';
    }

    protected function getVehicleTypeFromOffer(VehicleOffer $offer): string
    {
        return 'new car';
    }

    protected function getVehicleOfInterestFromReservation(Reservation $reservation): array
    {
        return [
            'vehicleSfId' => $reservation->vehicle->uuid ?? null,
            'vehicleProductName' => $reservation->vehicle->title ?? null,
            'make' => $reservation->vehicle->make?->name ?? null,
            'model' => $reservation->vehicle->model?->name ?? null,
            'regNo' => $reservation->vehicle->vrm_condensed ?? null,
            'year' => $reservation->vehicle->manufacture_year ?? null,
            'derivative' => $reservation->vehicle->derivative ?? null,
            'fueltype' => $reservation->vehicle->fuelType?->name ?? null,
            'vin' => $reservation->vehicle->vin ?? null,
            'mileage' => $reservation->vehicle->odometer_mi ?? null,
            'type' => $this->getVehicleTypeFromVehicle($reservation->vehicle),
            'dealer_id' => $reservation->vehicle->dealership?->data['salesforce-dealer-id'] ?? null,
        ];
    }

    protected function getVehicleTradeIn(EnquiryModel $enquiry): array
    {
        $vehicle_data = [
            'vehicleProductName' => null,
            'make' => null,
            'model' => null,
            'regNo' => null,
            'year' => null,
            'derivative' => null,
            'fueltype' => null,
            'vin' => null,
            'mileage' => null,
            'type' => null,
            'sellingType' => null,
        ];

        $valuation = $enquiry->objects->where('object_type', 'valuation')->first()?->object;
        if ($valuation) {
            $vehicle_data = [
                'vehicleProductName' => null,
                'make' => $valuation->make ?? null,
                'model' => $valuation->model ?? null,
                'regNo' => $valuation->registration ?? null,
                'year' => null,
                'derivative' => $valuation->derivative ?? null,
                'fueltype' => $valuation->fuel_type ?? null,
                'vin' => null,
                'mileage' => $valuation->mileage ?? null,
                'type' => null,
                'sellingType' => 'Sell My Vehicle',
            ];
        }

        return $vehicle_data;
    }

    /**
     * @param string $path
     * @return string
     */
    protected function endpoint(string $path): string
    {
        return $this->base_url . '/services/apexrest/' . $path;
    }

    /**
     * @return string
     */
    protected function tokenEndpoint(): string
    {
        return (
            app()->isProduction()
            && (bool) Settings::get('salesforce-crm-test-mode', false) == false
        )
            ? 'https://login.salesforce.com/services/oauth2/token'
            : 'https://test.salesforce.com/services/oauth2/token';
    }

    /**
     * @return string[]
     */
    protected function getHeaders(): array
    {
        return [
            'Authorization' => 'Bearer ' . $this->accessToken(),
            'Content-Type' => 'application/json',
        ];
    }

    /**
     * @return string|null
     */
    protected function accessToken(): ?string
    {
        $payload = [
            'grant_type' => 'password',
            'password' => $this->config->password(),
            'username' => $this->config->username(),
            'client_id' => $this->config->clientId(),
            'client_secret' => $this->config->clientSecret(),
        ];

        $response = Http::withHeaders([
            'Content-Type' => 'x-www-form-urlencoded'
        ])->asForm()->post($this->tokenEndpoint(), $payload)
            ->onError(fn ($error) => Log::error('Salesforce CRM API token error', [ $error ]));

        if ($response->successful()) {
            $this->base_url = $response->json('instance_url');

            if (empty($this->base_url)) {
                Log::error('Salesforce CRM unable to retrieve service URL', [ $response->json() ]);
            }

            return $response->json('access_token');
        }

        return null;
    }

    protected function convertEnquiryToParams(EnquiryModel $enquiry): array
    {
        $answers = $enquiry->getNormalizedAnswers()->keyBy('question_id')->map(fn($item) => $item['answer']);
        $params = FormQuestion::query()
            ->whereIn('id', $answers->keys())
            ->get()
            ->keyBy('id')
            ->map(fn($question) => $question->data['salesforce-field'] ?? null)
            ->filter()
            ->flip()
            ->map(fn($id, $key) => $answers[$id] ?? null)
            ->toArray();

        $params['pageUrl'] = $this->getEnquiryPageSource($enquiry, $params);

        // formType should be present in the enquiry type data, but if it is missing or empty, fallback to a safe value
        $params['formType'] = ($enquiry->type?->data['salesforce_form_type'] ?? null) ?: 'Enquiry Form';
        $params['email'] = $enquiry->email;
        $params['vehicleOfInterest'] = $this->getVehicleOfInterest($enquiry);
        $params['vehicleTradeIn'] = $this->getVehicleTradeIn($enquiry);
        $params['testDriveRequestedDateTime'] = $this->getTestDriveDatetime($params);

        try {
            $valuation = $enquiry->objects->where('object_type', 'valuation')->first()?->object;
            if ($valuation) {
                // First check average price (adjusted or not, then use retail price if not set
                $params['valuationAmount'] = $valuation->adjusted_average_price ?? $valuation->average_price
                    ?? $valuation->adjusted_retail_price ?? $valuation->retail_price;
            } elseif ($enquiry->reason_type === 'reservation') {
                $params['reserveCarAmount'] = $enquiry->reason->amount;
                $params['reserveCarReservationDate'] = $this->getUtcDateTimeStringFromLocalDateTime();
            }
        } catch (\Throwable $throwable) {
            Log::warning($throwable->getMessage(), [
                'tenant' => tenant('id'),
                'enquiry' => $enquiry->id,
            ]);
        }

        return $params;
    }

    /**
     * Handle URL length restrictions within Salesforce CRM.
     * Salesforce restrict URLs to 255 characters.
     * This function optionally removes URL parameters
     * and appends the original URL to the unrestricted enquiry "message" field.
     *
     * @param EnquiryModel $enquiry
     * @param array $params
     * @return string
     */
    protected function getEnquiryPageSource(EnquiryModel $enquiry, array &$params): ?string
    {
        if (
            !Settings::get('salesforce-crm-remove-url-params')
            || empty($enquiry->source)
        ) {
            return $enquiry->source;
        }

        if (!array_key_exists('message', $params)) {
            $params['message'] = '';
        }
        $params['message'] .= ' [Original enquiry URL: ' . $enquiry->source . ']';

        $parts = explode('?', (string) $enquiry->source);
        return reset($parts);
    }

    protected function convertReservationToParams(Reservation $reservation): array
    {
        $params = [];
        $params['formType'] = 'Reserve a Car';
        $params['pageUrl'] = Site::vehicleUrl($reservation->vehicle);
        $params['email'] = $reservation->email;
        if (!empty($reservation->first_name)) {
            $params['firstName'] = $reservation->first_name;
            $params['lastName'] = $reservation->last_name;
        } else {
            $params['lastName'] = $reservation->name;
        }
        $params['mobile'] = $reservation->contact_number;
        $params['vehicleOfInterest'] = $this->getVehicleOfInterestFromReservation($reservation);
        $params['reserveCarAmount'] = $reservation->amount;
        $params['reserveCarReservationDate'] = $this->getUtcDateTimeStringFromLocalDateTime();
        $params['vehicleTradeIn'] = [
            'vehicleProductName' => null,
            'make' => null,
            'model' => null,
            'regNo' => null,
            'year' => null,
            'derivative' => null,
            'fueltype' => null,
            'vin' => null,
            'mileage' => null,
            'type' => null,
            'sellingType' => null
        ];

        return $params;
    }

    protected function getTestDriveDatetime(array $params): ?string
    {
        if (array_key_exists('testDriveDate', $params) && array_key_exists('testDriveTime', $params)) {
            $parts = explode('T', (string) $params['testDriveDate']);
            $date = reset($parts);

            $input_time = is_array($params['testDriveTime'])
                ? reset($params['testDriveTime'])
                : $params['testDriveTime'];

            $time = match (strtoupper((string) $input_time)) {
                'MORNING' => $this->config->testDriveBookingTimeMorning(),
                'AFTERNOON' => $this->config->testDriveBookingTimeAfternoon(),
                'EVENING' => $this->config->testDriveBookingTimeEvening(),
                default => $this->config->testDriveBookingTimeAnytime(),
            };

            // ensure that time is correct format
            if (strlen((string) $time) == 5) {
                $time .= ':00';
            }

            // ensure that time is correct format
            if (strlen((string) $time) != 8) {
                $time == '00:00:00';
            }

            return $this->getUtcDateTimeStringFromLocalDateTime($date . ' ' . $time);
        }

        return null;
    }

    protected function getUtcDateTimeStringFromLocalDateTime(string $date_time = ''): string
    {
        if (empty($date_time)) {
            $date_time = Carbon::now()->format('Y-m-d H:i:s');
        }

        $utc_datetime = Carbon::createFromFormat(
            'Y-m-d H:i:s',
            $date_time,
            Settings::get('app-timezone')
        )->setTimezone('UTC');

        return str_replace(' ', 'T', $utc_datetime) . 'Z';
    }
}
