<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\Settings\SettingsService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
use Laravel\Socialite\Two\InvalidStateException;

class GoogleAuthController extends Controller
{
    public function __construct(private readonly SettingsService $settings) {}

    public function redirect(Request $request): RedirectResponse
    {
        abort_unless($this->isGoogleConfigured(), 503, 'Google login is not configured.');

        $this->configureGoogleService();

        $openerOrigin = $this->safeOrigin($request->query('origin'));

        if ($request->boolean('popup') && filled($openerOrigin)) {
            $request->session()->put('google_oauth_opener_origin', $openerOrigin);
        } else {
            $request->session()->forget('google_oauth_opener_origin');
        }

        if ($this->shouldStartOnConfiguredOrigin($request)) {
            return redirect()->away($this->configuredOriginStartUrl($request, $openerOrigin));
        }

        if ($request->boolean('popup')) {
            $request->session()->put('google_oauth_popup', true);
        } else {
            $request->session()->forget('google_oauth_popup');
        }

        return Socialite::driver('google')->redirect();
    }

    public function callback(Request $request): RedirectResponse|Response
    {
        abort_unless($this->isGoogleConfigured(), 503, 'Google login is not configured.');

        $this->configureGoogleService();

        try {
            $googleUser = Socialite::driver('google')->user();
            $request->session()->forget('google_oauth_state_retry_attempted');
        } catch (InvalidStateException $exception) {
            return $this->handleInvalidState($request, $exception);
        }

        $email = Str::lower((string) $googleUser->getEmail());

        abort_if($email === '', 422, 'Google account email is required.');
        abort_unless($this->emailDomainAllowed($email, $this->settings->get('google.allowed_user_email_domains', [])), 403, 'This email domain is not allowed.');

        $existingUser = User::query()->where('email', $email)->first();

        abort_if(
            ! $existingUser && ! $this->settings->isEnabled('google.auto_create_user_accounts'),
            403,
            'Automatic account creation is disabled.'
        );

        $user = User::query()->firstOrNew(['email' => $email]);

        $user->forceFill([
            'google_id' => (string) $googleUser->getId(),
            'name' => $googleUser->getName() ?: $googleUser->getNickname() ?: $email,
            'email_verified_at' => now(),
            'avatar_url' => $googleUser->getAvatar(),
            'role' => $existingUser?->isAdmin() ? 'admin' : $this->roleFor($email),
            'last_login_at' => now(),
        ]);

        if (! $user->exists || blank($user->password)) {
            $user->password = Str::password(64);
        }

        $user->save();

        Auth::guard('web')->login($user, remember: true);
        request()->session()->regenerate();

        $nextUrl = $this->nextUrlFor($user);

        if ($request->session()->pull('google_oauth_popup', false)) {
            return response()->view('auth.google-popup-callback', [
                'nextUrl' => $nextUrl,
                'targetOrigin' => $request->session()->pull('google_oauth_opener_origin') ?: $request->getSchemeAndHttpHost(),
            ]);
        }

        return redirect()->to($nextUrl);
    }

    public function logout(Request $request): RedirectResponse
    {
        Auth::guard('web')->logout();

        $request->session()->regenerateToken();

        return redirect()->route('landing');
    }

    private function nextUrlFor(User $user): string
    {
        if (! $user->hasCompletedQuestionnaire()) {
            return route('questionnaire.show');
        }

        if (! $user->hasUnlockedAccess()) {
            return route('payment.checkout');
        }

        if (! $user->hasAnalyzedResume()) {
            return route('resume.show');
        }

        if (! $user->hasCompletedProfile()) {
            return route('profile.edit');
        }

        return route('applicant.dashboard');
    }

    private function isGoogleConfigured(): bool
    {
        return $this->settings->isEnabled('google.login_enabled')
            && filled($this->settings->get('google.client_id', config('services.google.client_id')))
            && filled($this->settings->get('google.client_secret', config('services.google.client_secret')))
            && filled($this->settings->get('google.redirect_url', config('services.google.redirect')));
    }

    private function roleFor(string $email): string
    {
        $adminEmails = collect(config('services.admin.emails', []))
            ->map(fn (string $adminEmail): string => Str::lower($adminEmail))
            ->all();

        $adminDomains = collect([
            ...$this->settings->get('google.allowed_admin_email_domains', []),
            ...$this->settings->get('security.allowed_admin_email_domains', []),
        ])
            ->map(fn (string $domain): string => Str::lower(ltrim($domain, '@')))
            ->filter()
            ->all();

        if (in_array($email, $adminEmails, true) || in_array(Str::after($email, '@'), $adminDomains, true)) {
            return 'admin';
        }

        return (string) $this->settings->get('google.default_role_after_signup', 'user');
    }

