<?php

namespace App\Http\Controllers;

use App\AIContentRepository;
use App\Events\VehicleUpdated;
use App\ExportMapRepository;
use App\Exports\VehicleExport;
use App\Facades\Settings;
use App\Http\Requests\AddVehicleRequest;
use App\Http\Requests\CopyVehicleRequest;
use App\Http\Requests\ImportFileRequest;
use App\Http\Requests\SetFeaturedVehiclesRequest;
use App\Http\Requests\UpdateVehicleRequest;
use App\Http\Requests\UpdateVehicleStateRequest;
use App\Http\Requests\SelectionSearchSearchRequest;
use App\Http\Resources\VehicleListResource;
use App\Http\Resources\VehiclePrintResource;
use App\Http\Resources\VehicleViewResource;
use App\ImageSyncServiceHelper;
use App\Jobs\BulkImportJob;
use App\Jobs\ProcessMediaUseChunkJob;
use App\MediaRepository;
use App\Models\BulkImportLog;
use App\Models\ExportMap;
use App\Modules\Stock\AutoTraderStock;
use App\Services\AutoTraderHub;
use App\SmartImageManagementRepository;
use App\VehicleRepository;
use App\VehicleSpec\Jobs\FetchVehicleSpecData;
use App\VehicleSpec\Jobs\VehicleCapIdLookup;
use App\VehicleStockSyncLogRepository;
use Carbon\Carbon;
use Exception;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Facades\Excel;
use Mtc\MercuryDataModels\MediaUse;
use Mtc\MercuryDataModels\Services\FinanceService;
use Mtc\MercuryDataModels\Services\FinanceServiceHelper;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleMake;
use Mtc\MercuryDataModels\VehicleModel;
use Mtc\VehicleLookup\VehicleLookupService;

class VehicleController extends Controller
{
    use DispatchesJobs;

    public function __construct()
    {
        $this->middleware([
            'permission:edit-vehicles',
        ]);
    }

    /**
     * Display a list of vehicles
     *
     * @return VehicleListResource
     */
    public function index(SelectionSearchSearchRequest $request): VehicleListResource
    {
        $perPage = min((int) $request->input('per_page', 15), 200);

        $vehicles = Vehicle::query()
            ->with('primaryMediaUse.media')
            ->withViewCount()
            ->withSearchViewCount()
            ->withImageCount()
            ->withEnquiryCount()
            ->setSortBy($request->input('sort_by', 'id_desc'))
            ->setFilters($request->input('filters', []))
            ->when($request->filled('selections'), fn($query) => $query->setSelections($request->getSelections()))
            ->when(
                $request->filled('search_term'),
                fn($query) => $query->where(
                    fn($q) => $q->whereFullText('search_content', $request->input('search_term'))
                        ->orWhere('slug', 'like', $request->input('search_term') . '%')
                )
            )
            ->when(
                $request->filled('ids'),
                fn($query) => $query->whereIn('id', explode(',', $request->input('ids')))
            );

        if ($request->withArchived()) {
            $vehicles = $vehicles->withTrashed();
        }

        return new VehicleListResource($vehicles->paginate($perPage));
    }

    /**
     * Return vehicles formatted for print view
     */
    public function print(SelectionSearchSearchRequest $request): VehiclePrintResource
    {
        $vehicles = Vehicle::query()
            ->with(['primaryMediaUse.media', 'transmission', 'fuelType', 'dealership'])
            ->where('is_sold', false)
            ->setSortBy($request->input('sort_by', 'id_desc'))
            ->setFilters($request->input('filters', []))
            ->when(
                $request->filled('selections'),
                fn($query) => $query->setSelections($request->getSelections())
            )
            ->when(
                $request->filled('search_term'),
                fn($query) => $query->where(
                    fn($q) => $q->whereFullText('search_content', $request->input('search_term'))
                        ->orWhere('slug', 'like', $request->input('search_term') . '%')
                )
            )
            ->when(
                $request->filled('ids'),
                fn($query) => $query->whereIn('id', explode(',', $request->input('ids')))
            );

        if ($request->withArchived()) {
            $vehicles = $vehicles->withTrashed();
        }

        return new VehiclePrintResource($vehicles->get());
    }

