<?php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use App\Facades\Settings;
use Mtc\MercuryDataModels\Vehicle;
use SimpleXMLElement;
use Mtc\ContentManager\Models\MediaUse;
use App\Jobs\ImportImagesFromUrlList;
use Mtc\MercuryDataModels\VehicleType;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Mtc\MercuryDataModels\ApiNotification;
use Mtc\MercuryDataModels\VehicleReview;

class CarAndDrivingService
{
    use DispatchesJobs;

    private string $endpoint =
        'https://www.caranddriving.com/ws/A041/ws.asmx/GetReviewsFromManufacturerCategoryAndType';

    private string $source = 'car-and-driving';

    private array $apiSummary = [
        'total_reviews_received' => 0,
        'total_cap_codes_received' => 0,
        'total_cap_ids_that_exists_on_site' => 0,
        'total_reviews_with_no_cap_ids' => 0,
        'total_images_processed' => 0,
        'total_videos_processed' => 0,
        'total_reviews_processed' => 0,
        'cap_ids_that_exists_on_site' => []
    ];

    public function import(string $userId): void
    {
        $response = Http::timeout(300)->get($this->endpoint, $this->buildParams($userId));

        if (!$response->successful()) {
            $this->createImportSummary($userId, false, $response->body());
            throw new \Exception('Failed to retrieve data stock from car and driving for tenant: ' . tenant('id'));
        }

        $xml = simplexml_load_string($response->body(), 'SimpleXMLElement', LIBXML_NOCDATA);
        $reviews = $xml->reviewlist->review ?? [];

        $this->apiSummary['total_reviews_received'] = count($reviews);

        $this->syncReview($reviews);
        $this->createImportSummary($userId, true, $response->body());
    }

    public function isCarAndDrivingEnabled(): bool
    {
        return Settings::get('app-content-car-and-driving-enabled');
    }

    /**
     * @note
     * Reviews will typically have one of the following 4 types; new1, new2, used or family
     *  although there are some other categorisations as well for specialist applications;
     * notably motorcycles and caravans.
     *
     * New car reviews come as new1 and new2;
     * the vast majority of reviews are new2, and these are the ones you should meet most, especially with new cars.
     *
     * The new1 reviews are typically long term ownership reviews, rather than general reviews on a car,
     *  and many of our customers don't use them unless they specifically want that for a particular article.
     */
    private function type(): string
    {
        $type = Settings::get('app-content-car-and-driving-sync-type');
        if ($type == 'all') {
            return 'new1,new2,used,family';
        }

        return ($type == 'new') ? 'new2' : 'used';
    }

    private function syncReview(SimpleXMLElement|array $reviews): void
    {
        foreach ($reviews as $review) {
            try {
                $capCodes = collect($this->parseCapCodes($review->capcodelist));

                if ($capCodes->isEmpty()) {
                    $this->apiSummary['total_reviews_with_no_cap_ids']++;
                    continue;
                }

                $this->apiSummary['total_cap_codes_received'] += $capCodes->count();

                $videos = $this->parseVideos($review->videolist ?? null);

                $vehicleReview = VehicleReview::updateOrCreate(
                    [
                        'external_review_id' => (string) $review->reviewid
                    ],
                    [
                        'provider'       => $this->source,
                        'make'           => (string) $review->manufacturer,
                        'model'          => (string) $review->model,
                        'main_video_url' => $this->extractVideoUrl($videos),
                        'review'         => json_encode($this->parseParagraphs($review->paragraphlist ?? null)),
                        'meta'           => [
                            'review_type' => (string) $review->reviewtype,
                            'status'      => (string) $review->status,
                            'cap_ids'     => $capCodes->pluck('cap_id')->all(),
                            'is_van'      => (string) $review->isvan,
                        ],
                    ]
                );

                $this->apiSummary['total_reviews_processed']++;

                $capCodes->each(fn ($capCode) => $this->bindReviewToVehicles($review, $capCode, $vehicleReview, $videos));
            } catch (\Throwable $throwable) {
                Log::error('Review import failed: ' . $throwable->getMessage(), [
                    'review_id' => (string) $review->reviewid ?? null
                ]);
            }
        }
    }