    private function emailDomainAllowed(string $email, mixed $allowedDomains): bool
    {
        $allowedDomains = collect(is_array($allowedDomains) ? $allowedDomains : [])
            ->map(fn (string $domain): string => Str::lower(ltrim($domain, '@')))
            ->filter()
            ->all();

        return $allowedDomains === [] || in_array(Str::after($email, '@'), $allowedDomains, true);
    }

    private function configureGoogleService(): void
    {
        config([
            'services.google.client_id' => $this->settings->get('google.client_id', config('services.google.client_id')),
            'services.google.client_secret' => $this->settings->get('google.client_secret', config('services.google.client_secret')),
            'services.google.redirect' => $this->settings->get('google.redirect_url', config('services.google.redirect')),
        ]);
    }

    private function handleInvalidState(Request $request, InvalidStateException $exception): RedirectResponse|Response
    {
        Log::warning('Google OAuth state validation failed.', [
            'exception' => $exception::class,
            'request_host' => $request->getSchemeAndHttpHost(),
            'configured_redirect' => config('services.google.redirect'),
            'session_driver' => config('session.driver'),
            'session_domain' => config('session.domain'),
            'session_same_site' => config('session.same_site'),
        ]);

        $retryAttempted = (bool) $request->session()->pull('google_oauth_state_retry_attempted', false);
        $usesPopup = (bool) $request->session()->pull('google_oauth_popup', false);
        $openerOrigin = $request->session()->pull('google_oauth_opener_origin');

        $request->session()->migrate(true);
        $request->session()->regenerateToken();

        if (! $retryAttempted) {
            $request->session()->put('google_oauth_state_retry_attempted', true);

            if ($usesPopup) {
                $request->session()->put('google_oauth_popup', true);
            }

            if ($usesPopup && filled($openerOrigin)) {
                $request->session()->put('google_oauth_opener_origin', $openerOrigin);
            }

            return redirect()
                ->route('auth.google.redirect', array_filter([
                    'popup' => $usesPopup ? 1 : null,
                    'origin' => $openerOrigin,
                ]))
                ->with('status', 'Google sign-in was restarted with a fresh session.');
        }

        if ($usesPopup) {
            return response()->view('auth.google-popup-callback', [
                'nextUrl' => route('landing'),
                'targetOrigin' => $openerOrigin ?: $request->getSchemeAndHttpHost(),
                'errorMessage' => 'Google sign-in could not verify your session. Open the site using the same URL shown in APP_URL, then try again.',
            ], 422);
        }

        return redirect()
            ->route('landing')
            ->withErrors([
                'google_login' => 'Google sign-in could not verify your session. Open the site using the same URL shown in APP_URL, then try again.',
            ]);
    }

    private function shouldStartOnConfiguredOrigin(Request $request): bool
    {
        if ($request->boolean('canonical')) {
            return false;
        }

        $configuredRedirectOrigin = $this->originFromUrl((string) config('services.google.redirect'));

        return filled($configuredRedirectOrigin)
            && ! hash_equals($configuredRedirectOrigin, $request->getSchemeAndHttpHost());
    }

    private function configuredOriginStartUrl(Request $request, ?string $openerOrigin): string
    {
        $configuredRedirectOrigin = $this->originFromUrl((string) config('services.google.redirect'));

        $query = array_filter([
            'popup' => $request->boolean('popup') ? 1 : null,
            'origin' => $openerOrigin,
            'canonical' => 1,
        ]);

        return $configuredRedirectOrigin.route('auth.google.redirect', $query, absolute: false);
    }

    private function originFromUrl(string $url): ?string
    {
        $parts = parse_url($url);

        if (! isset($parts['scheme'], $parts['host'])) {
            return null;
        }

        $port = isset($parts['port']) ? ':'.$parts['port'] : '';

        return $parts['scheme'].'://'.$parts['host'].$port;
    }

    private function safeOrigin(mixed $origin): ?string
    {
        if (! is_string($origin) || blank($origin)) {
            return null;
        }

        return $this->originFromUrl($origin);
    }
}
