// META: script=/resources/testdriver.js
// META: script=/resources/testdriver-vendor.js
// META: script=/common/utils.js
// META: script=resources/fledge-util.sub.js
// META: timeout=long

"use strict";

// Runs one auction at a time using `auctionConfigOverrides` until the auction
// has a winner.
async function runAuctionsUntilWinner(test, uuid, auctionConfigOverrides) {
  let fencedFrameConfig = null;
  while (!fencedFrameConfig) {
    fencedFrameConfig =
        await runBasicFledgeAuction(test, uuid, auctionConfigOverrides);
  }
  return fencedFrameConfig;
}

// This tests the case of ties. The winner of an auction is normally checked
// by these tests by checking a report sent when the winner is loaded in a fenced
// frame. Unfortunately, that requires a lot of navigations, which can be slow.
//
// So instead, run a multi-seller auction. The inner auction has two bidders,
// which both bid, and the seller gives them the same score. For the first
// auction, the top-level seller just accepts the only bid it sees, and then
// as usual, we navigate a fenced frame, to learn which bidder won.
//
// The for subsequent auctions, the nested component auction is identical,
// but the top-level auction rejects bids from the bidder that won the
// first auction. So if we have a winner, we know that the other bidder
// won the tie. Auctions are run in parallel until this happens.
//
// The interest groups use "group-by-origin" execution mode, to potentially
// allow the auctions run in parallel to complete faster.
promise_test(async test => {
  const uuid = generateUuid(test);

  // Use different report URLs for each interest group, to identify
  // which interest group has won an auction.
  let reportURLs = [createBidderReportURL(uuid, /*id=*/'1'),
                    createBidderReportURL(uuid, /*id=*/'2')];

  // Use different ad URLs for each auction. These need to be distinct
  // so that the top-level seller can check the URL to check if the
  // winning bid from the component auction has already won an
  // auction.
  let adURLs = [createRenderURL(uuid),
                createRenderURL(uuid, /*script=*/';')];

  await Promise.all(
      [ joinInterestGroup(
          test, uuid,
          { name: 'group 1',
            ads: [{ renderURL: adURLs[0] }],
            executionMode: 'group-by-origin',
            biddingLogicURL: createBiddingScriptURL(
                { allowComponentAuction: true,
                  reportWin: `sendReportTo("${reportURLs[0]}");`})}),
        joinInterestGroup(
          test, uuid,
          { name: 'group 2',
            ads: [{ renderURL: adURLs[1] }],
            executionMode: 'group-by-origin',
            biddingLogicURL: createBiddingScriptURL(
                { allowComponentAuction: true,
                  reportWin: `sendReportTo("${reportURLs[1]}");`})})
      ]
  );

  let componentAuctionConfig = {
      seller: window.location.origin,
      decisionLogicURL: createDecisionScriptURL(uuid),
      interestGroupBuyers: [window.location.origin]
  };

  let auctionConfigOverrides = {
    decisionLogicURL: createDecisionScriptURL(uuid),
    interestGroupBuyers: [],
    componentAuctions: [componentAuctionConfig]
  };

  await runBasicFledgeAuctionAndNavigate(test, uuid, auctionConfigOverrides);

  // Waiting for the report URL of the winner should succeed, while waiting for
  // the one of the loser should throw. Wait for both, see which succeeds, and
  // set "winningAdURL" to the ad URL of the winner.
  let winningAdURL = '';
  try {
    await waitForObservedRequests(uuid, [reportURLs[0]]);
    winningAdURL = adURLs[0];
  } catch (e) {
    await waitForObservedRequests(uuid, [reportURLs[1]]);
    winningAdURL = adURLs[1];
  }

  // Modify `auctionConfigOverrides` to only accept the ad from the interest
  // group that didn't win the first auction.
  auctionConfigOverrides.decisionLogicURL =
      createDecisionScriptURL(
        uuid,
        {scoreAd: `if (browserSignals.renderURL === "${winningAdURL}")
                     return 0;`});

  // Add an abort controller, so can cancel extra auctions.
  let abortController = new AbortController();
  auctionConfigOverrides.signal = abortController.signal;

  // Run a bunch of auctions in parallel, until one has a winner.
  let fencedFrameConfig = await Promise.any(
    [ runAuctionsUntilWinner(test, uuid, auctionConfigOverrides),
      runAuctionsUntilWinner(test, uuid, auctionConfigOverrides),
      runAuctionsUntilWinner(test, uuid, auctionConfigOverrides),
      runAuctionsUntilWinner(test, uuid, auctionConfigOverrides),
      runAuctionsUntilWinner(test, uuid, auctionConfigOverrides),
      runAuctionsUntilWinner(test, uuid, auctionConfigOverrides)
    ]
  );
  // Abort the other auctions.
  abortController.abort('reason');

  // Load the fencedFrameConfig in a fenced frame, and double-check that each
  // interest group has won once.
  createAndNavigateFencedFrame(test, fencedFrameConfig);
  await waitForObservedRequests(uuid, [reportURLs[0], reportURLs[1]]);
}, 'runAdAuction tie.');
