<?php

namespace App\Imports;

use App\Facades\Settings;
use App\Jobs\ImportImagesFromUrlList;
use App\TaxonomyMap;
use App\Traits\MapsTaxonomies;
use App\Traits\TracksImportStats;
use DOMDocument;
use DOMXPath;
use Exception;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Mtc\MercuryDataModels\Franchise;
use Mtc\MercuryDataModels\OfferType;
use Mtc\MercuryDataModels\VehicleOffer;
use Mtc\MercuryDataModels\VehicleOfferFinance;

class DealerInternetImport
{
    use DispatchesJobs;
    use MapsTaxonomies;
    use TracksImportStats;

    private string $make_to_import;
    private ?Franchise $franchise;

    public function __construct()
    {
        $this->make_to_import = Settings::get('stock-dealer-internet-make-to-import');
        $this->franchise = $this->getFranchise($this->make_to_import);
    }

    public function importVehicles()
    {
        $this->resetStats();
        $imported_offer_ids = [];

        collect($this->getModelStockUrls())->each(function ($model_url, $model_name) use (&$imported_offer_ids) {
            $stock = $this->getModelStock($model_url);
            collect($stock)->each(function ($item) use (&$imported_offer_ids, $model_name) {
                try {
                    $imported_offer_ids[] = $this->saveStockItem($item, $model_name);
                } catch (Exception $exception) {
                    $this->incrementFailed();
                    Log::warning(tenant('id') . ' Dealer Internet sync failed for model: ' . $model_name, [
                        'message' => $exception->getMessage(),
                        'trace' => $exception->getTrace(),
                        'item' => $item,
                    ]);
                }
            });
        });

        if (count($imported_offer_ids)) {
            VehicleOffer::query()
                ->where('stock_provider', $this->getProviderName())
                ->whereNotIn('id', $imported_offer_ids)
                ->delete();
        }
    }

    public function getModelStockUrls(): array
    {
        $urls = [];

        try {
            $xpath = $this->getXPathDataFromEndpoint('/car');

            if (empty($xpath)) {
                return [];
            }

            $tag_type = 'ul';
            $id = 'webMobileNavDropdownVehicles';
            $expression = "//$tag_type [contains(@id, '$id')]";
            $nodes = $xpath->query($expression);

            // Extract the URLs
            foreach ($nodes as $node) {
                $anchors = $xpath->query('.//a', $node);

                foreach ($anchors as $anchor) {
                    if ($anchor->getAttribute('href') == '/car/') {
                        continue;
                    }

                    $urls[$this->getModelNameFromDescription($anchor->nodeValue)] = $anchor->getAttribute('href');
                }
            }
        } catch (Exception $exception) {
            Log::warning(tenant('id') . ' Failed to get model stock URLs for make: ' . $this->make_to_import, [
                'message' => $exception->getMessage(),
                'trace' => $exception->getTrace(),
            ]);
        }

        return $urls;
    }

    public function getModelStock(?string $model_href): array
    {
        if (empty($model_href)) {
            return [];
        }

        try {
            $xpath = $this->getXPathDataFromEndpoint($model_href . 'stock/all/');

            if (empty($xpath)) {
                return [];
            }

            $tag_type = 'input';
            $class_name = 'vistaVehicleData';
            $expression = "//$tag_type [contains(@id, '$class_name')]";
            $nodes = $xpath->query($expression);

            if (count($nodes) > 0) {
                // get stock items from the data
                $json = $this->sanitiseStockJsonString($nodes[0]->getAttribute('value'));
                $data = json_decode($json, true);
            }
        } catch (Exception $exception) {
            Log::warning(tenant('id') . ' Failed to get model stock URLs for: ' . $model_href, [
                'message' => $exception->getMessage(),
                'trace' => $exception->getTrace(),
            ]);
        }

        return $data['Stock'] ?? [];
    }

