<?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\AutoTraderAIApi;
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;
use Mtc\MercuryDataModels\VehicleAutoTraderData;

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)
            ->reject(fn ($row) => $row['metadata']['lifecycleState'] === 'SOLD');
        $vrms = $data->pluck('vehicle.registration')->filter();
        $vins = $data->pluck('vehicle.vin')->filter();
        $stockIds = $data->pluck('metadata.stockId')->filter();

        $stock = Vehicle::query()
            ->with(['dealership', 'autoTraderData', 'primaryMediaUse.media'])
            ->withImageCount()
            ->where(fn ($q) => $q
                ->whereIn('vrm_condensed', $vrms->toArray())
                ->orWhereIn('vin', $vins->toArray())
                ->orWhereIn('auto_trader_id', $stockIds->toArray()))
            ->get();

        $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) {
                $atStockId = $vehicle['metadata']['stockId'] ?? null;
                $atVin = $vehicle['vehicle']['vin'] ?? null;
                $atVrm = $vehicle['vehicle']['registration'] ?? null;

                /** @var Vehicle|null $localVehicle */
                $localVehicle = $stock->filter(fn ($entry) =>
                    ($atStockId && $entry->auto_trader_id === $atStockId)
                    || ($atVin && $entry->vin === $atVin)
                    || ($atVrm && $entry->vrm_condensed === $atVrm))
                    ->first();
                if ($localVehicle) {
                    $advertData = $vehicle['adverts']['retailAdverts'];
                    $localVehicle->autoTraderData()->update([
                        'advert_publish_state' => $advertData['autotraderAdvert']['status'] ?? null,
                        'profile_publish_state' => $advertData['profileAdvert']['status'] ?? null,
                        'lifecycle_state' => $vehicle['metadata']['lifecycleState'] ?? null,
                    ]);
                }
                return [
                    'stock_id' => $vehicle['metadata']['stockId'],
                    'vrm' => $vehicle['vehicle']['registration'],
                    'id' => $localVehicle?->id,
                    'make' => $vehicle['vehicle']['make'],
                    'model' => $vehicle['vehicle']['model'],
                    'make_id' => $localVehicle?->make_id,
                    'model_id' => $localVehicle?->model_id,
                    'is_new' => $localVehicle?->is_new,
                    'manufacture_year' => $localVehicle?->manufacture_year,
                    'derivative' => $localVehicle?->derivative,
                    'derivative_id' => $localVehicle?->autoTraderData?->derivative_id,
                    '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' => $advertData['autotraderAdvert']['status'] ?? null,
                    'publish_profile' => $localVehicle?->autoTraderData?->publish_profile ?? null,
                    'profile_publish_state' => $advertData['profileAdvert']['status'] ?? null,
                    '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())
            ->whereNotIn('vin', $vins->toArray())
            ->whereNotIn('auto_trader_id', $stockIds->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,
                'make_id' => $v->make_id,
                'model_id' => $v->model_id,
                'is_new' => $v->is_new,
                'manufacture_year' => $v->manufacture_year,
                'derivative' => $v->derivative,
                'derivative_id' => $v->autoTraderData?->derivative_id,
                '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,
                'publish_profile' => $v->autoTraderData?->publish_profile ?? null,
                'profile_publish_state' => $v->autoTraderData?->profile_publish_state,
                'at_export_error' => $v->autoTraderData?->at_export_error,
                'thumbnail' => $v->getPreviewImage('mini-thumb'),
            ])->values();

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

    public function refreshVehicleStatus(Vehicle $vehicle): void
    {
        $locationId = $vehicle->dealership?->data['auto-trader-location-id'] ?? null;
        $stockId = $vehicle->auto_trader_id;

        if (!$locationId || !$stockId) {
            return;
        }

        $atVehicle = $this->getStockFromLocation($locationId)
            ->first(fn ($v) => $v['metadata']['stockId'] === $stockId);

        if (!$atVehicle) {
            return;
        }

        $advertData = $atVehicle['adverts']['retailAdverts'] ?? [];
        $record = $vehicle->autoTraderData()->updateOrCreate(
            ['vehicle_id' => $vehicle->id],
            [
                'advert_publish_state' => $advertData['autotraderAdvert']['status'] ?? null,
                'profile_publish_state' => $advertData['profileAdvert']['status'] ?? null,
                'lifecycle_state' => $atVehicle['metadata']['lifecycleState'] ?? null,
            ],
        );
        $record->touch();
    }

    private function getStockFromLocation(string $did): Collection
    {
        $service = new AutoTraderService($did, $this->resolveStockApiClient());
        return $service->fetchVehicles();
    }

    /**
     * Pick which AT API client to read stock through. When the AT sales channel
     * is enabled, the dealer's stock lives under the AI/Hub credentials, so we
     * must use AutoTraderAIApi or its access token won't see the records.
     */
    private function resolveStockApiClient(): AutoTraderApi
    {
        return Settings::get('sales-channels-auto-trader-enabled', false)
            ? App::make(AutoTraderAIApi::class)
            : App::make(AutoTraderApi::class);
    }

    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, $this->resolveStockApiClient());
        $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);
            }
            $fuelType = $vehicle_data['vehicle']['standard']['fuelType'] ?? $vehicle_data['vehicle']['fuelType'] ?? '';
            if ($fuelType === 'Electric' || str_contains($fuelType, 'Plug-in')) {
                $vehicle_data['chargeTimes'] = $service->loadEVData($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 = $this->baseExportQuery()
                ->whereHas('autoTraderData', fn ($q) => $q->where('pending_at_export', true));

            $primaryCount = $query->count();
            $recordsExported = 0;
            $query->chunk(100, function (Collection $batch) use ($reference, &$recordsExported) {
                if ($this->export->bulkExport($reference, $batch)) {
                    $this->markExported($batch);
                    $recordsExported += $batch->count();
                }
            });

            $dueBatch = $this->dueForReExportQuery($primaryCount)->get();
            if ($dueBatch->isNotEmpty() && $this->export->bulkExport($reference, $dueBatch)) {
                $this->markExported($dueBatch);
                $recordsExported += $dueBatch->count();
            }

            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(),
            ]);
        }
    }

    /**
     * Base query for live vehicles eligible for AutoTrader export.
     */
    private function baseExportQuery()
    {
        $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);
        }

        return $query;
    }

    /**
     * Vehicles whose pending flag is cleared but whose last successful export
     * is older than the configured interval (or has never been recorded).
     * Limited to max($batchMin, ceil($primaryCount * $batchPercent / 100))
     * so re-export traffic stays bounded each run.
     */
    private function dueForReExportQuery(int $primaryCount)
    {
        $intervalDays = (int) (Settings::get('auto-trader-hub-reexport-interval-days') ?? 3);
        $batchMin = (int) (Settings::get('auto-trader-hub-reexport-batch-min') ?? 10);
        $batchPercent = (int) (Settings::get('auto-trader-hub-reexport-batch-percent') ?? 10);

        $limit = max($batchMin, (int) ceil($primaryCount * $batchPercent / 100));
        $cutoff = now()->subDays($intervalDays);

        return $this->baseExportQuery()
            ->whereHas('autoTraderData', function ($q) use ($cutoff) {
                $q->where('pending_at_export', false)
                    ->where(fn ($w) => $w->whereNull('last_exported_at')->orWhere('last_exported_at', '<', $cutoff));
            })
            ->orderByRaw('(SELECT last_exported_at FROM vehicle_autotrader_data WHERE vehicle_autotrader_data.vehicle_id = vehicles.id) ASC')
            ->limit($limit);
    }

    /**
     * Mark a batch of vehicles as successfully exported.
     */
    private function markExported(Collection $batch): void
    {
        $vehicleIds = $batch->pluck('id')->all();
        if (empty($vehicleIds)) {
            return;
        }

        VehicleAutoTraderData::query()
            ->whereIn('vehicle_id', $vehicleIds)
            ->update([
                'pending_at_export' => false,
                'last_exported_at' => now(),
            ]);
    }

    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']
                ]);
        }
    }
}