    /**
     * Save a new vehicle.
     *
     * @param AddVehicleRequest $request
     * @param VehicleRepository $repository
     * @return VehicleViewResource
     */
    public function store(
        AddVehicleRequest $request,
        VehicleRepository $repository
    ): VehicleViewResource|Response {
        try {
            $vehicle = $repository->create($request);
            return new VehicleViewResource($vehicle->refresh());
        } catch (Exception $exception) {
            return response([
                'message' => $exception->getMessage()
            ], 422);
        }
    }

    /**
     * Show vehicle details.
     *
     * @param int $id
     * @return VehicleViewResource
     */
    public function show(int $id): VehicleViewResource
    {
        $vehicle = Vehicle::query()->withTrashed()->findOrFail($id);
        return new VehicleViewResource($vehicle);
    }

    /**
     * Update vehicle details
     *
     * @param UpdateVehicleRequest $request
     * @param Vehicle $vehicle
     * @param VehicleRepository $repository
     * @return VehicleViewResource
     */
    public function update(
        UpdateVehicleRequest $request,
        Vehicle $vehicle,
        VehicleRepository $repository
    ): VehicleViewResource {
        $previousPrice = $vehicle->price;
        $previousImageCount = $vehicle->mediaUses()->count();
        $previousVideoCount = $vehicle->video_count;

        if ($repository->update($request, $vehicle)) {
            Event::dispatch(new VehicleUpdated(
                $vehicle,
                $previousPrice,
                $previousImageCount,
                $previousVideoCount
            ));
        }

        return new VehicleViewResource($vehicle->refresh());
    }

    /**
     * Update status of a vehicle
     *
     * @param UpdateVehicleStateRequest $request
     * @param Vehicle $vehicle
     * @return VehicleViewResource
     */
    public function updateStatus(UpdateVehicleStateRequest $request, Vehicle $vehicle)
    {
        $vehicle->updateStatus($request->input('state'));
        return new VehicleViewResource($vehicle->refresh());
    }

    /**
     * Remove the specified vehicle from storage.
     *
     * @param Vehicle $vehicle
     * @return bool
     */
    public function destroy(Vehicle $vehicle): bool
    {
        return $vehicle->delete();
    }

    /**
     * Trigger Finance data fetch for vehicle
     *
     * @param Vehicle $vehicle
     * @param FinanceService $service
     * @return Response
     */
    public function getFinance(Request $request, Vehicle $vehicle, FinanceService $service): Response
    {
        if (FinanceServiceHelper::hasEnabledProvider() && $service->request($vehicle, $request->input(), true)) {
            return response('ok');
        }
        return \response(['message' => 'Request failed, please try again later'], 422);
    }

    public function getCapId(Vehicle $vehicle): Response
    {
        if (!Settings::get('vehicle-spec-providers-cap-enabled')) {
            throw ValidationException::withMessages([
                'Cap Not available'
            ]);
        }
        if (!empty($vehicle->cap_id)) {
            throw ValidationException::withMessages([
                'Cap ID already set for vehicle'
            ]);
        }
        $this->dispatch(new VehicleCapIdLookup($vehicle));
        return response('ok');
    }

    /**
     * @param Vehicle $vehicle
     * @param AIContentRepository $AIContentRepository
     * @return JsonResponse
     * @throws ValidationException
     */
    public function getAIDescription(Vehicle $vehicle, AIContentRepository $AIContentRepository): JsonResponse
    {
        if (!$AIContentRepository->hasEnabledIntegration()) {
            throw ValidationException::withMessages([
                __('ai-content.integration_not_enabled')
            ]);
        }

        $description = $AIContentRepository->getDescription($vehicle);
        if (empty($description)) {
            throw ValidationException::withMessages([
                __('ai-content.description_not_available')
            ]);
        }

        return response()->json([
            'description' => $description
        ]);
    }

    public function getRecommendedImagesOrder(
        Vehicle $vehicle,
        SmartImageManagementRepository $smartImageManagementRepository
    ): JsonResponse {

        if (!$smartImageManagementRepository->hasEnabledIntegration()) {
            throw ValidationException::withMessages([
                __('smart_image_management.integration_not_available')
            ]);
        }

        $smartImageManagementRepository->loadSmartImageManagementData($vehicle);

        return response()->json([
            'recommended_image_order' => $smartImageManagementRepository->getRecommendedImageOrder(),
            'key_images' => $smartImageManagementRepository->getKeyImages(),
            'key_images_text' => $smartImageManagementRepository->getKeyImagesText(),
            'image_order_applied_text' => $smartImageManagementRepository->getImageOrderAppliedText(),
            'recommended_image_order_text' => $smartImageManagementRepository->getRecommendedImageOrderText(),
            'error' => $smartImageManagementRepository->getError(),
        ]);
    }

