// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import type * as Lantern from '../types/types.js';

// A DNS lookup will usually take ~1-2 roundtrips of connection latency plus the extra DNS routing time.
// Example: https://www.webpagetest.org/result/180703_3A_e33ec79747c002ed4d7bcbfc81462203/1/details/#waterfall_view_step1
// Example: https://www.webpagetest.org/result/180707_1M_89673eb633b5d98386de95dfcf9b33d5/1/details/#waterfall_view_step1
// DNS is highly variable though, many times it's a little more than 1, but can easily be 4-5x RTT.
// We'll use 2 since it seems to give the most accurate results on average, but this can be tweaked.
const DNS_RESOLUTION_RTT_MULTIPLIER = 2;

class DNSCache {
  static rttMultiplier = DNS_RESOLUTION_RTT_MULTIPLIER;

  rtt: number;
  resolvedDomainNames: Map<string, {resolvedAt: number}>;

  constructor({rtt}: {rtt: number}) {
    this.rtt = rtt;
    this.resolvedDomainNames = new Map();
  }

  getTimeUntilResolution(
      request: Lantern.NetworkRequest, options?: {requestedAt?: number, shouldUpdateCache?: boolean}): number {
    const {requestedAt = 0, shouldUpdateCache = false} = options || {};

    const domain = request.parsedURL.host;
    const cacheEntry = this.resolvedDomainNames.get(domain);
    let timeUntilResolved = this.rtt * DNSCache.rttMultiplier;
    if (cacheEntry) {
      const timeUntilCachedIsResolved = Math.max(cacheEntry.resolvedAt - requestedAt, 0);
      timeUntilResolved = Math.min(timeUntilCachedIsResolved, timeUntilResolved);
    }

    const resolvedAt = requestedAt + timeUntilResolved;
    if (shouldUpdateCache) {
      this.updateCacheResolvedAtIfNeeded(request, resolvedAt);
    }

    return timeUntilResolved;
  }

  updateCacheResolvedAtIfNeeded(request: Lantern.NetworkRequest, resolvedAt: number): void {
    const domain = request.parsedURL.host;
    const cacheEntry = this.resolvedDomainNames.get(domain) || {resolvedAt};
    cacheEntry.resolvedAt = Math.min(cacheEntry.resolvedAt, resolvedAt);
    this.resolvedDomainNames.set(domain, cacheEntry);
  }

  /**
   * Forcefully sets the DNS resolution time for a request.
   * Useful for testing and alternate execution simulations.
   */
  setResolvedAt(domain: string, resolvedAt: number): void {
    this.resolvedDomainNames.set(domain, {resolvedAt});
  }
}

export {DNSCache};