    private function bindReviewToVehicles($review, array $capCode, VehicleReview $vehicleReview, array $videos): void
    {
        Vehicle::query()
            ->where('cap_id', $capCode['cap_id'])
            ->where('type', (string) $review->isvan == "false" ? VehicleType::CAR->value : VehicleType::LCV->value)
            ->get()
            ->each(function (Vehicle $vehicle) use ($review, $vehicleReview, $capCode, $videos) {
                $vehicleReview->vehicles()->syncWithoutDetaching([$vehicle->id]);

                $this->apiSummary['total_cap_ids_that_exists_on_site']++;
                $this->apiSummary['cap_ids_that_exists_on_site'][] = $capCode['cap_id'];

                if ($this->shouldSyncImages($vehicle)) {
                    $this->syncImages(
                        $vehicle,
                        $this->parsePhotos($review->photolist ?? null)
                    );
                }

                if ($this->shouldSyncVideo()) {
                    $this->syncVideo($vehicle, $videos);
                }
            });
    }

    private function shouldSyncImages(Vehicle $vehicle): bool
    {
        if (!$this->isStockProviderAllowed($vehicle)) {
            return false;
        }

        if (!$this->isVehicleTypeAllowed($vehicle)) {
            return false;
        }

        return match (Settings::get('app-content-car-and-driving-image-sync-vehicle-types', 'none')) {
            'used' => $vehicle->is_new === false,
            'new'  => $vehicle->is_new === true,
            'all'  => true,
            'none' => false,
            default => false,
        };
    }

    private function isStockProviderAllowed(Vehicle $vehicle): bool
    {
        $allowed = Settings::get('app-content-car-and-driving-image-sync-stock-providers');

        if (empty($allowed)) {
            return true;
        }

        return in_array($vehicle->stock_provider ?? 'default', $allowed, true);
    }

    private function isVehicleTypeAllowed(Vehicle $vehicle): bool
    {
        $allowed = Settings::get('app-content-car-and-driving-image-sync-allowed-vehicle-types');

        if (empty($allowed)) {
            return true;
        }

        return in_array($vehicle->type, $allowed, true);
    }

    private function getImageImportBehaviour(): ?string
    {
        return Settings::get('app-content-car-and-driving-image-import-behaviour', 'only_if_none');
    }

    private function getImageOverwriteThreshold(): int
    {
        return Settings::get('app-content-car-and-driving-image-overwrite-threshold', 0);
    }

    private function shouldDeleteExistingCarAndDrivingImages(): bool
    {
        return Settings::get('app-content-car-and-driving-should-delete-existing-images', false);
    }

    private function syncImages(Vehicle $vehicle, array $images): void
    {
        $behaviour = $this->getImageImportBehaviour();
        $existingImagesCount = $vehicle->mediaUses()->count();

        // Delete Car and Driving images if other provider images exist & are above the minimum allowable threshold
        if ($this->shouldDeleteExistingCarAndDrivingImages()) {
            $existingNonCarAndDrivingImagesCount = $vehicle->mediaUses()
                ->whereDoesntHave('media', fn ($query) => $query->where('image_provider', $this->source))
                ->count();

            if ($existingNonCarAndDrivingImagesCount >= $this->getImageOverwriteThreshold()) {
                $vehicle->mediaUses()
                    ->whereHas('media', fn ($q) => $q->where('image_provider', $this->source))
                    ->delete();
                return;
            }
        }

        if ($behaviour === 'only_if_none' && $existingImagesCount > 0) {
            return;
        }

        if (
            $behaviour === 'overwrite'
            && $existingImagesCount < $this->getImageOverwriteThreshold()
        ) {
            $vehicle->mediaUses()->delete();
        }

        $imageLinks = collect($images)
            ->sortByDesc(fn ($image) => $image['is_default'] ? 1 : 0)
            ->pluck('large')
            ->filter()
            ->values()
            ->toArray();

        $toRemove = $this->getImagesToRemove($vehicle, $imageLinks);
        if (count($toRemove) > 0) {
            MediaUse::query()->whereIn('id', $toRemove)->delete();
        }

        $this->apiSummary['total_images_processed'] += count($imageLinks);

        $this->dispatch(new ImportImagesFromUrlList(collect($imageLinks), $vehicle, false, $this->source));
    }

    private function getImagesToRemove(?Vehicle $vehicle, array $images): Collection
    {
        $media_uses = $vehicle->mediaUses()->with('media')
            ->whereHas('media', fn ($query) => $query->where('image_provider', $this->source))
            ->get();

        $notInList = $media_uses
            ->filter(fn ($mediaUse) => !in_array($mediaUse->media->source_filename, $images))
            ->pluck('id');

        $duplicates = $media_uses->groupBy(fn ($mediaUse) => $mediaUse->media->source_filename)
            ->filter(fn (Collection $group) => $group->count() > 1)
            ->map(fn (Collection $group) => $group->first())
            ->pluck('id');

        return $notInList->merge($duplicates);
    }

    private function shouldSyncVideo(): bool
    {
        return Settings::get('app-content-car-and-driving-should-sync-videos') ?? false;
    }

