<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\Care\TeamMemberProvisioner;
use App\Services\Identity\IdentityTeamClient;
use Illuminate\Http\Client\Response as HttpResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Illuminate\View\View;

/**
 * "Sign in with Ladill" — Authorization Code + PKCE against auth.ladill.com.
 * Establishes a local session + thin user mirror keyed by the OIDC `sub`.
 */
class SsoLoginController extends Controller
{
    /**
     * Max consecutive failed callbacks before we stop bouncing back into the
     * authorize flow and show an error page instead (prevents an infinite
     * /sso/callback ↔ /sso/connect redirect loop on a persistent upstream error).
     */
    private const MAX_SSO_ATTEMPTS = 3;

    public function connect(Request $request, IdentityTeamClient $identity): RedirectResponse|View
    {
        $intended = (string) $request->query('redirect', route('care.dashboard'));

        if (Auth::check()) {
            return $this->safeRedirect($intended, route('care.dashboard'));
        }

        if (! $request->boolean('fallback')) {
            $request->session()->forget('sso.attempts');
        }

        if ($this->attemptSilentRefresh($request, $intended)) {
            app(TeamMemberProvisioner::class)->sync(Auth::user());
            $redirect = $this->resolveLanding($identity, Auth::user(), $intended);

            return $this->safeRedirect($redirect, route('care.dashboard'));
        }

        $verifier = Str::random(64);
        $state = Str::random(40);
        $request->session()->put('sso.verifier', $verifier);
        $request->session()->put('sso.state', $state);
        $request->session()->put('sso.intended', $intended);

        $challenge = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '=');

        $query = [
            'response_type' => 'code',
            'client_id' => (string) config('services.ladill_sso.client_id'),
            'redirect_uri' => (string) config('services.ladill_sso.redirect'),
            'scope' => 'openid profile email',
            'state' => $state,
            'code_challenge' => $challenge,
            'code_challenge_method' => 'S256',
        ];

        $loginHint = (string) $request->session()->get('sso.login_hint', '');
        if ($loginHint !== '') {
            $query['login_hint'] = $loginHint;
        }

        if (! $request->boolean('interactive')) {
            $query['prompt'] = 'none';
        }

        $authorizeUrl = rtrim((string) config('services.ladill_sso.issuer'), '/').'/oauth/authorize?'.http_build_query($query);

