<?php

namespace App\Services;

use App\Enums\UserTypeEnum;
use App\Models\Address;
use App\Models\Client;
use App\Models\PersonalAccessToken;
use App\Models\Provider;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class AuthService
{
    public function __construct(
        private readonly EmailService $emailService,
    ) {}

    public function login(string $email, string $password): ?array
    {
        $user = User::where('email', $email)
            ->whereIn('type', [UserTypeEnum::ADMIN->value, UserTypeEnum::USER->value])
            ->first();

        if (! $user || ! $user->password || ! Hash::check($password, $user->password)) {
            return null;
        }

        $deviceId = Str::uuid()->toString();

        $accessToken  = $user->createAccessToken($deviceId);
        $refreshToken = $user->createRefreshToken($deviceId);

        return [
            'payload' => [
                'access_token' => $accessToken,
                'user'         => $user,
            ],

            'refreshToken' => $refreshToken,
        ];
    }

    public function refresh(string $refreshToken, bool $isApp = false): ?array
    {
        if (! $refreshToken) {
            return null;
        }

        $tokenModel = PersonalAccessToken::findToken($refreshToken);

        if (
            ! $tokenModel ||
            ! in_array('refresh', $tokenModel->abilities) ||
            $tokenModel->expires_at < now()
        ) {
            return null;
        }

        $user = $tokenModel->tokenable;

        if (! $user) {
            return null;
        }

        $deviceId = Str::afterLast($tokenModel->name, '_');

        $tokens = $this->refreshTokenTransaction($tokenModel, $user, $deviceId, $isApp);

        return [
            'payload' => [
                'access_token' => data_get($tokens, 'access_token'),
                'user'         => $user,
            ],

            'refreshToken' => data_get($tokens, 'refresh_token'),
        ];
    }

    public function logout(): void
    {
        $user = Auth::user();

        if (! $user) {
            return;
        }

        $tokenName = $user->currentAccessToken()->name;

        $deviceId = Str::afterLast($tokenName, '_');

        $user->tokens()->where('name', 'like', "%_{$deviceId}")->delete();
    }

    //

    protected function refreshTokenTransaction(
        PersonalAccessToken $tokenModel,
        User                $user,
        string              $deviceId,
        bool                $isApp = false,
    ): array {
        return DB::transaction(function () use (
            $tokenModel,
            $user,
            $deviceId,
            $isApp,
        ): array {
            $tokenModel->update(['expires_at' => Carbon::now()]);

            $accessToken = $isApp
                ? $user->createAccessTokenApp($deviceId)
                : $user->createAccessToken($deviceId);

            $refreshToken = $isApp
                ? $user->createRefreshTokenApp($deviceId)
                : $user->createRefreshToken($deviceId);

            return [
                'access_token'  => $accessToken,
                'refresh_token' => $refreshToken,
            ];
        });
    }

    //

    public function clientSendCode(array $data): bool|array|null
    {
        try {
            DB::beginTransaction();

            $code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);

            $user = User::where(function ($query) use ($data) {
                $query->when(! empty(data_get($data, 'email')), function ($q) use ($data) {
                    $q->where('email', data_get($data, 'email'));
                })->when(! empty(data_get($data, 'phone')), function ($q) use ($data) {
                    $q->where('phone', data_get($data, 'phone'));
                });
            })->where('type', UserTypeEnum::CLIENT->value)->first();

            $isLogin = false;

            if ($user) {
                $user->code = $code;

                $user->validated_code = false;

                $user->save();

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

                $hasAddress = $client && Address::where('source', 'client')
                    ->where('source_id', $client->id)
                    ->exists();

                $isLogin = $client && $hasAddress;
            } else {
                $user = new User;

                $user->fill($data);

                $user->code = $code;
                $user->name = data_get($data, 'name', 'Usuário');
                $user->type = data_get($data, 'type', UserTypeEnum::CLIENT->value);

                $user->save();

                Client::create(['user_id' => $user->id]);
            }

            if (! empty(data_get($data, 'email'))) {
                $this->emailService->sendVerificationCode(
                    email:         data_get($data, 'email'),
                    code:          $code,
                    recipientName: data_get($data, 'name', ''),
                );
            } elseif (! empty(data_get($data, 'phone'))) {
                Log::info('SMS: envio de código por telefone ainda não implementado.', [
                    'phone' => data_get($data, 'phone'),
                ]);
            }

            DB::commit();

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

            Log::error('Erro ao enviar código de verificação.', [
                'error' => $e->getMessage(),
                'data'  => $data,
            ]);

            return false;
        }
    }

    public function providerSendCode(array $data): bool|array|null
    {
        try {
            DB::beginTransaction();

            $code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);

            $user = User::where(function ($query) use ($data) {
                $query->when(! empty(data_get($data, 'email')), function ($q) use ($data) {
                    $q->where('email', data_get($data, 'email'));
                })->when(! empty(data_get($data, 'phone')), function ($q) use ($data) {
                    $q->where('phone', data_get($data, 'phone'));
                });
            })->where('type', UserTypeEnum::PROVIDER->value)->first();

            $isLogin = false;

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

                $isLogin = $provider !== null;

                $user->code           = $code;
                $user->validated_code = false;

                $user->save();
            } else {
                $user = new User;

                $user->fill($data);

                $user->code = $code;
                $user->name = data_get($data, 'name', 'Usuário');
                $user->type = data_get($data, 'type', UserTypeEnum::PROVIDER->value);

                $user->save();
            }

            if (! empty(data_get($data, 'email'))) {
                $this->emailService->sendVerificationCode(
                    email: data_get($data, 'email'),
                    code: $code,
                    recipientName: data_get($data, 'name', ''),
                );
            } elseif (! empty(data_get($data, 'phone'))) {
                Log::info('SMS: envio de código por telefone ainda não implementado.', [
                    'phone' => data_get($data, 'phone'),
                ]);
            }

            DB::commit();

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

            Log::error('Erro ao enviar código de verificação.', [
                'error' => $e->getMessage(),
                'data'  => $data,
            ]);

            return false;
        }
    }

    //

    public function validateCodeClient(array $data, bool $isLogin): bool|array
    {
        $email = data_get($data, 'email');
        $phone = data_get($data, 'phone');
        $code  = data_get($data, 'code', '');

        $user = User::where(function ($query) use ($email, $phone) {
            $query->when($email, fn ($q) => $q->where('email', $email))
                ->when($phone, fn ($q) => $q->where('phone', $phone));
        })->where('type', UserTypeEnum::CLIENT->value)->where('code', $code)->first();

        if (! $user) {
            return false;
        }

        if ($isLogin) {
            $user->load('client');

            $client = $user->client;

            $hasAddress = $client && Address::where('source', 'client')
                ->where('source_id', $client->id)
                ->exists();

            if (! $hasAddress) {
                return ['error' => 'registration_incomplete'];
            }

            return $this->loginWithEmail($user->email, $code, UserTypeEnum::CLIENT->value);
        }

        return true;
    }

    public function validateCodeProvider(array $data, bool $isLogin): bool|array
    {
        $email = data_get($data, 'email');
        $phone = data_get($data, 'phone');
        $code  = data_get($data, 'code', '');

        $user = User::where(function ($query) use ($email, $phone) {
            $query->when($email, fn ($q) => $q->where('email', $email))
                ->when($phone, fn ($q) => $q->where('phone', $phone));
        })->where('type', UserTypeEnum::PROVIDER->value)->where('code', $code)->first();

        if (! $user) {
            return false;
        }

        if ($isLogin) {
            $user->load('provider');

            $provider = $user->provider ?? null;

            if (! $provider) {
                return ['error' => 'registration_incomplete'];
            }

            return $this->loginWithEmail($user->email, $code, UserTypeEnum::PROVIDER->value);
        }

        return true;
    }

    //

    public function validateCode(array $data, bool $isLogin): bool|array
    {
        $email = data_get($data, 'email');
        $phone = data_get($data, 'phone');
        $code  = data_get($data, 'code', '');

        $user = User::where(function ($query) use ($email, $phone) {
            $query->when($email, function ($q) use ($email) {
                $q->where('email', $email);
            })->when($phone, function ($q) use ($phone) {
                $q->where('phone', $phone);
            });
        })->where('code', $code)->first();

        if (! $user) {
            return false;
        }

        if ($isLogin) {
            $resultLogin = $this->loginWithEmail($user->email, $code, $user->type->value);

            return $resultLogin;
        }

        return true;
    }

    public function loginWithEmail(string $email, string $code, ?string $type = null): ?array
    {
        $user = User::where('email', $email)
            ->where('code', $code)
            ->when($type, fn ($q) => $q->where('type', $type))
            ->first();

        if (! $user) {
            return null;
        }

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

        $user->save();

        return $this->createAppSession($user);
    }

    //

    public function createAppSession(User $user): array
    {
        $user->loadMissing(['provider.profileMedia', 'provider.primaryBankAccount', 'client.profileMedia']);

        $deviceId = Str::uuid()->toString();

        $accessToken  = $user->createAccessTokenApp($deviceId);
        $refreshToken = $user->createRefreshTokenApp($deviceId);

        return [
            'payload' => [
                'access_token' => $accessToken,
                'user'         => $user,
            ],

            'refreshToken' => $refreshToken,
        ];
    }
}