    public function getStockSyncLogRecords(Vehicle $vehicle, VehicleStockSyncLogRepository $repository): JsonResponse
    {
        return response()->json([
            'data' => $repository->getImportRecords($vehicle)
        ]);
    }

    /**
     * Trigger Image fetch for vehicle
     *
     * @param Vehicle $vehicle
     * @return void
     */
    public function getImages(Vehicle $vehicle, ImageSyncServiceHelper $service): Response
    {
        $service->triggerSync($vehicle);
        return response('ok');
    }

    /**
     * Show list of featured vehicles
     *
     * @return VehicleListResource
     */
    public function getFeatured(): VehicleListResource
    {
        return new VehicleListResource(
            Vehicle::query()->where('featured', 1)->get()
        );
    }

    /**
     * Set vehicles as featured
     *
     * @param SetFeaturedVehiclesRequest $request
     * @return VehicleListResource
     */
    public function setFeatured(SetFeaturedVehiclesRequest $request): VehicleListResource
    {
        Vehicle::query()
            ->whereNotIn('id', $request->input('vehicle_ids'))
            ->update([
                'featured' => 0
            ]);

        Vehicle::query()
            ->whereIn('id', $request->input('vehicle_ids'))
            ->update([
                'featured' => 1
            ]);

        return $this->getFeatured();
    }

    public function getSpec(Vehicle $vehicle)
    {
        $this->dispatch(new FetchVehicleSpecData($vehicle));
    }

    public function getWarnings(VehicleRepository $repository): Response
    {
        return response([
            'warnings' => $repository->getVehicleWarnings(),
            'has_unmapped_taxonomies' => $repository->getHasUnmappedTaxonomies(),
        ]);
    }

    public function copy(
        CopyVehicleRequest $request,
        Vehicle $vehicle,
        VehicleRepository $repository
    ): VehicleViewResource {
        $vehicle = $repository->copy($request, $vehicle);
        return new VehicleViewResource($vehicle);
    }

    public function restore(int $vehicle_id, VehicleRepository $repository): Response
    {
        $repository->restore($vehicle_id);
        return \response('ok');
    }
    public function export(ExportMap $exportMap)
    {
        if ($exportMap->exists) {
            $fileName = Str::slug($exportMap->name) . '.' . $exportMap->format;
            return App::make(ExportMapRepository::class, ['export_map' => $exportMap])->download($fileName);
        }

        $fileName = 'vehicle_data.xlsx';
        return Excel::download(new VehicleExport(), $fileName);
    }

    /**
     * Import seo data from file
     *
     * @param ImportFileRequest $request
     * @return bool[]
     */
    public function import(ImportFileRequest $request): array
    {
        $tmpName = 'vehicle-import-' . Carbon::now()->format('Y_m_d_H_i_s') . '.'
            . $request->file('file')->getClientOriginalExtension();
        $path_to_file = 'vehicle-import/' . $tmpName;

        Storage::disk('file-storage')->put($path_to_file, $request->file('file')->getContent());
        $log = BulkImportLog::query()
            ->create([
                'user_id' => Auth::id(),
                'object_type' => 'vehicle',
                'import_type' => $request->input('type'),
                'automations' => $request->input('automations', []),
                'filename' => $path_to_file,
            ]);
        $this->dispatch(
            new BulkImportJob('vehicle', $path_to_file, 'edit-vehicles', $request->input('type') ?? '')
                ->setLog($log)
                ->setAdditionalTasks($request->input('automations', []))
        );
        return [
            'success' => true,
        ];
    }

