<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\SocialAccount;
use App\Models\User;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Laravel\Socialite\Contracts\User as SocialiteUser;
use Laravel\Socialite\Facades\Socialite;
use Throwable;

class OAuthController extends Controller
{
    /** Providers enabled for OAuth login. */
    private const ALLOWED_PROVIDERS = ['google', 'discord'];

    /**
     * Redirect the user to the provider's OAuth page.
     */
    public function redirectToProvider(string $provider): RedirectResponse
    {
        $this->abortIfInvalidProvider($provider);

        return Socialite::driver($provider)->redirect();
    }

    /**
     * Handle the provider callback and authenticate the user.
     */
    public function handleProviderCallback(string $provider): RedirectResponse
    {
        $this->abortIfInvalidProvider($provider);

        try {
            /** @var SocialiteUser $socialUser */
            $socialUser = Socialite::driver($provider)->user();
        } catch (Throwable) {
            return redirect()->route('login')
                ->withErrors(['oauth' => 'Authentication failed. Please try again.']);
        }

        $providerId   = (string) $socialUser->getId();
        $providerEmail = $this->resolveEmail($socialUser);
        $verified      = $this->isEmailVerifiedByProvider($provider, $socialUser);

        // ── 1. Provider account already linked → login ───────────────────────
        $existing = SocialAccount::query()
            ->where('provider', $provider)
            ->where('provider_id', $providerId)
            ->with('user')
            ->first();

        if ($existing !== null && $existing->user !== null) {
            return $this->loginAndRedirect($existing->user);
        }

        // ── 2. Email match → link to existing account ────────────────────────
        //    Covers both verified and unverified users: if the OAuth provider
        //    has confirmed this email we can safely link it and mark it verified,
        //    preventing a duplicate-email insert when the user had started
        //    registration via email but never finished verification.
        if ($providerEmail !== null && $verified) {
            $userByEmail = User::query()
                ->where('email', strtolower($providerEmail))
                ->first();

            if ($userByEmail !== null) {
                // If their email was not yet verified, promote it now — the
                // OAuth provider has already verified it on our behalf.
                if ($userByEmail->email_verified_at === null) {
                    $userByEmail->forceFill([
                        'email_verified_at' => now(),
                        'is_active'         => true,
                        // Keep their onboarding step unless already complete
                        'onboarding_step'   => $userByEmail->onboarding_step === 'email'
                            ? 'username'
                            : ($userByEmail->onboarding_step ?? 'username'),
                    ])->save();
                }

                $this->createSocialAccount($userByEmail, $provider, $providerId, $providerEmail, $socialUser->getAvatar());

                return $this->loginAndRedirect($userByEmail);
            }
        }

        // ── 3. Provider email not verified → reject auto-link ────────────────
        if ($providerEmail !== null && ! $verified) {
            return redirect()->route('login')
                ->withErrors(['oauth' => 'Your ' . ucfirst($provider) . ' email is not verified. Please verify it and try again.']);
        }

        // ── 4. No email at all → cannot proceed ──────────────────────────────
        if ($providerEmail === null) {
            return redirect()->route('login')
                ->withErrors(['oauth' => 'Could not retrieve your email address from ' . ucfirst($provider) . '. Please register with email.']);
        }

        // ── 5. New user creation ──────────────────────────────────────────────
        try {
            $user = $this->createOAuthUser($socialUser, $provider, $providerId, $providerEmail);
        } catch (UniqueConstraintViolationException) {
            // Race condition: another request inserted the same email between
            // the lookup above and this insert. Fetch and link instead.
            $user = User::query()->where('email', strtolower($providerEmail))->first();

            if ($user === null) {
                return redirect()->route('login')
                    ->withErrors(['oauth' => 'An error occurred during sign in. Please try again.']);
            }

            $this->createSocialAccount($user, $provider, $providerId, $providerEmail, $socialUser->getAvatar());
        }

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

    // ─── Private helpers ─────────────────────────────────────────────────────

    private function abortIfInvalidProvider(string $provider): void
    {
        abort_unless(in_array($provider, self::ALLOWED_PROVIDERS, true), 404);
    }

    /**
     * Create social_accounts row linked to a user.
     */
    private function createSocialAccount(
        User $user,
        string $provider,
        string $providerId,
        ?string $providerEmail,
        ?string $avatar
    ): void {
        SocialAccount::query()->updateOrCreate(
            ['provider' => $provider, 'provider_id' => $providerId],
            [
                'user_id'        => $user->id,
                'provider_email' => $providerEmail !== null ? strtolower($providerEmail) : null,
                'avatar'         => $avatar,
            ]
        );
    }

    /**
     * Create a brand-new user from OAuth data.
     */
    private function createOAuthUser(
        SocialiteUser $socialUser,
        string $provider,
        string $providerId,
        string $providerEmail
    ): User {
        $user = DB::transaction(function () use ($socialUser, $provider, $providerId, $providerEmail): User {
            $name = $this->resolveDisplayName($socialUser, $providerEmail);

            $user = User::query()->create([
                'username'                    => null,
                'name'                        => $name,
                'email'                       => strtolower($providerEmail),
                'email_verified_at'           => now(),
                'password'                    => Hash::make(Str::random(64)),
                'is_active'                   => true,
                'onboarding_step'             => 'username',
                'username_changed_at'         => now(),
                'last_username_change_at'     => now(),
            ]);

            $this->createSocialAccount(
                $user,
                $provider,
                $providerId,
                $providerEmail,
                $socialUser->getAvatar()
            );

            return $user;
        });

        return $user;
    }

    /**
     * Login the user and redirect appropriately.
     */
    private function loginAndRedirect(User $user): RedirectResponse
    {
        Auth::login($user, remember: true);

        request()->session()->regenerate();

        $step = strtolower((string) ($user->onboarding_step ?? ''));

        if (in_array($step, ['username', 'password'], true)) {
            return redirect()->route('setup.username.create');
        }

        return redirect()->intended(route('dashboard', absolute: false));
    }

    /**
     * Resolve a usable display name from the social user.
     */
    private function resolveDisplayName(SocialiteUser $socialUser, string $email): string
    {
        $name = trim((string) ($socialUser->getName() ?? ''));

        if ($name !== '') {
            return $name;
        }

        return Str::before($email, '@');
    }

    /**
     * Best-effort email resolution. Apple can return null email on repeat logins.
     */
    private function resolveEmail(SocialiteUser $socialUser): ?string
    {
        $email = $socialUser->getEmail();

        if ($email === null || $email === '') {
            return null;
        }

        return strtolower(trim($email));
    }

    /**
     * Determine whether the provider has verified the user's email.
     *
     * - Google: returns email_verified flag in raw data
     * - Discord: returns verified flag in raw data
     * - Apple: only issues tokens for verified Apple IDs
     */
    private function isEmailVerifiedByProvider(string $provider, SocialiteUser $socialUser): bool
    {
        $raw = (array) ($socialUser->getRaw() ?? []);

        return match ($provider) {
            'google'  => filter_var($raw['email_verified'] ?? false, FILTER_VALIDATE_BOOLEAN),
            'discord' => (bool) ($raw['verified'] ?? false),
            'apple'   => true, // Apple only issues tokens for verified Apple IDs
            default   => false,
        };
    }
}
