/**
 * Shaka Player DRM Configuration
 */
import shaka from "shaka-player";
import { DEFAULT_BANDWIDTH_ESTIMATE } from "../constants/bandwidth.js";
import { Logger } from "../utils/logger.js";
import {
  buildSafeEndpoint,
  buildSafeHeaderFlags,
  createLocalizedError,
  ERROR_MESSAGE_KEYS,
} from "../helpers/errorMessages.js";

// A certificate request that never receives *any* response (server hung,
// connection dropped silently, corporate proxy black-holing the request,
// etc.) would otherwise leave this fetch() pending forever: no error, no
// license request, playback just silently never starts.
const DRM_REQUEST_TIMEOUT_MS = 15000;

async function fetchWithTimeout(url, timeoutMs = DRM_REQUEST_TIMEOUT_MS) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    return await fetch(url, { signal: controller.signal });
  } catch (error) {
    if (error?.name === "AbortError") {
      const timeoutError = new Error(`Request timed out after ${timeoutMs}ms`);
      timeoutError.isTimeout = true;
      throw timeoutError;
    }
    throw error;
  } finally {
    clearTimeout(timer);
  }
}

export async function setupShakaDRM(shakaPlayer, drmConfig = {}, logger = null) {
  // Keep the caller's prefix but force the "drm" debug category so all DRM
  // logs are toggled together.
  const log = new Logger(logger?.getPrefix?.() || "Shaka-DRM", "drm");

  if (!shakaPlayer || !drmConfig) {
    log.warn("Shaka DRM setup requires player and drmConfig");
    return;
  }

  // setupShakaDRM() runs again on every loadSource() call that reuses the
  // same underlying shaka.Player instance (see shaka.js setSrc()). Without
  // clearing previous filters first, each call would ADD another copy of
  // the request/response filters below on top of the ones already
  // registered by the previous load, so headers get injected N times and
  // (for FairPlay) the base64 response filter runs N times, corrupting the
  // response after the second load. Filters/config are fully rebuilt below,
  // so clearing first is always safe.
  try {
    shakaPlayer.getNetworkingEngine()?.clearAllRequestFilters();
    shakaPlayer.getNetworkingEngine()?.clearAllResponseFilters();
  } catch (error) {
    log.warn("Could not clear previous Shaka networking filters:", error);
  }

  const config = {
    drm: {
      servers: {},
      // retryParameters: {
      //   maxAttempts: 3,
      //   baseDelay: 1000,
      //   backoffFactor: 2,
      //   fuzzFactor: 0.1,
      //   timeout: 30000,
      // },
    },
    // streaming: {
    //   bufferBehind: 30,
    //   bufferingGoal: 10,
    //   rebufferingGoal: 2,
    //   bufferingGoalDefault: 10,
    //   rebufferingGoalDefault: 2,
    //   bufferBehindDefault: 30,
    // },
    // abr: {
    //   enabled: true,
    //   defaultBandwidthEstimate: DEFAULT_BANDWIDTH_ESTIMATE,
    //   useNetworkInformation: true,
    // },
  };

  // Widevine
  if (
    drmConfig.widevine &&
    drmConfig.widevine.url &&
    drmConfig.widevine.licenseHeaders
  ) {
    config.drm.servers["com.widevine.alpha"] = drmConfig.widevine.url;
    log.info("Widevine license server configured", {
      endpoint: buildSafeEndpoint(drmConfig.widevine.url),
      ...buildSafeHeaderFlags(drmConfig.widevine.licenseHeaders),
    });

    // if (drmConfig.widevine.audioContentType) {
    //   config.drm["com.widevine.alpha"] = {
    //     audioContentType: drmConfig.widevine.audioContentType,
    //     videoContentType: drmConfig.widevine_0.videoContentType,
    //   };
    // }
  }

  let req, cert;

  // FairPlay
  const requestFairplay = Boolean(
    drmConfig.fairplay &&
      drmConfig.fairplay.certificateUrl &&
      drmConfig.fairplay.url &&
      drmConfig.fairplay.licenseHeaders
  );

  if (requestFairplay) {
    const certificateEndpoint = buildSafeEndpoint(
      drmConfig.fairplay.certificateUrl ??
        "https://lic.staging.drmtoday.com/license-server-fairplay/cert/orca"
    );

    try {
      req = await fetchWithTimeout(
        drmConfig.fairplay.certificateUrl ??
          "https://lic.staging.drmtoday.com/license-server-fairplay/cert/orca"
      );

      if (!req.ok) {
        throw new Error(`FairPlay certificate request failed with HTTP ${req.status}`);
      }

      cert = await req.arrayBuffer();

      config.drm.servers["com.apple.fps"] = drmConfig.fairplay.url;
      config.drm.advanced = {
        "com.apple.fps": {
          serverCertificate: new Uint8Array(cert),
        },
      };
      log.info("FairPlay license server configured", {
        endpoint: buildSafeEndpoint(drmConfig.fairplay.url),
        certificateEndpoint,
        certificateStatus: req.status,
        ...buildSafeHeaderFlags(drmConfig.fairplay.licenseHeaders),
      });
    } catch (error) {
      log.error("Error fetching FairPlay certificate:", {
        certificateEndpoint,
        error: error?.message,
      });
      // Without a certificate, Shaka has no usable "com.apple.fps" server
      // entry at all: FairPlay playback cannot proceed (Shaka would later
      // fail with the much less obvious SERVER_CERTIFICATE_REQUIRED/6015,
      // or simply never attempt a license request). Propagate so the
      // caller (Shaka tech's setSrc) reports a classified, actionable
      // error instead of silently continuing without FairPlay configured.
      throw createLocalizedError(
        error?.isTimeout
          ? ERROR_MESSAGE_KEYS.drmRequestTimeout
          : ERROR_MESSAGE_KEYS.drmLicenseRequestFailed,
        {
          drmContext: { drmStage: "fairplay-certificate", licenseEndpoint: certificateEndpoint },
        }
      );
    }
  }

  // playready
  if (
    drmConfig.playready &&
    drmConfig.playready.url &&
    drmConfig.playready.licenseHeaders
  ) {
    config.drm.servers["com.microsoft.playready"] = drmConfig.playready.url;
    log.info("PlayReady license server configured", {
      endpoint: buildSafeEndpoint(drmConfig.playready.url),
      ...buildSafeHeaderFlags(drmConfig.playready.licenseHeaders),
    });
  }

  // isDRM=true but none of widevine/fairplay/playready in drmConfig had the
  // required fields (e.g. the caller only supplied "fairplay" while playback
  // is happening on a non-WebKit engine that can't use it — see the Windows
  // FairPlay-misconfiguration hypothesis investigated for this stream).
  // Without this check, Shaka loads with zero configured license servers:
  // EME finds no usable key system for the manifest's DRM info, and either
  // fails with a much-less-obvious error deep inside Shaka, or (if content
  // has mixed clear/encrypted segments) simply never decodes anything with
  // no error at all.
  if (Object.keys(config.drm.servers).length === 0) {
    log.error("No usable DRM key systems were built from drmConfig; refusing to configure Shaka without DRM servers.");
    throw createLocalizedError(ERROR_MESSAGE_KEYS.drmConfigMissing, {
      drmContext: { drmStage: "config-missing" },
    });
  }

  // Apply configuration
  shakaPlayer.configure(config);

  shakaPlayer
    .getNetworkingEngine()
    .registerRequestFilter(function (type, request) {
      if (type == shaka.net.NetworkingEngine.RequestType.LICENSE) {
        const upfrontToken = drmConfig?.widevine?.licenseHeaders["x-dt-auth-token"] ??
        drmConfig?.fairplay?.licenseHeaders["x-dt-auth-token"] ??
        drmConfig?.playready?.licenseHeaders["x-dt-auth-token"];
        if (upfrontToken) {
          request.headers["x-dt-auth-token"] = upfrontToken;
        }
        const customData = drmConfig?.widevine?.licenseHeaders["x-dt-custom-data"] ??
          drmConfig?.fairplay?.licenseHeaders["x-dt-custom-data"] ??
          drmConfig?.playready?.licenseHeaders["x-dt-custom-data"];
        if (customData) {
          request.headers["x-dt-custom-data"] = customData;
        }
      }
    });

  if (requestFairplay) {
    shakaPlayer
      .getNetworkingEngine()
      .registerResponseFilter((type, response) => {
        if (type === shaka.net.NetworkingEngine.RequestType.LICENSE) {
          if (shakaPlayer.keySystem() === "com.apple.fps") {
            let responseText = shaka.util.StringUtils.fromUTF8(response.data);
            response.data =
              shaka.util.Uint8ArrayUtils.fromBase64(responseText).buffer;
          }
        }
      });
  }

  // Error events are already handled by Shaka.prototype._bindShakaErrorEvents()
  // in shaka.js (which also classifies DRM errors and calls
  // reportPlayerError()); no separate listener is registered here to avoid
  // duplicate "error" listeners piling up across repeated loadSource() calls
  // on the same shaka.Player instance.

  log.info("Shaka DRM configured successfully");
}

