<?php

namespace App\Domains\Auth\Services;

use App\Domains\Auth\Models\User;
use App\Domains\Notification\Events\UserRegistered;
use App\Domains\Tenant\Models\Tenant;
use App\Domains\Tenant\Support\TenantDomainNormalizer;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Laravel\Socialite\Contracts\User as SocialiteUser;
use Laravel\Socialite\Facades\Socialite;

class GoogleAuthService
{
    public function redirect(Request $request): RedirectResponse
    {
        $tenantCode = $request->string('tenant')->toString();
        $returnUrl = $request->string('return_url')->toString();

        $tenant = Tenant::query()->where('codigo', $tenantCode)->first();

        if (! $tenant || ! $this->isTenantReturnUrl($returnUrl, $tenant)) {
            throw ValidationException::withMessages([
                'tenant' => __('api.auth.invalid_tenant_or_return_url'),
            ]);
        }

        $state = (string) Str::uuid();
        Cache::put("google-oauth-context:{$state}", [
            'tenant_codigo' => $tenant->codigo,
            'return_url' => rtrim($returnUrl, '/'),
        ], now()->addMinutes(10));

        return Socialite::driver('google')
            ->scopes(['openid', 'profile', 'email'])
            ->stateless()
            ->with(['state' => $state])
            ->redirect();
    }

    public function callback(Request $request): RedirectResponse
    {
        $state = $request->string('state')->toString();

        /** @var array{tenant_codigo?: string, return_url?: string}|null $context */
        $context = Str::isUuid($state) ? Cache::pull("google-oauth-context:{$state}") : null;

        if (! is_array($context) || ! isset($context['tenant_codigo'], $context['return_url'])) {
            abort(400, __('api.auth.request_expired'));
        }

        $tenant = Tenant::query()->where('codigo', $context['tenant_codigo'])->first();

        if (! $tenant || ! $this->isTenantReturnUrl($context['return_url'], $tenant)) {
            abort(400, __('api.auth.invalid_return_url'));
        }

        /** @var SocialiteUser $googleUser */
        $googleUser = Socialite::driver('google')->stateless()->user();
        $user = $this->resolveUser($googleUser, $tenant);
        $token = $user->createToken(
            'google-oauth',
            ['*'],
            now()->addMinutes((int) config('sanctum.expiration')),
        )->plainTextToken;

        $exchangeCode = (string) Str::uuid();
        Cache::put("google-oauth-exchange:{$exchangeCode}", [
            'user_id' => $user->id,
            'token' => $token,
            'tenant_codigo' => $tenant->codigo,
        ], now()->addMinutes(5));

        return redirect()->to($context['return_url'].'/login?'.http_build_query([
            'oauth_code' => $exchangeCode,
        ]));
    }

    /** @return array{user: User, token: string, tenant_codigo: string} */
    public function exchange(string $exchangeCode, string $tenantCodigo): array
    {
        /** @var array{user_id: int, token: string, tenant_codigo?: string}|null $authentication */
        $authentication = Cache::pull("google-oauth-exchange:{$exchangeCode}");

        if (! $authentication) {
            throw ValidationException::withMessages([
                'oauth_code' => __('api.auth.oauth_code_expired'),
            ]);
        }

        if (($authentication['tenant_codigo'] ?? null) !== $tenantCodigo) {
            throw ValidationException::withMessages([
                'tenant_codigo' => __('api.auth.tenant_mismatch'),
            ]);
        }

        return [
            'user' => User::query()->findOrFail($authentication['user_id']),
            'token' => $authentication['token'],
            'tenant_codigo' => $tenantCodigo,
        ];
    }

    private function resolveUser(SocialiteUser $googleUser, Tenant $tenant): User
    {
        $googleId = $googleUser->getId();
        $email = $googleUser->getEmail();

        if (! is_string($googleId) || $googleId === '' || ! is_string($email) || ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw ValidationException::withMessages([
                'google' => __('api.auth.google_email_required'),
            ]);
        }

        $rawUser = $googleUser instanceof \Laravel\Socialite\Two\User ? $googleUser->getRaw() : [];
        $emailVerified = $rawUser['email_verified'] ?? $rawUser['verified_email'] ?? false;
        if (! in_array($emailVerified, [true, 'true', 1, '1'], true)) {
            throw ValidationException::withMessages([
                'google' => __('api.auth.google_email_unverified'),
            ]);
        }

        $user = User::query()->where('google_id', $googleId)->first();
        if ($user) {
            return $user;
        }

        $user = User::query()->where('email', $email)->first();
        if ($user) {
            $user->forceFill(['google_id' => $googleId])->save();

            return $user;
        }

        $name = $googleUser->getName();
        $user = User::query()->create([
            'nombre_apellido' => is_string($name) && $name !== '' ? $name : $email,
            'email' => $email,
            'email_verified_at' => now(),
            'google_id' => $googleId,
            'password' => Str::password(64),
        ]);

        UserRegistered::dispatch($user, $tenant->codigo);

        return $user;
    }

    private function isTenantReturnUrl(string $returnUrl, Tenant $tenant): bool
    {
        $parts = parse_url($returnUrl);
        if (! is_array($parts)
            || ! isset($parts['scheme'], $parts['host'])
            || isset($parts['user'], $parts['pass'], $parts['query'], $parts['fragment'])) {
            return false;
        }

        $scheme = strtolower($parts['scheme']);
        $host = TenantDomainNormalizer::normalize($parts['host']);
        $tenantDomain = TenantDomainNormalizer::normalize($tenant->dominio);
        $returnPath = TenantDomainNormalizer::normalizePath($parts['path'] ?? '/');

        if ($host === null
            || $tenantDomain === null
            || $host !== $tenantDomain
            || $returnPath !== $tenant->base_path) {
            return false;
        }

        return $scheme === 'https' || ($scheme === 'http' && in_array($host, ['localhost', '127.0.0.1'], true));
    }
}
