"use strict";

ChromeUtils.defineModuleGetter(
  this,
  "ctypes",
  "resource://gre/modules/ctypes.jsm"
);

add_task(async function() {
  let migrator = await MigrationUtils.getMigrator("ie");
  // Sanity check for the source.
  Assert.ok(await migrator.isSourceAvailable());

  // Windows versions newer than 1709 don't store cookies as files anymore,
  // thus our migrators don't import anything and this test is pointless.
  // In these versions the CookD folder contains a deprecated.cookie file.
  let deprecatedCookie = Services.dirsvc.get("CookD", Ci.nsIFile);
  deprecatedCookie.append("deprecated.cookie");
  if (deprecatedCookie.exists()) {
    return;
  }

  const BOOL = ctypes.bool;
  const LPCTSTR = ctypes.char16_t.ptr;
  const DWORD = ctypes.uint32_t;
  const LPDWORD = DWORD.ptr;

  let wininet = ctypes.open("Wininet");

  /*
  BOOL InternetSetCookieW(
    _In_  LPCTSTR lpszUrl,
    _In_  LPCTSTR lpszCookieName,
    _In_  LPCTSTR lpszCookieData
  );
  */
  // NOTE: Even though MSDN documentation does not indicate a calling convention,
  // InternetSetCookieW is declared in SDK headers as __stdcall but is exported
  // from wininet.dll without name mangling, so it is effectively winapi_abi
  let setIECookie = wininet.declare(
    "InternetSetCookieW",
    ctypes.winapi_abi,
    BOOL,
    LPCTSTR,
    LPCTSTR,
    LPCTSTR
  );

  /*
  BOOL InternetGetCookieW(
    _In_    LPCTSTR lpszUrl,
    _In_    LPCTSTR lpszCookieName,
    _Out_   LPCTSTR  lpszCookieData,
    _Inout_ LPDWORD lpdwSize
  );
  */
  // NOTE: Even though MSDN documentation does not indicate a calling convention,
  // InternetGetCookieW is declared in SDK headers as __stdcall but is exported
  // from wininet.dll without name mangling, so it is effectively winapi_abi
  let getIECookie = wininet.declare(
    "InternetGetCookieW",
    ctypes.winapi_abi,
    BOOL,
    LPCTSTR,
    LPCTSTR,
    LPCTSTR,
    LPDWORD
  );

  // We need to randomize the cookie to avoid clashing with other cookies
  // that might have been set by previous tests and not properly cleared.
  let date = new Date().getDate();
  const COOKIE = {
    get host() {
      return new URL(this.href).host;
    },
    href: `http://mycookietest.${Math.random()}.com`,
    name: "testcookie",
    value: "testvalue",
    expiry: new Date(new Date().setDate(date + 2)),
  };
  let data = ctypes.char16_t.array()(256);
  let sizeRef = DWORD(256).address();

  registerCleanupFunction(() => {
    // Remove the cookie.
    try {
      let expired = new Date(new Date().setDate(date - 2));
      let rv = setIECookie(
        COOKIE.href,
        COOKIE.name,
        `; expires=${expired.toUTCString()}`
      );
      Assert.ok(rv, "Expired the IE cookie");
      Assert.ok(
        !getIECookie(COOKIE.href, COOKIE.name, data, sizeRef),
        "The cookie has been properly removed"
      );
    } catch (ex) {}

    // Close the library.
    try {
      wininet.close();
    } catch (ex) {}
  });

  // Create the persistent cookie in IE.
  let value = `${COOKIE.value}; expires=${COOKIE.expiry.toUTCString()}`;
  let rv = setIECookie(COOKIE.href, COOKIE.name, value);
  Assert.ok(rv, "Added a persistent IE cookie: " + value);

  // Sanity check the cookie has been created.
  Assert.ok(
    getIECookie(COOKIE.href, COOKIE.name, data, sizeRef),
    "Found the added persistent IE cookie"
  );
  info("Found cookie: " + data.readString());
  Assert.equal(
    data.readString(),
    `${COOKIE.name}=${COOKIE.value}`,
    "Found the expected cookie"
  );

  // Sanity check that there are no cookies.
  Assert.equal(
    Services.cookies.countCookiesFromHost(COOKIE.host),
    0,
    "There are no cookies initially"
  );

  // Migrate cookies.
  await promiseMigration(migrator, MigrationUtils.resourceTypes.COOKIES);

  Assert.equal(
    Services.cookies.countCookiesFromHost(COOKIE.host),
    1,
    "Migrated the expected number of cookies"
  );

  // Now check the cookie details.
  let cookies = Services.cookies.getCookiesFromHost(COOKIE.host, {});
  Assert.ok(cookies.length);
  let foundCookie = cookies[0];

  Assert.equal(foundCookie.name, COOKIE.name);
  Assert.equal(foundCookie.value, COOKIE.value);
  Assert.equal(foundCookie.host, "." + COOKIE.host);
  Assert.equal(foundCookie.expiry, Math.floor(COOKIE.expiry / 1000));
});
