import { setCookie } from 'hono/cookie';
import { DEFAULT_COOKIE_EXPIRATION } from '@redocly/realm/dist/shared/constants.js';
import { JWT_SECRET_KEY } from '@redocly/realm/dist/server/constants.js';
import * as jwt from '@redocly/realm/dist/server/web-server/jwt/jwt.js';

import type { Store } from '@redocly/realm/dist/server/store.js';
import type { Logger } from '@redocly/realm/dist/server/utils/reporter/logger.js';
import type { Context } from 'hono';
import type { GraviteeSsoConfig } from './config.js';

import { getPathPrefix, withPathPrefix } from '@redocly/theme/core/utils';

import { isGraviteeSsoConfig } from './is-gravitee-sso-config.js';
import { GraviteeSsoError, LOGIN_PAGE_SLUG } from './constants.js';

export default async function (ctx: Context, store: Store): Promise<Response> {
  const logger = ctx.get('logger') as Logger;

  const parsedUrl = new URL(ctx.req.url);

  const idpId = parsedUrl.searchParams.get('idpId');
  const redirectTo = parsedUrl.searchParams.get('redirectTo') || '/';
  const loginPagePathname =
    parsedUrl.searchParams.get('loginPagePathname') || withPathPrefix(LOGIN_PAGE_SLUG);

  if (!idpId) {
    logger.error('Gravitee SSO: idpId query parameter not found');
    return ctx.text('Forbidden', 403);
  }

  const createErrorResponse = (error: GraviteeSsoError) => {
    const errorSearchParams = new URLSearchParams({
      error,
      idpId,
      redirectTo,
    });
    const errorRedirectUrl = `${loginPagePathname}?${errorSearchParams}`;

    return ctx.newResponse(null, 302, { Location: errorRedirectUrl });
  };

  const idpConfig = store.getConfig().ssoDirect?.[idpId] as GraviteeSsoConfig | unknown;

  if (!isGraviteeSsoConfig(idpConfig)) {
    logger.error(`Gravitee SSO: invalid idpId in query params: ${idpId}`);
    return ctx.text('Forbidden', 403);
  }

  const formData = await ctx.req.formData();

  const username = formData.get('username');
  const password = formData.get('password');

  if (!username || !password) {
    return createErrorResponse(GraviteeSsoError.INVALID_CREDENTIALS);
  }

  const basicAuthToken = btoa(username + ':' + password);

  const apiUrl = `${idpConfig.apiBaseUrl}/portal/environments/${idpConfig.env || 'DEFAULT'}`;

  try {
    const authRes = await fetch(`${apiUrl}/auth/login`, {
      method: 'POST',
      headers: {
        Authorization: `Basic ${basicAuthToken}`,
      },
    });

    if (authRes.status === 401) {
      logger.verbose('Gravitee SSO callback: invalid username or password');
      return createErrorResponse(GraviteeSsoError.INVALID_CREDENTIALS);
    } else if (authRes.status !== 200) {
      logger.error('Gravitee SSO callback: failed to fetch token from Gravitee API');
      return createErrorResponse(GraviteeSsoError.UNKNOWN_ERROR);
    }

    const userRes = await fetch(`${apiUrl}/user`, {
      method: 'GET',
      headers: {
        Authorization: `Basic ${basicAuthToken}`,
      },
    });

    if (userRes.status !== 200) {
      logger.error('Gravitee SSO callback: failed to fetch user information from Gravitee API');
      return createErrorResponse(GraviteeSsoError.UNKNOWN_ERROR);
    }

    const auth = await authRes.json();
    const user = await userRes.json();

    const userClaims = {
      ...user,
      sub: user.id,
      name: user.display_name || user.email || 'Unknown',
      idpId,
    };

    const encodedToken = await jwt.sign(userClaims, JWT_SECRET_KEY);

    const expirationDate = Date.now() + DEFAULT_COOKIE_EXPIRATION * 1000;

    setCookie(ctx, 'idp_access_token', auth.token || '', {
      path: getPathPrefix() || '/',
      httpOnly: true,
      expires: new Date(expirationDate),
    });

    const c1 = (ctx as any)._headers['set-cookie'];

    setCookie(ctx, 'authorization', encodedToken, {
      path: getPathPrefix() || '/',
      httpOnly: true,
      expires: new Date(expirationDate),
    });

    const resp = ctx.newResponse(null, 302, { Location: withPathPrefix(redirectTo) });

    resp.headers.append('set-cookie', c1);
    return resp;
  } catch (error) {
    logger.error(`Gravitee sso callback: ${error.message}`);
    return createErrorResponse(GraviteeSsoError.UNKNOWN_ERROR);
  }
}