    public function lookup(
        Request $request,
        VehicleLookupService $lookupService,
        VehicleRepository $repository
    ): Response {
        $data = [
            'values' => []
        ];
        try {
            // Check if lookup is disabled for offers context
            $context = $request->input('context');
            $disableLookupForOffers = $context === 'offers' && Settings::get('offers-disable-lookup', false);

            if ($disableLookupForOffers) {
                $activeDriver = null;
                $data['has_lookup'] = false;
            } else {
                if ($request->input('provider') === 'at-connect') {
                    $lookupService->setDriver('at-connect');
                }
                $activeDriver = $lookupService->findActiveDriver();
                $data['has_lookup'] = $lookupService->hasActiveDriver();
            }
            $make = VehicleMake::query()->find($request->input('make_id'));
            $model = VehicleModel::query()->find($request->input('model_id'));
            $data['values'] = match ($request->input('data_type')) {
                'vrm' => $repository->findVehicleByVrm($request->input('vrm'), true),
                'make' => VehicleMake::query()
                    ->when($activeDriver == 'auto-trader', fn($query) => $query->whereNotNull('autotrader_id'))
                    ->when($activeDriver == 'cap', fn($query) => $query->whereNotNull('cap_id'))
                    ->select(['name', 'id', 'slug', 'cars', 'lcvs', 'motorcycles', 'motorhomes', 'caravans'])
                    ->get(),
                'model' => VehicleModel::query()
                    ->when($activeDriver == 'auto-trader', fn($query) => $query->whereNotNull('autotrader_id'))
                    ->when(
                        $activeDriver == 'cap',
                        fn($query) => $query->where(fn($capQuery) => $capQuery->whereNotNull('lcv_cap_id')
                            ->orWhereNotNull('cap_id'))
                    )
                    ->where('make_id', $request->input('make_id'))
                    ->select(['name', 'id', 'type', 'slug'])
                    ->get(),
                'derivative' => $lookupService->findDerivatives($make, $model),
                'variant' => $lookupService->findVariants($make, $model, $request->input('derivative') ?? ''),
                default => [],
            };
            $data['variant_type'] = $activeDriver;
        } catch (\Exception $exception) {
            $data['error'] = $exception->getMessage();
        }

        return \response($data);
    }

    public function reCropMedia(Vehicle $vehicle, MediaRepository $repository)
    {
        $media_uses = MediaUse::query()
            ->with([
                'media',
                'owner',
            ])
            ->where('owner_type', $vehicle->getMorphClass())
            ->where('owner_id', $vehicle->id)
            ->get();

        ProcessMediaUseChunkJob::dispatchSync($media_uses, $repository);
        return \response('ok');
    }

    public function removeFromAutoTrader(Vehicle $vehicle, AutoTraderHub $service): Response
    {
        $token = Settings::get('auto-trader-hub-token');
        if (!empty($vehicle->auto_trader_id) && $service->setToken($token)->removeVehicle($vehicle)) {
            $vehicle->update([
                'auto_trader_id' => null,
            ]);
        }
        return \response('ok');
    }

    public function refreshAutoTraderStatus(Vehicle $vehicle): array
    {
        $vehicle->load(['dealership', 'autoTraderData']);

        App::make(AutoTraderStock::class)->refreshVehicleStatus($vehicle);
        $vehicle->load('autoTraderData');

        return ['auto_trader_data' => $vehicle->autoTraderData];
    }

    public function saveOrder(Request $request)
    {
        $ids = collect($request->input())->pluck('id');

        $originalOrders = Vehicle::whereIn('id', $ids)
            ->orderBy('order')
            ->get(['id', 'order'])
            ->pluck('order', 'id');

        $orderValues = $originalOrders->values()->contains(null)
            ? collect(range(0, $ids->count() - 1))
            : $originalOrders->values();

        try {
            DB::transaction(static function () use ($ids, $originalOrders, $orderValues) {
                $ids->mapWithKeys(
                    fn($id, $index) => [$id => $orderValues->get($index)]
                )
                    ->filter(
                        fn($newOrderValue, $id) => $originalOrders[$id] !== $newOrderValue
                    )
                    ->each(function ($newOrderValue, $id) {
                        Vehicle::query()
                            ->where('id', $id)
                            ->update(['order' => $newOrderValue]);
                    });
            });

            return response('ok');
        } catch (\Throwable $e) {
            Log::error('Failed to save vehicle order', [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
                'tenant' => tenant('id'),
            ]);

            return response()->json([
                'status' => 'error',
                'message' => 'Failed to save the new order. Please try again.',
            ], 500);
        }
    }
}
