<?php

namespace App\Modules\Stock;

use App\Contracts\SalesChannel;
use App\Contracts\StockProvider;
use App\Events\StockSyncFinished;
use App\Facades\Settings;
use App\Imports\AutoTraderApiToVehicleImport;
use App\Jobs\AutoTraderCSVExportJob;
use App\SalesChannelType;
use App\Services\AutoTraderApi;
use App\Services\AutoTraderHub;
use App\Traits\ChecksForExportMap;
use App\Traits\TracksImportStats;
use App\Traits\UsesSalesChannelRules;
use Carbon\Carbon;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Mtc\AutoTraderStock\Services\AutoTraderService;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\SalesChannelHistory;
use Mtc\MercuryDataModels\Vehicle;

class AutoTraderStock implements StockProvider, SalesChannel
{
    use ChecksForExportMap;
    use DispatchesJobs;
    use TracksImportStats;
    use UsesSalesChannelRules;

    private Collection $dealerships;
    private bool $fullSync;

    public function __construct(
        private readonly AutoTraderApiToVehicleImport $import,
        private readonly AutoTraderHub $export
    ) {
    }


    public function enabled(): bool
    {
        return (Settings::get('stock-auto-trader-enabled', false) ?? false)
            || (Settings::get('sales-channels-auto-trader-enabled', false) ?? false);
    }

    public function name(): string
    {
        return 'AutoTrader';
    }

    /**
     * Fields to add to dealership management
     *
     * @return array[]
     */
    public function dealershipAdditionalDataFields()
    {
        return [
            'auto-trader-location-id' => [
                'type' => 'text',
                'label' => 'Dealer ID within AutoTrader',
            ],
        ];
    }

    public function runScheduledImport(bool $fullSync = true): void
    {
        $this->resetStats();
        $this->fullSync = $fullSync;
        Vehicle::query()
            ->where('stock_provider', $this->import->getProviderName())
            ->update(['was_recently_synced' => 0]);

        $this->dealerships = $this->loadDealerships();
        $this->dealerships
            ->each(fn($dealership, $dealership_id) => $this->syncDealershipData($dealership, $dealership_id));

        if (Settings::get('auto-trader-stock-remove-old-vehicles') && $this->fullSync) {
            Vehicle::query()
                ->where('stock_provider', $this->import->getProviderName())
                ->where('was_recently_synced', 0)
                ->delete();
        }
        Event::dispatch(new StockSyncFinished('auto-trader'));
    }

    public function getStockStatus(): array
    {
        $this->dealerships = $this->loadDealerships()->unique();
        $locationData = $this->dealerships
            ->keyBy(fn($did) => $did)
            ->map(fn($did) => $this->getStockFromLocation($did));

        $stock = Vehicle::query()->with('dealership')->withImageCount()->get()->keyBy('vrm_condensed');
        return [
            'locations' => $locationData->map(fn($data, $did) => [
                'location' => $did,
                'total' => $data->count(),
                'published' => $data
                    ->filter(fn($v) => $v['adverts']['retailAdverts']['autotraderAdvert']['status'] == 'PUBLISHED')
                    ->count(),
                'profile' => $data
                    ->filter(fn($v) => $v['adverts']['retailAdverts']['profileAdvert']['status'] == 'PUBLISHED')
                    ->count(),
                'deleted' => $data
                    ->filter(fn($v) => in_array($v['metadata']['lifecycleState'], ['WASTEBIN', 'DELETED']))
                    ->count(),
                'no_images' => $data
                    ->reject(fn($v) => in_array($v['metadata']['lifecycleState'], ['WASTEBIN', 'DELETED']))
                    ->filter(fn($v) => count($v['media']['images']) == 0)
                    ->count(),
            ]),
            'vehicles' => $locationData->flatten(1)
                ->filter()
                ->map(fn($vehicle) => [
                    'stock_id' => $vehicle['metadata']['stockId'],
                    'location' => $vehicle['advertiser']['advertiserId'],
                    'vrm' => $vehicle['vehicle']['registration'],
                    'dealership' => $stock[$vehicle['vehicle']['registration']]?->dealership?->name ?? null,
                    'id' => $stock[$vehicle['vehicle']['registration']]?->id ?? null,
                    'make' => $vehicle['vehicle']['make'],
                    'model' => $vehicle['vehicle']['model'],
                    'images' => count($vehicle['media']['images']),
                    'image_count' => $stock[$vehicle['vehicle']['registration']]?->image_count ?? null,
                    'status' => $vehicle['metadata']['lifecycleState'],
                    'published' => $vehicle['adverts']['retailAdverts']['autotraderAdvert']['status'],
                    'profile' => $vehicle['adverts']['retailAdverts']['profileAdvert']['status'],
                ]),
        ];
    }

