<?php

namespace App\Services;

use App\Enums\ApprovalStatusEnum;
use App\Enums\UserTypeEnum;
use App\Models\Address;
use App\Models\City;
use App\Models\DeviceToken;
use App\Models\Provider;
use App\Models\ProviderServicesType;
use App\Models\ProviderWorkingDay;
use App\Models\PushNotificationLog;
use App\Models\State;
use App\Models\User;
use App\Notifications\Push\Prestador\Transacional\CadastroAprovadoPush;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\UploadedFile;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class ProviderService
{
    public function __construct(
        private readonly AuthService $authService,
        private readonly ProviderBankAccountService $providerBankAccountService,
        private readonly MediaService $mediaService,
        private readonly EmailService $emailService,
    ) {}

    public function getAll(): Collection
    {
        $providers = Provider::query()
            ->with(['user', 'profileMedia'])
            ->join('users', 'providers.user_id', '=', 'users.id')
            ->select('providers.*')
            ->orderBy('users.name', 'asc')
            ->get();

        return $providers;
    }

    public function findById(int $id): ?Provider
    {
        return Provider::with(['user', 'profileMedia', 'documentFrontMedia', 'documentBackMedia'])->find($id);
    }

    public function create(array $data): Provider
    {
        return DB::transaction(function () use ($data) {
            $provider = Provider::create($data);

            if (! empty(data_get($data, 'recipient_name'))) {
                $this->providerBankAccountService->createPrimaryFromRecipientPayload($provider, $data);
            }

            return $provider->fresh(['user', 'profileMedia']);
        });
    }

    public function update(int $id, array $data): ?Provider
    {
        $model = $this->findById($id);

        if (! $model) {
            return null;
        }

        $wasAccepted = $model->approval_status === ApprovalStatusEnum::ACCEPTED;

        if (data_get($data, 'avatar') !== null && data_get($data, 'avatar') instanceof UploadedFile) {
            $media = $this->mediaService->replaceFile(
                newFile:  data_get($data, 'avatar'),
                folder:   "provider/avatar/{$model->id}",
                source:   'provider',
                sourceId: $model->id,
                old:      $model->profileMedia,
            );

            $data['profile_media_id'] = $media->id;

            unset($data['avatar']);
        }

        $model->update($data);

        $provider = $model->fresh(['user', 'profileMedia']);

        if (! $wasAccepted && $provider->approval_status === ApprovalStatusEnum::ACCEPTED) {
            $this->notifyApproved($provider);
        }

        return $provider;
    }

    public function delete(int $id): bool
    {
        $model = $this->findById($id);

        if (! $model) {
            return false;
        }

        return $model->delete();
    }

    //

    public function getPending(int $page = 1, int $perPage = 10): LengthAwarePaginator
    {
        return Provider::query()
            ->where('approval_status', ApprovalStatusEnum::PENDING->value)
            ->with(['user', 'profileMedia'])
            ->orderBy('created_at', 'asc')
            ->paginate($perPage, ['*'], 'page', $page);
    }

    //

    public function register(array $data): array
    {
        try {
            DB::beginTransaction();

            $email = data_get($data, 'email');
            $phone = data_get($data, 'phone');
            $code  = data_get($data, 'code');

            $user = User::query()
                ->where('type', UserTypeEnum::PROVIDER->value)
                ->where('code', $code)
                ->where(function ($query) use ($email, $phone) {
                    if (! empty($email)) {
                        $query->orWhere('email', $email);
                    }

                    if (! empty($phone)) {
                        $query->orWhere('phone', $phone);
                    }
                })
                ->latest('id')
                ->first();

            if (! $user) {
                throw new \Exception(__('messages.user_not_found_or_code_not_validated'));
            }

            $user->name = data_get($data, 'name');

            if (empty($user->email) && ! empty($email)) {
                $user->email = $email;
            }

            if (empty($user->phone) && ! empty($phone)) {
                $user->phone = $phone;
            }

            $user->save();

            $provider = Provider::withTrashed()->where('user_id', $user->id)->first();

            if (! $provider) {
                $provider = new Provider;

                $provider->user_id = $user->id;
            } elseif ($provider->trashed()) {
                $provider->restore();
            }

            $provider->rg              = data_get($data, 'rg');
            $provider->document        = $this->sanitizeDigits(data_get($data, 'document'));
            $provider->birth_date      = data_get($data, 'birth_date');
            $provider->gender          = data_get($data, 'gender');
            $provider->daily_price_8h  = data_get($data, 'daily_price_8h');
            $provider->daily_price_6h  = data_get($data, 'daily_price_6h');
            $provider->daily_price_4h  = data_get($data, 'daily_price_4h');
            $provider->daily_price_2h  = data_get($data, 'daily_price_2h');
            $provider->approval_status = ApprovalStatusEnum::PENDING->value;

            $provider->save();

            $provider->refresh();

            $provider->load('profileMedia', 'documentFrontMedia', 'documentBackMedia');

            $selfie = $this->mediaService->replaceFile(
                newFile:  data_get($data, 'selfie'),
                folder:   "provider/avatar/{$provider->id}",
                source:   'provider',
                sourceId: $provider->id,
                old:      $provider->profileMedia,
            );

            $provider->profile_media_id = $selfie->id;

            $front = $this->mediaService->replaceFile(
                newFile:  data_get($data, 'document_front'),
                folder:   "provider/documentos/{$provider->id}",
                source:   'provider_document',
                sourceId: $provider->id,
                old:      $provider->documentFrontMedia,
                filename: 'frente.'.data_get($data, 'document_front')->getClientOriginalExtension(),
            );

            $provider->document_front_media_id = $front->id;

            $back = $this->mediaService->replaceFile(
                newFile:  data_get($data, 'document_back'),
                folder:   "provider/documentos/{$provider->id}",
                source:   'provider_document',
                sourceId: $provider->id,
                old:      $provider->documentBackMedia,
                filename: 'verso.'.data_get($data, 'document_back')->getClientOriginalExtension(),
            );

            $provider->document_back_media_id = $back->id;

            $provider->save();

            Address::where('source', 'provider')->where('source_id', $provider->id)->delete();

            $this->createProviderAddress($provider->id, $data);

            if (! empty(data_get($data, 'recipient_name'))) {
                $this->providerBankAccountService->createPrimaryFromRecipientPayload($provider, $data);
            }

            ProviderServicesType::where('provider_id', $provider->id)->delete();

            $this->createProviderServicesTypes($provider->id, $data);

            ProviderWorkingDay::where('provider_id', $provider->id)->delete();

            $this->createProviderWorkingDays($provider->id, $data);

            if ((empty($user->email) && empty($user->phone)) || empty($user->code)) {
                throw new \Exception(__('messages.user_not_found_or_code_not_validated'));
            }

            $user->registration_complete = true;

            $user->validated_code = true;
            $user->code           = null;

            $user->save();

            $result = $this->authService->createAppSession($user);

            DB::commit();

            return $result;
        } catch (\Exception $e) {
            DB::rollBack();

            Log::error('Erro ao cadastrar prestador: '.$e->getMessage(), [
                'data' => $data,
            ]);

            throw $e;
        }
    }

    //

    public function approve(int $id): Provider
    {
        [$provider, $wasAccepted] = DB::transaction(function () use ($id) {
            $provider = Provider::query()->lockForUpdate()->findOrFail($id);

            $wasAccepted = $provider->approval_status === ApprovalStatusEnum::ACCEPTED;

            $provider->update([
                'approval_status' => ApprovalStatusEnum::ACCEPTED->value,
                'selfie_verified' => true,
            ]);

            return [$provider->fresh(['user', 'profileMedia']), $wasAccepted];
        });

        if (! $wasAccepted) {
            $this->notifyApproved($provider);
        }

        return $provider;
    }

    public function reject(int $id): Provider
    {
        return DB::transaction(function () use ($id) {
            $provider = Provider::findOrFail($id);

            $provider->update(['approval_status' => ApprovalStatusEnum::REJECTED->value]);

            return $provider->fresh(['user', 'profileMedia']);
        });
    }

    //

    private function createProviderAddress(int $providerId, array $data): void
    {
        $state = null;
        $city  = null;

        if (! empty(data_get($data, 'state'))) {
            $state = State::query()
                ->whereRaw('LOWER(code) = ?', [mb_strtolower(data_get($data, 'state'))])
                ->first();
        }

        if (! empty(data_get($data, 'city'))) {
            $cityQuery = City::query()
                ->whereRaw('LOWER(name) = ?', [mb_strtolower(data_get($data, 'city'))]);

            if ($state) {
                $cityQuery->where('state_id', $state->id);
            }

            $city = $cityQuery->first();
        }

        $address = new Address;

        $address->source    = 'provider';
        $address->source_id = $providerId;
        $address->zip_code  = $this->sanitizeDigits(data_get($data, 'zip_code'));

        $address->address  = data_get($data, 'address');
        $address->number   = data_get($data, 'number');
        $address->district = data_get($data, 'district');

        $address->has_complement = (bool) data_get($data, 'has_complement', false);

        $address->complement   = data_get($data, 'complement');
        $address->nickname     = data_get($data, 'nickname');
        $address->instructions = data_get($data, 'instructions');
        $address->address_type = data_get($data, 'address_type', 'home');

        $address->state_id = $state?->id;
        $address->city_id  = $city?->id;

        $address->latitude  = data_get($data, 'latitude');
        $address->longitude = data_get($data, 'longitude');

        $address->save();
    }

    private function createProviderServicesTypes(int $providerId, array $data): void
    {
        $serviceTypeIds = data_get($data, 'services_types_ids', data_get($data, 'service_types_ids', []));

        $uniqueIds = array_values(array_unique(array_map('intval', $serviceTypeIds)));

        foreach ($uniqueIds as $serviceTypeId) {
            ProviderServicesType::create([
                'provider_id'     => $providerId,
                'service_type_id' => $serviceTypeId,
            ]);
        }
    }

    private function createProviderWorkingDays(int $providerId, array $data): void
    {
        $workingDays = data_get($data, 'working_days', []);

        $seen = [];

        foreach ($workingDays as $workingDay) {
            $day = (int) data_get($workingDay, 'day', -1);

            $period = data_get($workingDay, 'period');

            if ($day < 0 || $day > 6 || ! in_array($period, ['morning', 'afternoon'], true)) {
                continue;
            }

            $uniqueKey = $day.'-'.$period;

            if (data_get($seen, $uniqueKey) !== null) {
                continue;
            }

            $seen[$uniqueKey] = true;

            ProviderWorkingDay::create([
                'provider_id' => $providerId,
                'day'         => $day,
                'period'      => $period,
            ]);
        }
    }

    //

    private function sanitizeDigits(?string $value): ?string
    {
        if ($value === null) {
            return null;
        }

        $digits = preg_replace('/\D+/', '', $value);

        return $digits === '' ? null : $digits;
    }

    /**
     * Efeitos colaterais da aprovação do cadastro do prestador.
     * Chamado apenas na transição para ACCEPTED, sempre fora da transação.
     */
    private function notifyApproved(Provider $provider): void
    {
        $this->sendApprovedEmail($provider);
        $this->sendApprovedPush($provider);
    }

    private function sendApprovedEmail(Provider $provider): void
    {
        if (! empty($provider->user?->email)) {
            try {
                $this->emailService->sendProviderApproved(
                    email:         $provider->user->email,
                    recipientName: $provider->user->name ?? '',
                    locale:        $provider->user->language?->value,
                );
            } catch (\Throwable $exception) {
                Log::error('Falha ao enviar e-mail de aprovação do prestador', [
                    'provider_id' => $provider->id,
                    'user_id'     => $provider->user?->id,
                    'email'       => $provider->user?->email,
                    'error'       => $exception->getMessage(),
                ]);
            }

            return;
        }

        Log::warning('E-mail de aprovação do prestador ignorado: usuário não possui e-mail', [
            'provider_id' => $provider->id,
            'user_id'     => $provider->user?->id,
        ]);
    }

    private function sendApprovedPush(Provider $provider): void
    {
        $user = $provider->user;

        if (! $user) {
            Log::warning('Push de aprovação do prestador ignorado: prestador sem usuário', [
                'provider_id' => $provider->id,
            ]);

            return;
        }

        $notification = new CadastroAprovadoPush;

        $alreadySent = PushNotificationLog::query()
            ->where('user_id', $user->id)
            ->where('label', $notification->label())
            ->exists();

        if ($alreadySent) {
            return;
        }

        $hasActiveToken = DeviceToken::query()
            ->where('user_id', $user->id)
            ->where('app_type', $notification->target()->value)
            ->where('active', true)
            ->exists();

        if (! $hasActiveToken) {
            Log::warning('Push de aprovação do prestador ignorado: usuário não possui device token ativo', [
                'provider_id' => $provider->id,
                'user_id'     => $user->id,
            ]);

            return;
        }

        try {
            app(PushNotificationService::class)->sendToUser($user, $notification);
        } catch (\Throwable $exception) {
            Log::error('Falha ao enviar push de aprovação do prestador', [
                'provider_id' => $provider->id,
                'user_id'     => $user->id,
                'error'       => $exception->getMessage(),
            ]);
        }
    }
}
