import { type VerifyResult } from '@frontmcp/auth';

import {
  isOrchestratedMode,
  isOrchestratedRemote,
  isPublicMode,
  isTransparentMode,
  type AuthOptions,
} from '../../types';
import { urlToSafeId } from '../../utils';
import { type ServerRequest } from '../server.interface';

/**
 * Convert URL to a safe provider ID (hostname with dots replaced)
 * Matches the logic in auth-provider-detection.ts for consistency
 */
function urlToProviderId(url: string): string {
  try {
    const parsed = new URL(url);
    return parsed.hostname.replace(/\./g, '_');
  } catch {
    return url.replace(/[^a-zA-Z0-9]/g, '_');
  }
}

/**
 * Base class for primary auth provider.
 * Used for easy access current auth context by dependency injection.
 *
 * In tool context, you can access current auth context by calling:
 *  this.get(FrontMcpAuth) | this.get(Auth)
 *
 * Or in session scoped Providers in constructor arguments:
 *  constructor(private readonly auth: FrontMcpAuth) {
 *  // auth.fetch('MY endpoint ')
 *  }
 */
export abstract class FrontMcpAuth<Options extends AuthOptions = AuthOptions> {
  ready: Promise<void>;
  readonly options: Options;
  readonly id: string;

  constructor(options: Options) {
    this.options = options;
    this.id = this.deriveId(options);
  }

  /**
   * Derive the provider ID from options.
   * This logic MUST match deriveProviderId in auth-provider-detection.ts
   * to ensure consistent provider IDs across the system.
   */
  private deriveId(options: AuthOptions): string {
    if (isPublicMode(options)) {
      return options.issuer ?? 'public';
    }

    if (isTransparentMode(options)) {
      return options.providerConfig?.id ?? urlToSafeId(options.provider);
    }

    if (isOrchestratedMode(options)) {
      if (isOrchestratedRemote(options)) {
        return options.providerConfig?.id ?? urlToSafeId(options.provider);
      }
      // Local mode – match detection defaults
      return options.local?.issuer ?? 'local';
    }

    return 'unknown';
  }

  abstract fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;

  abstract validate(request: ServerRequest): Promise<void>;

  abstract get issuer(): string;

  /**
   * Cryptographically verify a gateway-issued access token (public/local/remote
   * "gateway" modes — i.e. NOT transparent). Gateway tokens are signed by this
   * instance, so the instance is the only place that holds the verification key.
   *
   * The base implementation rejects everything: only auth instances that
   * actually mint gateway tokens (i.e. {@link LocalPrimaryAuth}) override this
   * with a real signature + expiration check. Transparent mode never calls this
   * — it verifies against the upstream provider's JWKS instead.
   *
   * @param _token          The raw bearer token (already confirmed to be a JWT).
   * @param _expectedIssuer The request-derived base URL. Provided for context /
   *                        logging; issuer equality is intentionally NOT enforced
   *                        (proxy/tunnel setups legitimately present `iss` ≠
   *                        baseUrl). Signature + expiration are the security core.
   */
  verifyGatewayToken(_token: string, _expectedIssuer: string): Promise<VerifyResult> {
    return Promise.resolve({
      ok: false,
      error: 'gateway token verification is not supported by this auth mode',
    });
  }
}

export { FrontMcpAuth as Auth };
