import * as plugins from '../../plugins.js';
import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js';

export interface IAuthRequest {
  identity?: interfaces.data.IIdentity;
  apiToken?: string;
}

export interface IAuthRequirement {
  scope?: interfaces.data.TApiTokenScope;
  requireAdminIdentity?: boolean;
  requireAdminToken?: boolean;
}

export interface IAuthContext {
  type: 'identity' | 'apiToken';
  userId: string;
  role?: string;
  isAdmin: boolean;
  scopes: interfaces.data.TApiTokenScope[];
  identity?: interfaces.data.IIdentity;
  token?: interfaces.data.IStoredApiToken;
}

const typedAuthError = (messageArg: string) => {
  return new plugins.typedrequest.TypedResponseError(messageArg);
};

export async function requireOpsAuth(
  opsServerRefArg: OpsServer,
  requestArg: IAuthRequest,
  requirementArg: IAuthRequirement = {},
): Promise<IAuthContext> {
  let identityNeedsAdmin = false;
  let tokenNeedsAdmin = false;
  let tokenNeedsScope = false;

  if (requestArg.identity?.jwt) {
    const identity = await opsServerRefArg.adminHandler.validateIdentity(requestArg.identity);
    if (identity) {
      const isAdmin = identity.role === 'admin';
      if (!requirementArg.requireAdminIdentity || isAdmin) {
        return {
          type: 'identity',
          userId: identity.userId,
          role: identity.role,
          isAdmin,
          scopes: [],
          identity,
        };
      }
      identityNeedsAdmin = true;
    }
  }

  if (requestArg.apiToken) {
    const tokenManager = opsServerRefArg.dcRouterRef.apiTokenManager;
    const token = tokenManager ? await tokenManager.validateToken(requestArg.apiToken) : null;
    if (token) {
      if (requirementArg.requireAdminToken && token.policy?.role !== 'admin') {
        tokenNeedsAdmin = true;
      } else if (requirementArg.scope && !tokenManager!.hasScope(token, requirementArg.scope)) {
        tokenNeedsScope = true;
      } else {
        const scopes = token.policy?.role === 'admin'
          ? ['*' as interfaces.data.TApiTokenScope]
          : Array.from(new Set([...(token.scopes || []), ...(token.policy?.scopes || [])]));
        return {
          type: 'apiToken',
          userId: token.createdBy,
          role: token.policy?.role || 'operator',
          isAdmin: token.policy?.role === 'admin',
          scopes,
          token,
        };
      }
    }
  }

  if (tokenNeedsScope) {
    throw typedAuthError('insufficient scope');
  }
  if (tokenNeedsAdmin) {
    throw typedAuthError('admin API token required');
  }
  if (identityNeedsAdmin) {
    throw typedAuthError('admin identity required');
  }
  throw typedAuthError('unauthorized');
}
