<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\Franchisee;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
use Throwable;

class MicrosoftLoginController extends Controller
{
    public function redirect(Request $request): RedirectResponse
    {
        try {
            $intended = $request->query('to', '/');
            session(['auth.intended_url' => $intended]);

            $codeVerifier = Str::random(64);
            session(['microsoft_code_verifier' => $codeVerifier]);

            $codeChallenge = $this->base64UrlEncode(
                hash('sha256', $codeVerifier, true)
            );

            return Socialite::driver('microsoft')
                ->scopes([
                    'openid',
                    'profile',
                    'email',
                    'User.Read',
                ])
                ->with([
                    'code_challenge_method' => 'S256',
                    'code_challenge' => $codeChallenge,
                ])
                ->redirect();
        } catch (Throwable $e) {
            Log::error('Microsoft redirect failed', [
                'message' => $e->getMessage(),
            ]);

            return $this->redirectToLoginWithError('init_failed');
        }
    }

    public function callback(Request $request): RedirectResponse
    {
        try {
            if ($request->filled('error')) {
                Log::warning('Microsoft OAuth denied or failed', [
                    'error' => $request->get('error'),
                    'error_description' => $request->get('error_description'),
                ]);

                return $this->redirectToLoginWithError('oauth_denied');
            }

            $authorizationCode = $request->get('code');

            if (!$authorizationCode) {
                return $this->redirectToLoginWithError('missing_authorization_code');
            }

            $codeVerifier = session('microsoft_code_verifier');

            if (!$codeVerifier || !is_string($codeVerifier)) {
                return $this->redirectToLoginWithError('missing_code_verifier');
            }

            $tenant = trim((string) config('services.microsoft.tenant', 'common'));
            $baseUrl = rtrim((string) config('services.microsoft.microsoft_auth', 'https://login.microsoftonline.com'), '/');

            $tokenResponse = Http::asForm()->post(
                "{$baseUrl}/{$tenant}/oauth2/v2.0/token",
                [
                    'grant_type' => 'authorization_code',
                    'client_id' => (string) config('services.microsoft.client_id'),
                    'client_secret' => (string) config('services.microsoft.client_secret'),
                    'code' => $authorizationCode,
                    'redirect_uri' => route('microsoft.callback'),
                    'code_verifier' => $codeVerifier,
                    'scope' => 'openid profile email User.Read',
                ]
            );

            if ($tokenResponse->failed()) {
                Log::warning('Microsoft token exchange failed', [
                    'status' => $tokenResponse->status(),
                    'body' => $tokenResponse->json(),
                ]);

                return $this->redirectToLoginWithError('token_exchange_failed');
            }

            $accessToken = $tokenResponse->json('access_token');

            if (!$accessToken || !is_string($accessToken)) {
                return $this->redirectToLoginWithError('missing_access_token');
            }

            $microsoftUser = Socialite::driver('microsoft')->userFromToken($accessToken);

            $email = $this->resolveMicrosoftEmail($microsoftUser);

            if (!$email) {
                Log::warning('Microsoft user email missing', [
                    'raw_user' => property_exists($microsoftUser, 'user') ? $microsoftUser->user : null,
                ]);

                return $this->redirectToLoginWithError('email_not_provided');
            }

            $user = User::query()
                ->whereRaw('LOWER(email) = ?', [mb_strtolower($email)])
                ->first();

            if (!$user) {
                return $this->redirectToLoginWithError('user_not_found');
            }

            if (!$user->franchisee_id) {
                return $this->redirectToLoginWithError('account_not_linked');
            }

            $franchisee = Franchisee::find($user->franchisee_id);

            if (!$franchisee || $franchisee->status === 'disabled') {
                return $this->redirectToLoginWithError('account_disabled');
            }

            Auth::login($user, true);
            $request->session()->regenerate();

            $intended = session('auth.intended_url', '/');

            $request->session()->forget([
                'microsoft_code_verifier',
                'auth.intended_url',
            ]);

            return redirect($intended);
        } catch (Throwable $e) {
            Log::error('Microsoft login failed', [
                'message' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
            ]);

            return $this->redirectToLoginWithError('oauth_failed');
        }
    }

    protected function redirectToLoginWithError(string $code): RedirectResponse
    {
        $intended = session('auth.intended_url', '/');

        session()->forget([
            'microsoft_code_verifier',
            'auth.intended_url',
        ]);

        return redirect('/login?microsoft_error=' . urlencode($code) . '&to=' . urlencode($intended));
    }

    protected function resolveMicrosoftEmail($microsoftUser): ?string
    {
        $candidates = [
            method_exists($microsoftUser, 'getEmail') ? $microsoftUser->getEmail() : null,
            method_exists($microsoftUser, 'getNickname') ? $microsoftUser->getNickname() : null,
        ];

        if (property_exists($microsoftUser, 'user') && is_array($microsoftUser->user)) {
            $raw = $microsoftUser->user;

            $candidates[] = $raw['mail'] ?? null;
            $candidates[] = $raw['userPrincipalName'] ?? null;
            $candidates[] = $raw['preferred_username'] ?? null;
        }

        foreach ($candidates as $candidate) {
            if (is_string($candidate)) {
                $candidate = trim($candidate);

                if (filter_var($candidate, FILTER_VALIDATE_EMAIL)) {
                    return mb_strtolower($candidate);
                }
            }
        }

        return null;
    }

    protected function base64UrlEncode(string $value): string
    {
        return rtrim(strtr(base64_encode($value), '+/', '-_'), '=');
    }
}