    protected function saveStockItem(array $data, string $model_name = ''): int
    {
        $make_id = $this->getMappedTaxonomy(TaxonomyMap::MAKE, $this->make_to_import);

        $offer = VehicleOffer::query()->updateOrCreate([
            'uuid' => $data['DealerOrderNumber'],
        ], [
            'uuid' => $data['DealerOrderNumber'],
            'stock_provider' => $this->getProviderName(),
            'name' => implode(' ', [
                $this->make_to_import,
                $model_name,
                $data['Engine'] ?? ''
            ]),
            'make_id' => $make_id,
            'model_id' => $this->getMappedTaxonomy(TaxonomyMap::MODEL, $model_name, null, $make_id),
            'derivative' => $data['Engine'] ?? '',
            'trim' => $data['Series'] ?? '',
            'type_id' => OfferType::query()->where('slug', 'new')->first()?->id,
            'colour' => $data['Colour'],
            'price' => $data['DealerPriceNoFormat'] ?? '',
            'full_price' => $data['PriceListPriceNoFormat'] ?? '',
            'key_features' => collect($data['Options'] ?? null)->map(fn ($option) => $option['Name'] ?? ''),
            'transmission_id' => $this->getMappedTaxonomy(
                TaxonomyMap::TRANSMISSION,
                $this->getTransmission($data['Transmission'] ?? ''),
                $data,
            ),
            'franchise_id' => $this->franchise?->id ?? null,
        ]);

        if ($offer->wasRecentlyCreated) {
            $this->incrementCreated();
            $offer->published = (bool)Settings::get('stock-dealer-internet-default-to-published');
            $offer->save();
        } else {
            $this->incrementUpdated();
        }

        $this->syncImages($offer, $data['VehicleImage'] ?? null);

        $customer_deposit = $data['OptionsFinanceDeposit'] ?? 0;
        $dealer_deposit = $data['OptionsFinanceDepositAllowance'] ?? 0;

        VehicleOfferFinance::query()->updateOrCreate([
            'offer_id' => $offer->id,
        ], [
            'provider' => $this->getProviderName(),
            'finance_type' => $this->getFinanceType($data),
            'final_payment' => $data['OptionsFinanceGMFV'] ?? 0,
            'full_price' => $data['DealerPriceNoFormat'] ?? 0,
            'deposit' => $data['OptionsFinanceDeposit'] ?? 0,
            'total_amount' => $data['OptionsFinanceTotalAmountPayable'] ?? 0,
            'total_credit_amount' => $data['OptionsFinanceAmountOfCredit'] ?? 0,
            'apr' => $data['OptionsFinanceAPR'] ?? 0,
            'interest_rate' => $data['OptionsFinanceRateOfInterest'] ?? 0,
            'term' => $data['OptionsFinanceTerm'] ?? 0,
            'number_of_payments' => $data['OptionsFinanceTerm'] ?? 0,
            'monthly_price' => $data['MonthlyPriceNoFormat'] ?? 0,
            'first_payment' => $data['MonthlyPriceNoFormat'] ?? 0,
            'annual_mileage' => $data['OptionsFinanceMileage'] ?? 0,
            'customer_deposit' => $customer_deposit,
            'dealer_deposit_contribution' => $dealer_deposit,
            'deposit' => $customer_deposit + $dealer_deposit,
            'option_to_purchase_fee' => $data['OptionsFinancePurchaseFee'] ?? 0,
            'excess_mileage_charge' => $data['OptionsFinanceExcessMileageCharge'] ?? 0,
        ]);

        return $offer->id;
    }

    protected function getTransmission(string $transmission): ?string
    {
        if (empty($transmission)) {
            return null;
        }

        return stripos($transmission, 'manual') === false ? 'automatic' : 'manual';
    }

    /**
     * Attempt to determine the finance product type, based on data provided.
     * The finance example does not explicitly state the finance type.
     * We expect to receive only HP and PCP finance examples.
     * OptionsFinanceGMFV is understood to be the final payment, which is only available for PCP finance.
     *
     * @param array $data
     * @return string
     */
    protected function getFinanceType(array $data): string
    {
        if (array_key_exists('OptionsFinanceGMFV', $data) && $data['OptionsFinanceGMFV'] > 0) {
            // this finance example has a final payment > 0
            return 'PCP';
        }

        // this finance example has no final payment
        return 'HP';
    }

    protected function syncImages(VehicleOffer $offer, string $image)
    {
        if (empty($image)) {
            return;
        }

        $image = str_replace(' ', '%20', $image);
        $image = str_replace('.di.medium-thumb.png', '', $image);

        $this->dispatch(new ImportImagesFromUrlList(
            collect([
                $image,
            ]),
            $offer,
            false,
            'dealer-internet-import',
        ));
    }

    protected function getXPathDataFromEndpoint(string $endpoint = ''): ?DOMXPath
    {
        $data = $this->call($endpoint);

        if (empty($data)) {
            return null;
        }

        // Create a DOMDocument instance and load HTML content
        $dom = new DOMDocument();
        @$dom->loadHTML($data);

        return new DOMXPath($dom);
    }

    protected function call(string $endpoint): string
    {
        $response = Http::get($this->endpoint($endpoint));

        if ($response->failed()) {
            Log::warning($response->body());
            return '';
        }

        return $response->body();
    }

    protected function endpoint(string $string): string
    {
        $username = Settings::get('stock-dealer-internet-username');
        $base_url  = 'https://www.dealerinternet.co.uk/web/' . $username;

        // remove potentially duplicated slugs
        $string = str_replace('/web/' . $username, '', $string);
        return $base_url . $string;
    }

    protected function sanitiseStockJsonString(string $string): string
    {
        // We have seen malformed &ququot; and &quotquot; entities in the data.
        // Replace any similar entities with a double quote.
        return preg_replace('/&qu.*?ot;/', '"', $string);
    }

    protected function getProviderName(): string
    {
        return 'dealer-internet-import';
    }

    protected function getModelNameFromDescription(string $string): string
    {
        // We expect to see 'New Ford Focus'
        // or 'New All-Electric Ford Explorer'.
        // We only want 'Focus' or 'Explorer'.
        $string = explode('Ford', $string);
        return trim((string) last($string));
    }

    protected function getFranchise(string $make): ?Franchise
    {
        return Franchise::query()->where('slug', strtolower($make))->first();
    }
}
