<?php

namespace App\Services;

use App\Enums\GenderEnum;
use App\Models\Address;
use App\Models\Client;
use App\Models\Provider;
use App\Models\ProviderServicesType;
use App\Rules\ScheduleBusinessRules;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;

class SearchService
{
    public function __construct(
        private readonly ZipCodeCoordinatesService $zipCodeCoordinatesService,
    ) {}

    public function buscaPrestadores(?string $name = null, ?string $date = null): array
    {
        $user = Auth::user();

        $cliente = Client::where('user_id', $user->id)->first();

        $blockedProviderIds = ScheduleBusinessRules::getBlockedProviderIdsForClient($cliente->id);

        $providersWithWorkingDays = ScheduleBusinessRules::getProviderIdsWithWorkingDays();

        $clientPrimaryAddress = Address::where('source', 'client')
            ->where('source_id', $cliente->id)
            ->orderByDesc('is_primary')
            ->orderByDesc('id')
            ->first();

        $clientDistanceAddress = $this->addressForDistance($cliente->id, $clientPrimaryAddress);

        $clientCoordinates = $this->zipCodeCoordinatesService->resolve(
            $clientDistanceAddress?->latitude !== null ? (float) $clientDistanceAddress->latitude : null,
            $clientDistanceAddress?->longitude !== null ? (float) $clientDistanceAddress->longitude : null,
            $clientDistanceAddress?->zip_code,
        );

        $distanceSelect = $this->distanceSelect(
            data_get($clientCoordinates, 'latitude'),
            data_get($clientCoordinates, 'longitude'),
        );

        $baseQuery = Provider::leftJoin(
            'users as provider_user',
            'provider_user.id',
            '=',
            'providers.user_id'
        )
            ->visibleToCustomers()
            ->leftJoin(
                DB::raw("
                    (
                        SELECT DISTINCT ON (source_id)
                            *
                        FROM addresses
                        WHERE source = 'provider'
                        AND deleted_at IS NULL
                        ORDER BY
                            source_id,
                            (latitude IS NOT NULL AND longitude IS NOT NULL) DESC,
                            is_primary DESC,
                            id DESC
                    ) AS provider_address
                "),
                'provider_address.source_id',
                '=',
                'providers.id'
            )
            ->whereNotNull('provider_address.id')
            ->whereNotIn('providers.id', $blockedProviderIds)
            ->whereIn('providers.id', $providersWithWorkingDays)
            ->whereNull('providers.deleted_at')
            ->whereNotNull('providers.daily_price_8h')
            ->whereNotNull('providers.daily_price_6h')
            ->whereNotNull('providers.daily_price_4h')
            ->whereNotNull('providers.daily_price_2h')
            ->when($name, fn ($q) => $q->where('provider_user.name', 'ILIKE', "%{$name}%"))
            ->select(
                'providers.id',
                'providers.id as provider_id',
                'providers.profile_media_id',
                'provider_user.name as provider_name',
                'providers.gender',
                'provider_address.zip_code as provider_zip_code',
                'provider_address.district',
                'provider_address.latitude as provider_latitude',
                'provider_address.longitude as provider_longitude',
                'providers.average_rating',
                'providers.total_services',
                'providers.daily_price_8h',
                'providers.daily_price_6h',
                'providers.daily_price_4h',
                'providers.daily_price_2h',
                'providers.created_at',

                DB::raw("
                    (
                        SELECT COUNT(*)
                        FROM reviews
                        LEFT JOIN schedules
                            ON schedules.id = reviews.schedule_id
                        WHERE reviews.origin = 'provider'
                        AND schedules.provider_id = providers.id
                    ) AS total_reviews
                "),

                $distanceSelect,
            )
            ->orderByRaw('distance_km ASC NULLS LAST');

        $providers = (clone $baseQuery)
            ->when(
                $clientPrimaryAddress?->city_id,
                fn ($query, int $cityId) => $query->where('provider_address.city_id', $cityId)
            )
            ->get();

        if ($providers->isEmpty() && $clientPrimaryAddress?->city_id) {
            $providers = $baseQuery->get();
        }

        $this->zipCodeCoordinatesService->preload(
            $providers->whereNull('distance_km')->pluck('provider_zip_code')
        );

        $providers->each(function ($provider) use ($clientDistanceAddress) {
            if ($provider->distance_km !== null) {
                return;
            }

            $provider->distance_km = $this->zipCodeCoordinatesService->calculateDistance(
                $clientDistanceAddress?->latitude !== null ? (float) $clientDistanceAddress->latitude : null,
                $clientDistanceAddress?->longitude !== null ? (float) $clientDistanceAddress->longitude : null,
                $clientDistanceAddress?->zip_code,
                $provider->provider_latitude !== null ? (float) $provider->provider_latitude : null,
                $provider->provider_longitude !== null ? (float) $provider->provider_longitude : null,
                $provider->provider_zip_code,
            );
        });

        $providers = $providers
            ->sortBy(fn ($provider) => $provider->distance_km ?? PHP_FLOAT_MAX)
            ->values();

        $filtered = $providers->when(
            $date,
            fn ($collection) => $collection->whereIn(
                'provider_id',
                ScheduleBusinessRules::getAvailableProviderIdsForDate(
                    $date,
                    $collection->pluck('provider_id')
                )->toArray()
            )->values()
        );

        $filtered->load('profileMedia');

        return $filtered->map(function ($item) {
            $arr = is_array($item) ? $item : $item->toArray();

            $arr['profile_media_url'] = $item->profileMedia?->path
                ? Storage::temporaryUrl($item->profileMedia->path, now()->addMinutes(60))
                : null;

            $arr['gender_label']        = GenderEnum::labelFor(data_get($arr, 'gender'));
            $arr['daily_price_8h_base'] = data_get($arr, 'daily_price_8h');
            $arr['daily_price_6h_base'] = data_get($arr, 'daily_price_6h');
            $arr['daily_price_4h_base'] = data_get($arr, 'daily_price_4h');
            $arr['daily_price_2h_base'] = data_get($arr, 'daily_price_2h');
            $arr['daily_price_8h']      = $this->applyCreditCardFee(data_get($arr, 'daily_price_8h'));
            $arr['daily_price_6h']      = $this->applyCreditCardFee(data_get($arr, 'daily_price_6h'));
            $arr['daily_price_4h']      = $this->applyCreditCardFee(data_get($arr, 'daily_price_4h'));
            $arr['daily_price_2h']      = $this->applyCreditCardFee(data_get($arr, 'daily_price_2h'));

            unset(
                $arr['id'],
                $arr['profile_media_id'],
                $arr['profile_media'],
                $arr['provider_zip_code'],
            );

            return $arr;
        })->values()->toArray();
    }

    public function perfilPrestador(int $providerId): array
    {
        $provider = Provider::query()
            ->visibleToCustomers()
            ->whereNull('providers.deleted_at')
            ->with(['user:id,name', 'profileMedia'])
            ->findOrFail($providerId);

        $address = Address::where('source', 'provider')
            ->where('source_id', $provider->id)
            ->orderByDesc('is_primary')
            ->orderByDesc('id')
            ->first();

        $serviceTypes = ProviderServicesType::query()
            ->join('service_types', 'service_types.id', '=', 'provider_services_types.service_type_id')
            ->where('provider_services_types.provider_id', $provider->id)
            ->whereNull('provider_services_types.deleted_at')
            ->whereNull('service_types.deleted_at')
            ->where('service_types.is_active', true)
            ->orderBy('service_types.description')
            ->get([
                'service_types.id',
                'service_types.description',
            ])
            ->toArray();

        $totalReviews = DB::table('reviews')
            ->leftJoin('schedules', 'schedules.id', '=', 'reviews.schedule_id')
            ->where('reviews.origin', 'provider')
            ->where('schedules.provider_id', $provider->id)
            ->count();

        return [
            'provider_id'         => $provider->id,
            'provider_name'       => $provider->user?->name,
            'district'            => $address?->district,
            'age'                 => $provider->birth_date?->age,
            'gender'              => $provider->gender,
            'gender_label'        => GenderEnum::labelFor($provider->gender),
            'average_rating'      => $provider->average_rating,
            'total_services'      => $provider->total_services,
            'total_reviews'       => $totalReviews,
            'profile_media_url'   => $provider->profileMedia?->path
                ? Storage::temporaryUrl($provider->profileMedia->path, now()->addMinutes(60))
                : null,
            'service_types'       => $serviceTypes,

            'daily_price_8h_base' => $provider->daily_price_8h,
            'daily_price_6h_base' => $provider->daily_price_6h,
            'daily_price_4h_base' => $provider->daily_price_4h,
            'daily_price_2h_base' => $provider->daily_price_2h,
            'daily_price_8h'      => $this->applyCreditCardFee($provider->daily_price_8h),
            'daily_price_6h'      => $this->applyCreditCardFee($provider->daily_price_6h),
            'daily_price_4h'      => $this->applyCreditCardFee($provider->daily_price_4h),
            'daily_price_2h'      => $this->applyCreditCardFee($provider->daily_price_2h),
        ];
    }

    //

    private function applyCreditCardFee(?float $price): ?float
    {
        if ($price === null) {
            return null;
        }

        $rate = $this->platformCreditCardFeeRate();

        return round($price * (1 + $rate), 2);
    }

    private function platformCreditCardFeeRate(): float
    {
        $rate = config('services.pagarme.platform_credit_card_fee_rate', 0.16);

        if (is_string($rate)) {
            $rate = str_replace(',', '.', trim($rate));
        }

        $rate = (float) $rate;

        return $rate > 1 ? $rate / 100 : $rate;
    }

    private function addressForDistance(int $clientId, ?Address $primaryAddress): ?Address
    {
        if ($primaryAddress?->latitude !== null && $primaryAddress?->longitude !== null) {
            return $primaryAddress;
        }

        return Address::where('source', 'client')
            ->where('source_id', $clientId)
            ->whereNotNull('latitude')
            ->whereNotNull('longitude')
            ->orderByDesc('is_primary')
            ->orderByDesc('id')
            ->first() ?? $primaryAddress;
    }

    private function distanceSelect(?float $clientLatitude, ?float $clientLongitude): \Illuminate\Contracts\Database\Query\Expression
    {
        return DistanceService::sqlExpression($clientLatitude, $clientLongitude);
    }
}