    public function getStockStatusForLocation(string $locationId): array
    {
        $data = $this->getStockFromLocation($locationId);
        $vrms = $data->pluck('vehicle.registration')->filter();

        $stock = Vehicle::query()
            ->with(['dealership', 'autoTraderData', 'primaryMediaUse.media'])
            ->withImageCount()
            ->whereIn('vrm_condensed', $vrms->toArray())
            ->get()
            ->keyBy('vrm_condensed');

        $summary = [
            'total' => $data->count(),
            'published' => $data
                ->filter(fn($v) => $v['adverts']['retailAdverts']['autotraderAdvert']['status'] == 'PUBLISHED')
                ->count(),
            'profile' => $data
                ->filter(fn($v) => $v['adverts']['retailAdverts']['profileAdvert']['status'] == 'PUBLISHED')
                ->count(),
            'deleted' => $data
                ->filter(fn($v) => in_array($v['metadata']['lifecycleState'], ['WASTEBIN', 'DELETED']))
                ->count(),
            'no_images' => $data
                ->reject(fn($v) => in_array($v['metadata']['lifecycleState'], ['WASTEBIN', 'DELETED']))
                ->filter(fn($v) => count($v['media']['images']) == 0)
                ->count(),
        ];

        $vehicles = $data->filter()->map(function ($vehicle) use ($stock) {
            $localVehicle = $stock[$vehicle['vehicle']['registration']] ?? null;

            return [
                'stock_id' => $vehicle['metadata']['stockId'],
                'vrm' => $vehicle['vehicle']['registration'],
                'id' => $localVehicle?->id,
                'make' => $vehicle['vehicle']['make'],
                'model' => $vehicle['vehicle']['model'],
                'price' => $vehicle['adverts']['retailAdverts']['totalPrice']['amountGBP'] ?? null,
                'attention_grabber' => $vehicle['adverts']['retailAdverts']['attentionGrabber'] ?? null,
                'images' => count($vehicle['media']['images']),
                'image_count' => $localVehicle?->image_count,
                'status' => $vehicle['metadata']['lifecycleState'],
                'published' => $vehicle['adverts']['retailAdverts']['autotraderAdvert']['status'],
                'profile' => $vehicle['adverts']['retailAdverts']['profileAdvert']['status'],
                'publish_advert' => $localVehicle?->autoTraderData?->publish_advert ?? null,
                'advert_publish_state' => $localVehicle?->autoTraderData?->advert_publish_state,
                'at_export_error' => $localVehicle?->autoTraderData?->at_export_error,
                'thumbnail' => $localVehicle?->getPreviewImage('mini-thumb'),
            ];
        })->values();

        $dealershipIds = Dealership::all()
            ->filter(fn(Dealership $d) => ($d->data['auto-trader-location-id'] ?? null) === $locationId)
            ->pluck('id');

        $localOnly = Vehicle::query()
            ->with(['autoTraderData', 'primaryMediaUse.media', 'make', 'model'])
            ->withImageCount()
            ->whereIn('dealership_id', $dealershipIds)
            ->whereNotIn('vrm_condensed', $vrms->toArray())
            ->get()
            ->map(fn(Vehicle $v) => [
                'stock_id' => $v->auto_trader_id,
                'vrm' => $v->vrm_condensed,
                'id' => $v->id,
                'make' => $v->make?->name,
                'model' => $v->model?->name,
                'price' => $v->price,
                'attention_grabber' => $v->attention_grabber,
                'images' => 0,
                'image_count' => $v->image_count,
                'status' => 'LOCAL_ONLY',
                'published' => null,
                'profile' => null,
                'publish_advert' => $v->autoTraderData?->publish_advert ?? null,
                'advert_publish_state' => $v->autoTraderData?->advert_publish_state,
                'at_export_error' => $v->autoTraderData?->at_export_error,
                'thumbnail' => $v->getPreviewImage('mini-thumb'),
            ])->values();

        return [
            'summary' => $summary,
            'vehicles' => $vehicles,
            'local_only' => $localOnly,
        ];
    }

