/* eslint-disable @typescript-eslint/member-ordering */
import {
  Injectable,
  signal,
  computed
} from '@angular/core';

/** Default timeout (ms) for the NTP-style HTTP sync request. */
const DEFAULT_SYNC_TIMEOUT_MS = 5000;

/**
 * Maintains a clock-offset between the local system time and the
 * remote signaling server.
 *
 * The offset is estimated using a simple NTP-style round-trip
 * measurement and is stored as a reactive Angular signal so that
 * any dependent computed value auto-updates when a new sync occurs.
 */
@Injectable({ providedIn: 'root' })
export class TimeSyncService {
  /**
   * Internal offset signal:
   * `serverTime = Date.now() + offset`.
   */
  private readonly _offset = signal<number>(0);

  /** Epoch timestamp of the most recent successful sync. */
  private lastSyncTimestamp = 0;

  /** Reactive read-only offset (milliseconds). */
  readonly offset = computed(() => this._offset());

  /**
   * Return a server-adjusted "now" timestamp.
   *
   * @returns Epoch milliseconds aligned to the server clock.
   */
  now(): number {
    return Date.now() + this._offset();
  }

  /**
   * Set the offset from a known server timestamp.
   *
   * @param serverTime - Epoch timestamp reported by the server.
   * @param receiveTimestamp - Local epoch timestamp when the server time was
   *   observed. Defaults to `Date.now()` if omitted.
   */
  setFromServerTime(serverTime: number, receiveTimestamp?: number): void {
    const observedAt = receiveTimestamp ?? Date.now();

    this._offset.set(serverTime - observedAt);
    this.lastSyncTimestamp = Date.now();
  }

  /**
   * Perform an HTTP-based clock synchronisation using a simple
   * NTP-style round-trip.
   *
   * 1. Record `clientSendTime` (`t0`).
   * 2. Fetch `GET {baseApiUrl}/time`.
   * 3. Record `clientReceiveTime` (`t2`).
   * 4. Estimate one-way latency as `(t2 − t0) / 2`.
   * 5. Compute offset: `serverNow − midpoint(t0, t2)`.
   *
   * Any network or parsing error is silently ignored so that the
   * last known offset (or zero) is retained.
   *
   * @param baseApiUrl - API base URL (e.g. `http://host:3001/api`).
   * @param timeoutMs  - Maximum time to wait for the response.
   */
  async syncWithEndpoint(
    baseApiUrl: string,
    timeoutMs: number = DEFAULT_SYNC_TIMEOUT_MS
  ): Promise<void> {
    try {
      const controller = new AbortController();
      const clientSendTime = Date.now();
      const timer = setTimeout(() => controller.abort(), timeoutMs);
      const response = await fetch(`${baseApiUrl}/time`, {
        signal: controller.signal
      });
      const clientReceiveTime = Date.now();

      clearTimeout(timer);

      if (!response.ok)
        return;

      const data = await response.json();
      const serverNow = Number(data?.now) || Date.now();
      const midpoint = (clientSendTime + clientReceiveTime) / 2;

      this._offset.set(serverNow - midpoint);
      this.lastSyncTimestamp = Date.now();
    } catch {
      // Sync failure is non-fatal; retain the previous offset.
    }
  }
}