/**
 * Adaptive streaming
 */
export function setupAdaptiveStreaming(shakaPlayer, streamingConfig = {}) {
  const config = {
    streaming: {
      bufferBehind: 30,
      bufferingGoal: 10,
      rebufferingGoal: 2,
      ...streamingConfig,
    },
    abr: {
      enabled: true,
      defaultBandwidthEstimate: DEFAULT_BANDWIDTH_ESTIMATE,
      useNetworkInformation: true,
      ...streamingConfig.abr,
    },
    manifest: {
      dash: {
        clockSyncUri: "",
        ignoreDrmInfo: false,
        ...streamingConfig.dash,
      },
      hls: {
        ...streamingConfig.hls,
      },
    },
  };

  shakaPlayer.configure(config);
}

/**
 * Bitrate management
 */
export function setupBitrateManagement(shakaPlayer, bitrateConfig = {}) {
  const config = {
    abr: {
      enabled: bitrateConfig.enabled !== false,
      defaultBandwidthEstimate:
        bitrateConfig.defaultBandwidth ?? DEFAULT_BANDWIDTH_ESTIMATE,
      useNetworkInformation: bitrateConfig.useNetworkInfo !== false,
      restrictions: {
        minWidth: bitrateConfig.minWidth,
        maxWidth: bitrateConfig.maxWidth,
        minPixels: bitrateConfig.minPixels,
        maxPixels: bitrateConfig.maxPixels,
        minFrameRate: bitrateConfig.minFrameRate,
        maxFrameRate: bitrateConfig.maxFrameRate,
        minBandwidth: bitrateConfig.minBandwidth,
        maxBandwidth: bitrateConfig.maxBandwidth,
      },
    },
  };

  shakaPlayer.configure(config);
}

/**
 * Caption/subtitle
 */
export function setupTextTracks(shakaPlayer, textConfig = {}) {
  if (textConfig.preferredLanguages) {
    shakaPlayer.configure({
      preferredAudioLanguage: textConfig.preferredLanguages.audio || "en",
      preferredTextLanguage: textConfig.preferredLanguages.text || "en",
    });
  }

  if (textConfig.tryStartWithClosedCaptions) {
    shakaPlayer.configure({
      textTrackRestrictions: {
        onlyForcedSubtitles: false,
      },
    });
  }
}

export default {
  setupShakaDRM,
  setupAdaptiveStreaming,
  setupBitrateManagement,
  setupTextTracks,
};