    private function getStockFromLocation(string $did): Collection
    {
        $service = new AutoTraderService($did, App::make(AutoTraderApi::class));
        return $service->fetchVehicles();
    }

    private function syncDealershipData($dealership, $dealership_id)
    {
        $config = config('auto_trader_stock', []);
        $config['is_production'] = config('auto_trader_stock.use_sandbox') === false;

        $service = new AutoTraderService($dealership, App::make(AutoTraderApi::class));
        $vehicles = $service->fetchVehicles();

        $this->import->setDealershipId($dealership_id);

        $vehicles->filter(fn($vehicle_data) => $this->fullSync || $this->isRecentlyUpdated($vehicle_data))
            ->each(fn($vehicle_data) => $this->syncVehicle($vehicle_data, $service));
    }

    public function loadDealerships()
    {
        $this->dealerships = Dealership::all()
            ->each(function (Dealership $dealership) {
                $dealership->stock_location = $dealership->data['auto-trader-location-id'] ?? null;
            });

        return $this->dealerships
            ->keyBy(fn(Dealership $dealership) => $dealership->id)
            ->map(fn(Dealership $dealership) => $dealership->data['auto-trader-location-id'] ?? null)
            ->filter();
    }

    public function runAfterStockSync(): bool
    {
        return Settings::get('sales-channels-auto-trader-method') !== 'ftp';
    }

    public function runScheduledExport(): void
    {

        if ($this->hasActiveExportMap()) {
            return;
        }

        if (Settings::get('sales-channels-auto-trader-method') === 'ftp') {
            // AT CSV Export is restricted to 4x per day
            // We run a hourly schedule on weekdays between 7 and 19
            if (in_array(Carbon::now()->hour, [8, 11, 14, 18])) {
                $this->dispatch(new AutoTraderCSVExportJob());
            }
        } else {
            $this->exportViaApi();
            $this->syncVehicleAutoTraderId();
        }
    }

    private function syncVehicle(array $vehicle_data, AutoTraderService $service): void
    {
        try {
            if (!empty($vehicle_data['vehicle']['derivativeId']) && !$this->import->isVehicleDeleted($vehicle_data)) {
                $vehicle_data['enriched_data'] = $service->fetchAdditional($vehicle_data);
            }
            if ($this->import->exists($vehicle_data) === false) {
                $this->import->add($vehicle_data);
                $this->incrementCreated();
            } else {
                $this->import->update($vehicle_data);
                $this->incrementUpdated();
            }
        } catch (\Exception $e) {
            $this->incrementFailed();
        }
    }

