import { Box, Button, Icon, Icons, Spinner, Text, color, config } from 'folds';
import React, { useCallback, useEffect } from 'react';
import { createClient } from 'matrix-js-sdk';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { setFallbackSession } from '../../../state/sessions';
import { completeAuthorizationCodeGrant } from './oidcLoginUtil';
import { getOidcCallbackUrl } from './oidcConfig';
import { parseOidcCallbackParams } from './oidcState';

/**
 * Exchange the authorization code for a Matrix session and persist it. The SDK
 * (oidc-client-ts) reads the transient PKCE/state it stored in sessionStorage
 * before the redirect and validates `state` (CSRF). `redirectUri` is the
 * deterministic callback URL, recomputed here.
 */
const completeOidcLogin = async (code: string, state: string): Promise<void> => {
  const { tokenResponse, homeserverUrl, oidcClientSettings, idTokenClaims } =
    await completeAuthorizationCodeGrant(code, state);

  // Derive Matrix user_id + device_id from the freshly-issued access token (the
  // device is owned by the authentication service under MSC3861).
  const tmp = createClient({ baseUrl: homeserverUrl, accessToken: tokenResponse.access_token });
  const { user_id: userId, device_id: deviceId } = await tmp.whoami();
  if (!userId || !deviceId) {
    throw new Error('Homeserver did not return a user/device for the OIDC session.');
  }

  setFallbackSession(tokenResponse.access_token, deviceId, userId, homeserverUrl, {
    refreshToken: tokenResponse.refresh_token,
    expiresInMs: tokenResponse.expires_in ? tokenResponse.expires_in * 1000 : undefined,
    oidc: {
      issuer: oidcClientSettings.issuer,
      clientId: oidcClientSettings.clientId,
      redirectUri: getOidcCallbackUrl(),
      idTokenClaims: idTokenClaims as unknown as Record<string, unknown>,
    },
  });
};

function CallbackError({ message }: { message: string }) {
  return (
    <Box
      direction="Column"
      gap="400"
      alignItems="Center"
      justifyContent="Center"
      style={{ height: '100dvh', padding: config.space.S700, textAlign: 'center' }}
    >
      <Icon size="600" filled src={Icons.Warning} style={{ color: color.Critical.Main }} />
      <Text size="H3">Sign-in failed</Text>
      <Text size="T300" priority="300">
        {message}
      </Text>
      {/* Rendered outside the router, so use a plain navigation to the app root. */}
      <Button variant="Primary" onClick={() => window.location.assign(import.meta.env.BASE_URL)}>
        <Text as="span" size="B400">
          Back to login
        </Text>
      </Button>
    </Box>
  );
}

/**
 * Standalone page mounted by App.tsx for the OIDC redirect path (before the
 * router, since the redirect_uri must be a real non-hash path).
 */
export function OidcCallback() {
  const params = parseOidcCallbackParams(window.location.search);

  const [state, complete] = useAsyncCallback<void, unknown, [string, string]>(
    useCallback(completeOidcLogin, []),
  );

  useEffect(() => {
    if (params.kind === 'success') complete(params.code, params.state);
  }, [params, complete]);

  useEffect(() => {
    // Session persisted — full-page reload at the app root so it boots the
    // authenticated client (works for both hash and browser router configs).
    if (state.status === AsyncStatus.Success) {
      window.location.replace(import.meta.env.BASE_URL);
    }
  }, [state.status]);

  if (params.kind === 'error') {
    return <CallbackError message={params.errorDescription || params.error} />;
  }
  if (params.kind === 'invalid') {
    return (
      <CallbackError message="This sign-in link is invalid or has expired. Please start over." />
    );
  }
  if (state.status === AsyncStatus.Error) {
    return <CallbackError message="Failed to complete sign-in. Please try again." />;
  }

  return (
    <Box
      direction="Column"
      gap="400"
      alignItems="Center"
      justifyContent="Center"
      style={{ height: '100dvh' }}
    >
      <Spinner size="600" variant="Secondary" />
      <Text size="T300" priority="300">
        Signing you in…
      </Text>
    </Box>
  );
}
