<?php

namespace App\Services;

use App\Facades\Settings;
use App\Facades\Site;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Mtc\MercuryDataModels\ApiNotification;
use Mtc\MercuryDataModels\Vehicle;

class SalesforceStockApi
{
    /**
     * 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 = '';

    /**
     * Store the Salesforce API access token to be reused across requests.
     *
     * @var string
     */
    private string $access_token = '';

    /**
     * Store the expiry time of the access token.
     *
     * @var Carbon|null
     */
    private ?Carbon $access_token_expiry = null;

    public function updateVehicle(Vehicle $vehicle): void
    {
        try {
            $data = $this->mapData($vehicle);
            $response = Http::withHeaders($this->getHeaders())
                ->patch($this->endpoint($vehicle), $data)
                ->onError(fn ($error) => Log::error('Salesforce Stock API error', [$error]));

            ApiNotification::query()
                ->create([
                    'provider' => 'salesforce',
                    'reference' => $vehicle->vrm_condensed ?? null,
                    'processed' => $response->successful(),
                    'data' => [
                        'request' => $data,
                        'result' => $response->json(),
                    ],
                ]);
        } catch (\Exception $exception) {
            Log::error('Exception: ' . $exception->getMessage());
        }
    }

    public function updateBatch(Collection $batch): void
    {
        try {
            $data = $this->mapBatchData($batch);
            $response = Http::withHeaders($this->getHeaders())
                ->post($this->endpointBatch(), $data)
                ->onError(fn ($error) => Log::error('Salesforce Stock API error', [$error]));

            ApiNotification::query()
                ->create([
                    'provider' => 'salesforce',
                    'reference' => null,
                    'processed' => $response->successful(),
                    'data' => [
                        'request' => $data,
                        'result' => $response->json(),
                    ],
                ]);
        } catch (\Exception $exception) {
            Log::error('Exception: ' . $exception->getMessage());
        }
    }

    protected function mapBatchData(Collection $batch): array
    {
        return [
            'allOrNone' => false,
            'operations' => [
                [
                    'type' => 'Update',
                    'records' => $batch->map(fn(Vehicle $vehicle) => [
                        'fields' => [
                            'Id' => $vehicle->uuid,
                            'Website__c' => Site::vehicleUrl($vehicle),
                        ],
                    ]),
                ],
            ],
        ];
    }

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

    /**
     * @return string|null
     */
    protected function accessToken(): ?string
    {
        if ($this->tokenIsValid()) {
            return $this->access_token;
        }

        $payload = [
            'grant_type' => 'password',
            'password' => $this->password(),
            'username' => $this->username(),
            'client_id' => $this->clientId(),
            'client_secret' => $this->clientSecret(),
        ];

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

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

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

            $this->access_token = $response->json('access_token');
            $this->access_token_expiry = Carbon::now()
                ->addMinutes((int)Settings::get('sales-force-stock-export-token-expiry', 60));

            return $this->access_token;
        }

        return null;
    }

    protected function tokenIsValid()
    {
        return !empty($this->access_token) && Carbon::now() < $this->access_token_expiry;
    }

    /**
     * @return string
     */
    protected function tokenEndpoint(): string
    {
        return app()->isProduction()
            ? 'https://login.salesforce.com/services/oauth2/token'
            : 'https://test.salesforce.com/services/oauth2/token';
    }

    /**
     * @param string $path
     * @return string
     */
    protected function endpoint(Vehicle $vehicle): string
    {
        return $this->base_url . '/services/data/v61.0/sobjects/Stock_Vehicle__c/' . $vehicle->uuid;
    }

    protected function endpointBatch(): string
    {
        return $this->base_url . '/services/data/v61.0/ui-api/records/batch';
    }

    protected function mapData(Vehicle $vehicle)
    {
        return [
            'Website__c' => Site::vehicleUrl($vehicle),
        ];
    }

    protected function enabled(): bool
    {
        return (bool) Settings::get('sales-force-stock-export-enabled');
    }

    protected function username(): ?string
    {
        return Settings::get('sales-force-stock-export-username');
    }

    protected function password(): ?string
    {
        return Settings::get('sales-force-stock-export-password');
    }

    protected function clientId(): ?string
    {
        return Settings::get('sales-force-stock-export-client-id');
    }

    protected function clientSecret(): ?string
    {
        return Settings::get('sales-force-stock-export-client-secret');
    }
}