    private function extractVideoUrl(array $videos): ?string
    {
        $hdVideo = $videos[0]['hdvideo'] ?? [];

        if (!empty($hdVideo) && isset($hdVideo['hdvideolist'])) {
            $videoToUse = collect($hdVideo['hdvideolist']->hdvideoitem)
                ->filter(fn ($list) => (string) $list->video_type === 'short 700 mp4')
                ->first();

            if (!empty($videoToUse) && !empty($videoToUse->video_url)) {
                return (string) $videoToUse->video_url;
            }
        }

        return null;
    }

    protected function syncVideo(Vehicle $vehicle, array $video): void
    {
        $url = $this->extractVideoUrl($video);

        if ($url) {
            $vehicle->main_video_url = $url;
            $vehicle->save();

            $this->apiSummary['total_videos_processed']++;
        }
    }

    private function normalizeXmlList(null|array|SimpleXMLElement $xmlElement): array
    {
        if (!$xmlElement) {
            return [];
        }

        if ($xmlElement instanceof \SimpleXMLElement) {
            if ($xmlElement->count() <= 1) {
                return [$xmlElement];
            }
            return iterator_to_array($xmlElement, false);
        }

        return (array)$xmlElement;
    }


    private function parseParagraphs(?SimpleXMLElement $paragraphList): array
    {
        $paragraphs = $paragraphList->paragraph ?? [];

        if ($paragraphs instanceof \SimpleXMLElement) {
            $paragraphs = $paragraphs->count() > 1
                ? iterator_to_array($paragraphs, false)
                : [$paragraphs];
        }

        return collect($paragraphs)
            ->map(fn ($p) => [
                'type' => (string)$p->type,
                'headline' => (string)$p->headline,
                'text' => (string)$p->text,
            ])
            ->toArray();
    }


    private function parsePhotos(?SimpleXMLElement $photoList): array
    {
        $photos = $this->normalizeXmlList($photoList->photo ?? []);

        return collect($photos)
            ->map(fn ($photo) => [
                'mini' => (string)$photo->mini,
                'std' => (string)$photo->std,
                'big' => (string)$photo->big,
                'large' => (string)$photo->large,
                'is_default' => ((string)$photo->isdefault) === 'true',
                'type' => (string)$photo->type ?? null,
            ])
            ->toArray();
    }

    private function parseVideos(?SimpleXMLElement $videoList): array
    {
        $videos = $this->normalizeXmlList($videoList->video ?? []);

        return collect($videos)
            ->map(fn ($video) => [
                'test_id' => (string)$video->test_id,
                'test_name' => (string)$video->test_name,
                'doc_id' => (string)$video->doc_id,
                'category' => (string)$video->category,
                'img_url' => (string)$video->img_url,
                'video_id' => (string)$video->video_id,
                'video_filename' => (string)$video->video_filename,
                'video_url_for_iframe' => (string)$video->video_urlforiframe,
                'video_desc' => (string)$video->video_desc,
                'test_date' => (string)$video->test_date,
                'is_default' => ((string)$video->isdefault) === 'true',
                'hdvideo' => (array)$video->hdvideo ?? [],
            ])
            ->toArray();
    }

    private function parseCapCodes(?SimpleXMLElement $capcodeList): array
    {
        $codes = $this->normalizeXmlList($capcodeList->capcode ?? []);

        return collect($codes)
            ->map(fn ($code) => [
                'cap_id' => (string)$code->cap_id,
                'capcode' => (string)$code->capcode,
                'capmodelshort' => (string)$code->capmodelshort,
                'capmodellong' => (string)$code->capmodellong,
            ])
            ->toArray();
    }

    public function getEndpoint(): string
    {
        return $this->endpoint;
    }

    private function buildParams(string $userId): array
    {
        return [
            'user_id' => $userId,
            'type' => $this->type(),
            'howmany' => 999999,
            'manufacturer' => '',
            'carname' => '',
            'category' => '',
            'ordering' => '',
            'hasphotosonly' => '',
            'hasvideosonly' => '',
            'getsummaries' => '',
            'rangereviewsonly' => '',
            'docapsearch' => '',
            'liveonly' => 'true'
        ];
    }

    private function createImportSummary(string $userId, bool $processed, ?string $rawResponse): void
    {
        ApiNotification::query()->create([
            'provider' => $this->source,
            'processed' => $processed,
            'headers' => [],
            'data' => [
                'summary' => $this->apiSummary,
                'request_params' => $this->buildParams($userId),
                'endpoint' => $this->endpoint,
            ],
            'data_model' => VehicleReview::class,
        ]);
    }
}
