import {
  filter,
  map,
  Observable,
  take
} from 'rxjs';

import { UsersActions } from '../../../../store/users/users.actions';
import type { User } from '../../../../shared-kernel';

export const DEFAULT_POST_AUTH_URL = '/dashboard';
export const AUTH_MODE_AUTHORIZE = 'authorize';

const AUTH_ROUTE_PATHS = new Set(['/login', '/register']);
const MAX_RETURN_URL_DEPTH = 10;

export type AuthenticationOutcome =
  | { kind: 'success'; user: User }
  | { kind: 'failure'; error: string };

export function isAuthRoutePath(path: string): boolean {
  return AUTH_ROUTE_PATHS.has(path);
}

export function getRoutePathFromUrl(url: string): string {
  if (!url) {
    return '/';
  }

  const [path] = url.split(/[?#]/, 1);

  return path || '/';
}

export function extractReturnUrlParam(url: string): string | null {
  const queryStart = url.indexOf('?');

  if (queryStart === -1) {
    return null;
  }

  const hashStart = url.indexOf('#', queryStart + 1);
  const query = hashStart === -1
    ? url.slice(queryStart + 1)
    : url.slice(queryStart + 1, hashStart);

  return new URLSearchParams(query).get('returnUrl');
}

export function resolveSafeReturnUrl(
  url: string | null | undefined,
  fallback = DEFAULT_POST_AUTH_URL
): string {
  let candidate = url?.trim() ?? '';
  let depth = 0;

  while (candidate && depth < MAX_RETURN_URL_DEPTH) {
    if (!candidate.startsWith('/') || candidate.startsWith('//')) {
      return fallback;
    }

    const path = getRoutePathFromUrl(candidate);

    if (!isAuthRoutePath(path)) {
      return candidate;
    }

    const nestedReturnUrl = extractReturnUrlParam(candidate)?.trim();

    if (!nestedReturnUrl) {
      return fallback;
    }

    candidate = nestedReturnUrl;
    depth += 1;
  }

  return fallback;
}

export function buildLoginReturnQueryParams(
  currentUrl: string,
  fallback = DEFAULT_POST_AUTH_URL,
  extra: Record<string, string | undefined> = {}
): Record<string, string> {
  const safeReturnUrl = resolveSafeReturnUrl(currentUrl, fallback);
  const queryParams: Record<string, string> = {};

  if (safeReturnUrl !== fallback) {
    queryParams['returnUrl'] = safeReturnUrl;
  }

  for (const [key, value] of Object.entries(extra)) {
    if (value?.trim()) {
      queryParams[key] = value.trim();
    }
  }

  return queryParams;
}

export function isAuthorizeAuthMode(mode: string | null | undefined): boolean {
  return mode?.trim() === AUTH_MODE_AUTHORIZE;
}

export function waitForAuthenticationOutcome(
  actions$: Observable<{ type: string; user?: User; error?: string }>
): Observable<AuthenticationOutcome> {
  return actions$.pipe(
    filter((action) =>
      action.type === UsersActions.setCurrentUser.type
      || action.type === UsersActions.loadCurrentUserFailure.type
    ),
    take(1),
    map((action) => {
      if (action.type === UsersActions.loadCurrentUserFailure.type) {
        return {
          kind: 'failure' as const,
          error: action.error || 'Authentication failed'
        };
      }

      if (!action.user) {
        return {
          kind: 'failure' as const,
          error: 'Authentication failed'
        };
      }

      return {
        kind: 'success' as const,
        user: action.user
      };
    })
  );
}