        return redirect()->away($authorizeUrl);
    }

    public function callback(Request $request): RedirectResponse
    {
        $intended = (string) $request->session()->get('sso.intended', route('care.dashboard'));

        if ($request->filled('error')) {
            if (in_array($request->query('error'), ['login_required', 'interaction_required', 'consent_required'], true)
                && ! $request->boolean('interactive')) {
                return redirect()->away((string) config('ladill.marketing_url'));
            }

            return $this->finishCallback($request, $intended, (string) $request->query('error_description', $request->query('error')));
        }

        if (! $request->filled('code')
            || $request->query('state') !== $request->session()->pull('sso.state')) {
            return $this->finishCallback($request, $intended, 'invalid_state');
        }

        $issuer = rtrim((string) config('services.ladill_sso.issuer'), '/');

        $tokenRes = Http::asForm()->post($issuer.'/oauth/token', [
            'grant_type' => 'authorization_code',
            'client_id' => (string) config('services.ladill_sso.client_id'),
            'client_secret' => (string) config('services.ladill_sso.client_secret'),
            'redirect_uri' => (string) config('services.ladill_sso.redirect'),
            'code' => (string) $request->query('code'),
            'code_verifier' => (string) $request->session()->pull('sso.verifier'),
        ]);
        if ($tokenRes->failed()) {
            return $this->finishCallback($request, $intended, 'token_exchange_failed');
        }

        $user = $this->loginFromTokenResponse($request, $tokenRes);
        if (! $user) {
            return $this->finishCallback($request, $intended, 'userinfo_failed');
        }

        Auth::login($user, remember: true);
        $request->session()->regenerate();
        $request->session()->forget('sso.attempts');

        app(TeamMemberProvisioner::class)->sync($user);
        app(\App\Services\Care\DemoLoginReseeder::class)->maybeResetAfterLogin($user);

        return $this->finishCallback($request, $intended, null, app(IdentityTeamClient::class));
    }

    public function failed(Request $request): View
    {
        return view('auth.sso-error', [
            'reason' => (string) $request->session()->get('sso.error', ''),
        ]);
    }

    public function logout(Request $request): RedirectResponse
    {
        Auth::logout();
        $request->session()->invalidate();
        $request->session()->regenerateToken();

        // Per-app sign-out: end only this app's session and keep the platform
        // (auth.ladill.com) SSO session alive so "Sign in again" re-auths silently.
        return redirect()->route('care.signed-out');
    }

    /** Platform session ended — clear this app and offer silent sign-in again. */
    public function platformSignedOut(Request $request): RedirectResponse
    {
        Auth::logout();
        $request->session()->invalidate();
        $request->session()->regenerateToken();

        return redirect()->route('sso.connect', [
            'redirect' => (string) $request->query('redirect', ''),
        ]);
    }

    public function logoutBridge(Request $request): View
    {
        $return = $this->safeReturnUrl((string) $request->query('return', ''));
        $hubUrl = 'https://'.config('app.auth_domain').'/logout/sso/hub?'.http_build_query([
            'embedded' => 1,
            'return' => $return,
        ]);

        return view('auth.sso-logout-bridge', [
            'hubUrl' => $hubUrl,
            'return' => $return,
            'authOrigin' => 'https://'.config('app.auth_domain'),
        ]);
    }

    public function frontchannelLogout(Request $request): Response|RedirectResponse
    {
        Auth::logout();
        $request->session()->invalidate();
        $request->session()->regenerateToken();

        $return = (string) $request->query('return', '');
        $root = (string) config('app.platform_domain', 'ladill.com');
        $host = parse_url($return, PHP_URL_HOST);
        if (str_starts_with($return, 'https://') && is_string($host) && ($host === $root || str_ends_with($host, '.'.$root))) {
            return redirect()->away($return);
        }

        return response('', 204);
    }

    private function attemptSilentRefresh(Request $request, string $intended): bool
    {
        $refreshToken = (string) $request->session()->get('sso.refresh_token', '');
        if ($refreshToken === '') {
            return false;
        }

        $issuer = rtrim((string) config('services.ladill_sso.issuer'), '/');
        $tokenRes = Http::asForm()->post($issuer.'/oauth/token', [
            'grant_type' => 'refresh_token',
            'refresh_token' => $refreshToken,
            'client_id' => (string) config('services.ladill_sso.client_id'),
            'client_secret' => (string) config('services.ladill_sso.client_secret'),
            'scope' => 'openid profile email',
        ]);

        if ($tokenRes->failed()) {
            $request->session()->forget('sso.refresh_token');

            return false;
        }

        $user = $this->loginFromTokenResponse($request, $tokenRes);

        if (! $user) {
            return false;
        }

        Auth::login($user, remember: true);
        $request->session()->put('sso.intended', $intended);

        return true;
    }

    private function loginFromTokenResponse(Request $request, HttpResponse $tokenRes): ?User
    {
        $refreshToken = (string) $tokenRes->json('refresh_token', '');
        if ($refreshToken !== '') {
            $request->session()->put('sso.refresh_token', $refreshToken);
        }

        $issuer = rtrim((string) config('services.ladill_sso.issuer'), '/');
        $claims = Http::withToken((string) $tokenRes->json('access_token'))->acceptJson()->get($issuer.'/oauth/userinfo');
        if ($claims->failed() || ! $claims->json('sub')) {
            return null;
        }

        $email = (string) ($claims->json('email') ?: '');
        if ($email !== '') {
            $request->session()->put('sso.login_hint', $email);
        }

        return User::updateOrCreate(
            ['public_id' => (string) $claims->json('sub')],
            [
                'name' => $claims->json('name'),
                'email' => $email !== '' ? $email : (string) $claims->json('sub').'@users.ladill.com',
                'avatar_url' => $claims->json('picture'),
            ],
        );
    }

    private function finishCallback(
        Request $request,
        string $intended,
        ?string $error = null,
        ?IdentityTeamClient $identity = null,
    ): RedirectResponse {
        if ($error) {
            $attempts = (int) $request->session()->get('sso.attempts', 0) + 1;

            if ($attempts >= self::MAX_SSO_ATTEMPTS) {
                $request->session()->forget(['sso.attempts', 'sso.state', 'sso.verifier', 'sso.intended']);
                $request->session()->flash('sso.error', $error);

                return redirect()->route('sso.failed');
            }

            $request->session()->put('sso.attempts', $attempts);

            return redirect()->route('sso.connect', [
                'redirect' => $intended,
                'interactive' => 1,
                'fallback' => 1,
            ]);
        }

        $user = Auth::user();
        $redirect = ($user instanceof User && $identity)
            ? $this->resolveLanding($identity, $user, $intended)
            : $intended;

        return $this->safeRedirect($redirect, route('care.dashboard'));
    }

    private function resolveLanding(IdentityTeamClient $identity, User $user, string $intended): string
    {
        try {
            $access = $identity->appAccess($user->ownerRef(), $intended);
            $member = app(\App\Services\Care\OrganizationResolver::class)->memberFor($user);
            $access = app(\App\Services\Care\CarePermissions::class)
                ->filterStaffAppAccess($member, $access);
            \App\Support\StaffUx::remember($access);

            return $access['url'] !== '' ? $access['url'] : $intended;
        } catch (\Throwable) {
            return $intended;
        }
    }

    private function safeReturnUrl(string $url): string
    {
        $host = parse_url($url, PHP_URL_HOST);
        $root = (string) config('app.platform_domain', 'ladill.com');

        if (is_string($host) && str_starts_with($url, 'https://')
            && ($host === $root || str_ends_with($host, '.'.$root))) {
            return $url;
        }

        return route('care.signed-out');
    }

    private function safeRedirect(string $url, string $fallback): RedirectResponse
    {
        $host = parse_url($url, PHP_URL_HOST);
        $root = (string) config('app.platform_domain', 'ladill.com');

        if (is_string($host) && str_starts_with($url, 'https://')
            && ($host === $root || str_ends_with($host, '.'.$root))) {
            return redirect()->away($url);
        }

        return redirect()->away($fallback);
    }
}