    private function exportViaApi(): void
    {
        try {
            $this->export->setToken(Settings::get('auto-trader-hub-token'));
            $reference = $this->export->initExport();

            $includeDeleted = Settings::get('auto-trader-upsert-send-deleted');
            $maxAgeDays = (int)Settings::get('auto-trader-upsert-deleted-max-age-days');

            $query = Vehicle::query()
                ->with([
                    'dealership',
                    'make',
                    'model',
                    'fuelType',
                    'drivetrain',
                    'transmission',
                    'bodyStyle',
                    'mediaUses.media',
                    'features',
                    'equipment',
                    'specs',
                    'autoTraderData',
                ])
                ->where(fn($subQuery) => $subQuery->whereNotNull('registration_number')->orWhereNotNull('vin'));

            if ($this->hasSalesChannelRules(SalesChannelType::AUTOTRADER)) {
                $this->applyQueryConditionsForChannel($query, SalesChannelType::AUTOTRADER);
            }

            $recordsExported = $query->count();
            $query->chunk(100, function (Collection $batch) use ($reference) {
                $this->export->bulkExport($reference, $batch);
            });
            if ($includeDeleted && $maxAgeDays > 0) {
                $deletedQuery = Vehicle::onlyTrashed()
                    ->with([
                        'dealership',
                        'make',
                        'model',
                        'fuelType',
                        'drivetrain',
                        'transmission',
                        'bodyStyle',
                        'mediaUses.media',
                        'features',
                        'equipment',
                        'specs',
                    ])
                    ->where(fn($subQuery) => $subQuery->whereNotNull('registration_number')->orWhereNotNull('vin'))
                    ->where('deleted_at', '>=', now()->subDays($maxAgeDays));

                if ($this->hasSalesChannelRules(SalesChannelType::AUTOTRADER)) {
                    $this->applyQueryConditionsForChannel($deletedQuery, SalesChannelType::AUTOTRADER);
                }

                $removed = $deletedQuery->get();

                // Check if vehicles are still live with this vrm
                $same_vrm_exists = Vehicle::query()
                    ->whereIn('vrm_condensed', $removed->pluck('vrm_condensed'))
                    ->pluck('vrm_condensed');

                // Ones live need to be removed from the sync
                $removed = $removed
                    ->reject(fn(Vehicle $vehicle) => $same_vrm_exists->contains($vehicle->vrm_condensed));

                $removed->chunk(50)->each(fn($chunk) => $this->export->bulkExport($reference, $chunk));
                $recordsExported += $removed->count();
            }

            SalesChannelHistory::store('auto-trader-sales', true, $recordsExported . ' records exported');
        } catch (\Exception $exception) {
            SalesChannelHistory::store('auto-trader-sales', false, $exception->getMessage());
            Log::error('AutoTrader stock export failed with error:', [
                'tenant' => tenant('id'),
                'message' => $exception->getMessage(),
            ]);
        }
    }

    private function isRecentlyUpdated(array $vehicle_data): bool
    {
        try {
            return Carbon::parse($vehicle_data['metadata']['lastUpdatedByAdvertiser'])
                ->greaterThanOrEqualTo(Carbon::now()->subHours(24));
        } catch (\Exception) {
            // issue parsing updated timestamp
        }
        return true;
    }

    public function setAdvertFields(bool $isPublished): array
    {
        return [
            'autotraderAdvert' => $isPublished ? 'PUBLISHED' : 'NOT_PUBLISHED',
            'profileAdvert' => $isPublished ? 'PUBLISHED' : 'NOT_PUBLISHED',
        ];
    }

    private function syncVehicleAutoTraderId(): void
    {
        $this->dealerships = $this->loadDealerships()->unique();
        $this->dealerships
            ->keyBy(fn($did) => $did)
            ->map(fn($did) => $this->getStockFromLocation($did))
            ->each(fn($locationData) => $locationData
                ->reject(fn($vehicle) => in_array($vehicle['metadata']['lifecycleState'], ['WASTEBIN', 'DELETED']))
                ->each(fn($vehicle) => $this->sanityCheckVehicleAuToTraderId($vehicle)));
    }

    private function sanityCheckVehicleAuToTraderId($vehicle): void
    {
        $exists = Vehicle::query()
            ->where('auto_trader_id', $vehicle['metadata']['stockId'])
            ->where('vrm_condensed', $vehicle['vehicle']['registration'])
            ->exists();

        if ($exists) {
            return;
        }

        if (!empty($vehicle['vehicle']['registration'])) {
            Vehicle::query()
                ->where('vrm_condensed', $vehicle['vehicle']['registration'])
                ->update([
                    'auto_trader_id' => $vehicle['metadata']['stockId']
                ]);
        }
    }
}
