import { blockUrls, commonJSHandlers, commons, rules } from "./rules.js";
// Vars
let initialized = false;
let cachedRules = {};
let tabList = {};
const xmlTabs = {};
let lastDeclarativeNetRuleId = 1;
let settings = { statusIndicators: true, whitelistedDomains: {} };
const isManifestV3 = chrome.runtime.getManifest().manifest_version == 3;

// Badges
function setBadge(tabId, text) {
  const chromeAction = chrome?.browserAction ?? chrome?.action;

  if (!chromeAction || !settings.statusIndicators) return;

  chromeAction.setBadgeText({ text: text || "", tabId: tabId });

  if (chromeAction.setBadgeBackgroundColor)
    chromeAction.setBadgeBackgroundColor({
      color: "#646464",
      tabId: tabId,
    });
}

function setSuccessBadge(tabId) {
  setBadge(tabId, "✅");
}

function setDisabledBadge(tabId) {
  setBadge(tabId, "⛔");
}

// Common functions
function getHostname(url, cleanup) {
  try {
    if (url.indexOf("http") != 0) {
      throw true;
    }

    const a = new URL(url);

    return typeof cleanup == "undefined"
      ? a.hostname
      : a.hostname.replace(/^w{2,3}\d*\./i, "");
  } catch (error) {
    return false;
  }
}

// Whitelisting
function updateSettings() {
  return new Promise((resolve) => {
    lastDeclarativeNetRuleId = 1;
    chrome.storage.local.get(
      { settings: { whitelistedDomains: {}, statusIndicators: true } },
      async ({ settings: storedSettings }) => {
        settings = storedSettings;

        if (isManifestV3) {
          updateWhitelistRules();
        }
        resolve();
      }
    );
  });
}

async function updateWhitelistRules() {
  if (!isManifestV3) {
    console.warn("Called unsupported function");
    return;
  }
  const previousRules = (
    chrome.declarativeNetRequest.getDynamicRules()
  ).map((v) => {
    return v.id;
  });
  const addRules = Object.entries(settings.whitelistedDomains)
    .filter((element) => element[1])
    .map((v) => {
      return {
        id: lastDeclarativeNetRuleId++,
        priority: 1,
        action: { type: "allow" },
        condition: {
          urlFilter: "*",
          resourceTypes: ["script", "stylesheet", "xmlhttprequest", "image"],
          initiatorDomains: [v[0]],
        },
      };
    });

  chrome.declarativeNetRequest.updateDynamicRules({
    addRules,
    removeRuleIds: previousRules,
  });
}

function isWhitelisted(tab) {
  if (typeof settings.whitelistedDomains[tab.hostname] != "undefined") {
    return true;
  }

  for (const i in tab.host_levels) {
    if (typeof settings.whitelistedDomains[tab.host_levels[i]] != "undefined") {
      return true;
    }
  }

  return false;
}

function getWhitelistedDomain(tab) {
  if (typeof settings.whitelistedDomains[tab.hostname] != "undefined") {
    return tab.hostname;
  }

  for (const i in tab.host_levels) {
    if (typeof settings.whitelistedDomains[tab.host_levels[i]] != "undefined") {
      return tab.host_levels[i];
    }
  }

  return false;
}

async function toggleWhitelist(tab) {
  if (tab.url.indexOf("http") != 0 || !tabList[tab.id]) {
    return;
  }

  if (tabList[tab.id].whitelisted) {
    // const hostname = getWhitelistedDomain(tabList[tab.id]);
    delete settings.whitelistedDomains[tabList[tab.id].hostname];
  } else {
    settings.whitelistedDomains[tabList[tab.id].hostname] = true;
  }
  chrome.storage.local.set({ settings }, function () {
    for (const i in tabList) {
      if (tabList[i].hostname == tabList[tab.id].hostname) {
        tabList[i].whitelisted = !tabList[tab.id].whitelisted;
      }
    }
  });
  if (isManifestV3) {
    updateWhitelistRules();
  }
}

// Maintain tab list

function getPreparedTab(tab) {
  tab.hostname = false;
  tab.whitelisted = false;
  tab.host_levels = [];

  if (tab.url) {
    tab.hostname = getHostname(tab.url, true);

    if (tab.hostname) {
      const parts = tab.hostname.split(".");

      for (let i = parts.length; i >= 2; i--) {
        tab.host_levels.push(parts.slice(-1 * i).join("."));
      }

      tab.whitelisted = isWhitelisted(tab);
    }
  }

  return tab;
}

function onCreatedListener(tab) {
  tabList[tab.id] = getPreparedTab(tab);
}

function onUpdatedListener(tabId, changeInfo, tab) {
  if (changeInfo.status) {
    tabList[tab.id] = getPreparedTab(tab);
  }
}

function onRemovedListener(tabId) {
  if (tabList[tabId]) {
    delete tabList[tabId];
  }
}

async function recreateTabList(magic) {
  tabList = {};
  let results;
  if (isManifestV3) {
    results = chrome.tabs.query({});
  } else {
    results = new Promise((resolve, reject) => {
      chrome.tabs.query({}, (result) => {
        if (chrome.runtime.lastError) reject(chrome.runtime.lastError);
        resolve(result);
      });
    });
  }
  results.forEach(onCreatedListener);

  if (magic) {
    for (const i in tabList) {
      if (tabList.hasOwnProperty(i)) {
        doTheMagic(tabList[i].id);
      }
    }
  }
}

chrome.tabs.onCreated.addListener(onCreatedListener);
chrome.tabs.onUpdated.addListener(onUpdatedListener);
chrome.tabs.onRemoved.addListener(onRemovedListener);

// chrome.runtime.onStartup.addListener(async () =>   initialize(true));
chrome.runtime.onInstalled.addListener(
  async () => initialize(false, true)
);

// URL blocking

function blockUrlCallback(d) {
  // Cached request: find the appropriate tab
  // TODO: parse rules.json for this function.

  if (d.tabId == -1 && d.initiator) {
    // const hostname = getHostname(d.initiator, true);
    for (const tabId in tabList) {
      if (tabList[tabId].hostname == getHostname(d.initiator, true)) {
        d.tabId = parseInt(tabId);
        break;
      }
    }
  }

  if (tabList[d.tabId]?.whitelisted ?? false) {
    setDisabledBadge(d.tabId);
    return { cancel: false };
  }

  if (tabList[d.tabId] && d.url) {
    const cleanURL = d.url.split("?")[0];

    // To shorten the checklist, many filters are grouped by keywords

    for (const group in blockUrls.common_groups) {
      if (d.url.indexOf(group) > -1) {
        const groupFilters = blockUrls.common_groups[group];

        for (const i in groupFilters) {
          if (
            (groupFilters[i].q && d.url.indexOf(groupFilters[i].r) > -1) ||
            (!groupFilters[i].q && cleanURL.indexOf(groupFilters[i].r) > -1)
          ) {
            // Check for exceptions

            if (groupFilters[i].e && tabList[d.tabId].host_levels.length > 0) {
              for (const level in tabList[d.tabId].host_levels) {
                for (const exception in groupFilters[i].e) {
                  if (
                    groupFilters[i].e[exception] ==
                    tabList[d.tabId].host_levels[level]
                  ) {
                    return { cancel: false };
                  }
                }
              }
            }
            setSuccessBadge(d.tabId);
            return { cancel: true };
          }
        }
      }
    }

    // Check ungrouped filters

    const groupFilters = blockUrls.common;

    for (const i in groupFilters) {
      if (
        (groupFilters[i].q && d.url.indexOf(groupFilters[i].r) > -1) ||
        (!groupFilters[i].q && cleanURL.indexOf(groupFilters[i].r) > -1)
      ) {
        // Check for exceptions

        if (groupFilters[i].e && tabList[d.tabId].host_levels.length > 0) {
          for (const level in tabList[d.tabId].host_levels) {
            for (const exception in groupFilters[i].e) {
              if (
                groupFilters[i].e[exception] ==
                tabList[d.tabId].host_levels[level]
              ) {
                return { cancel: false };
              }
            }
          }
        }
        setSuccessBadge(d.tabId);
        return { cancel: true };
      }
    }

    // Site specific filters

    if (d.tabId > -1 && tabList[d.tabId].host_levels.length > 0) {
      for (const level in tabList[d.tabId].host_levels) {
        if (blockUrls.specific[tabList[d.tabId].host_levels[level]]) {
          const rules = blockUrls.specific[tabList[d.tabId].host_levels[level]];

          for (const i in rules) {
            if (d.url.indexOf(rules[i]) > -1) {
              setSuccessBadge(d.tabId);
              return { cancel: true };
            }
          }
        }
      }
    }
  }

  return { cancel: false };
}
if (!isManifestV3) {
  chrome.webRequest.onBeforeRequest.addListener(
    blockUrlCallback,
    {
      urls: ["http://*/*", "https://*/*"],
      types: ["script", "stylesheet", "xmlhttprequest"],
    },
    ["blocking"]
  );

  chrome.webRequest.onHeadersReceived.addListener(
    function (d) {
      if (tabList[d.tabId]) {
        d.responseHeaders.forEach(function (h) {
          if (h.name == "Content-Type" || h.name == "content-type") {
            xmlTabs[d.tabId] = h.value.indexOf("/xml") > -1;
          }
        });
      }

      return { cancel: false };
    },
    { urls: ["http://*/*", "https://*/*"], types: ["main_frame"] },
    ["blocking", "responseHeaders"]
  );
}
// Reporting

function reportWebsite(info, tab, anon, issueType, notes, callback) {
  if (tab.url.indexOf("http") != 0 || !tabList[tab.id]) {
    return;
  }

  const hostname = getHostname(tab.url);

  if (hostname.length == 0) {
    return;
  }

  if (tabList[tab.id].whitelisted) {
    return chrome.notifications.create("report", {
      type: "basic",
      title: chrome.i18n.getMessage("reportSkippedTitle", hostname),
      message: chrome.i18n.getMessage("reportSkippedMessage"),
      iconUrl: "icons/48.png",
    });
  }
  if (!anon) {
    chrome.tabs.create({
      url: `https://github.com/OhMyGuus/I-Dont-Care-About-Cookies/issues/new?assignees=OhMyGuus&labels=Website+request&template=website_request.yml&title=%5BREQ%5D%3A+${encodeURIComponent(
        hostname
      )}&url=${encodeURIComponent(hostname)}&version=${encodeURIComponent(
        chrome.runtime.getManifest().version
      )}&browser=${encodeURIComponent(getBrowserAndVersion())}`,
    });
  } else {
    fetch("https://api.istilldontcareaboutcookies.com/api/report", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        issueType,
        notes,
        url: tab.url,
        browser: getBrowserAndVersion(),
        extensionVersion: chrome.runtime.getManifest().version,
      }),
    })
      .then((response) => response.json())
      .then((response) => {
        if (
          response &&
          !response.error &&
          !response.errors &&
          response.responseURL
        ) {
          chrome.tabs.create({
            url: response.responseURL,
          });
          callback({ error: false });
        } else {
          callback({ error: true });
        }
      })
      .catch(() => {
        callback({ error: true });
      });
  }
}

function getBrowserAndVersion() {
  const useragent = navigator.userAgent;
  if (useragent.includes("Firefox")) {
    return useragent.match(/Firefox\/([0-9]+[\S]+)/)[0].replace("/", " ");
  } else if (useragent.includes("Chrome")) {
    if (navigator.userAgentData.brands.length > 2) {
      const { brand, version } = navigator.userAgentData.brands[1];
      return brand + " " + version;
    }
  }
  return "Other";
}

// Adding custom CSS/JS

function activateDomain(hostname, tabId, frameId) {
  if (!cachedRules[hostname]) {
    cachedRules[hostname] = rules[hostname] || {};
  }

  if (!cachedRules[hostname]) {
    return false;
  }

  const cachedRule = cachedRules[hostname];
  let status = false;

  // cached_rule.s = Custom css for webpage
  // cached_rule.c = Common css for webpage
  // cached_rule.j = Common js  for webpage

  if (typeof cachedRule.s != "undefined") {
    insertCSS({ tabId, frameId: frameId || 0, css: cachedRule.s });
    status = true;
  }

  if (typeof cachedRule.c != "undefined") {
    insertCSS({ tabId, frameId: frameId || 0, css: commons[cachedRule.c] });
    status = true;
  }

  if (typeof cachedRule.j != "undefined") {
    executeScript({
      tabId,
      frameId,
      file: `/data/js/${commonJSHandlers[cachedRule.j]}.js`,
    });
    status = true;
  }

  if (status) {
    setSuccessBadge(tabId);
  }

  return status;
}

function doTheMagic(tabId, frameId, anotherTry) {
  if (!tabList[tabId] || tabList[tabId].url.indexOf("http") != 0) {
    return;
  }

  if (tabList[tabId].whitelisted) {
    setDisabledBadge(tabId);
    return;
  }

  // Common CSS rules
  insertCSS(
    { tabId, frameId: frameId || 0, file: "/data/css/common.css" },
    function () {
      // A failure? Retry.
      if (chrome.runtime.lastError) {
        console.log(chrome.runtime.lastError);

        const currentTry = anotherTry || 1;

        if (currentTry == 10) {
          return;
        }
        if (currentTry > 5) {
          setTimeout(() => doTheMagic(tabId, frameId || 0, currentTry + 1));
        } else {
          doTheMagic(tabId, frameId || 0, currentTry + 1);
        }
        return;
      }

      // Common social embeds
      executeScript({ tabId, frameId, file: "/data/js/embedsHandler.js" });

      if (activateDomain(tabList[tabId].hostname, tabId, frameId || 0)) {
        return;
      }

      for (const level in tabList[tabId].host_levels) {
        if (
          activateDomain(tabList[tabId].host_levels[level], tabId, frameId || 0)
        ) {
          return true;
        }
      }

      // Common JS rules when custom rules don't exist
      executeScript({
        tabId,
        frameId,
        file: "/data/js/0_defaultClickHandler.js",
      });
    }
  );
}

chrome.webNavigation.onCommitted.addListener(async (tab) => {
  if (tab.frameId > 0) {
    return;
  }
  if (!initialized) {
    initialize();
  }

  tabList[tab.tabId] = getPreparedTab(tab);

  doTheMagic(tab.tabId);
});

chrome.webNavigation.onCompleted.addListener(async function (tab) {
  if (!initialized) {
    initialize();
  }
  if (tab.frameId > 0 && tab.url != "about:blank") {
    doTheMagic(tab.tabId, tab.frameId);
  }
});

// Toolbar menu

chrome.runtime.onMessage.addListener((request, info, sendResponse) => {
  initialize().then(() => {
    let responseSend = false;
    if (typeof request == "object") {
      if (request.tabId && tabList[request.tabId]) {
        if (request.command == "get_active_tab") {
          const response = { tab: tabList[request.tabId] };

          if (response.tab.whitelisted) {
            response.tab.hostname = getWhitelistedDomain(
              tabList[request.tabId]
            );
          }
          sendResponse(response);
          responseSend = true;
        } else if (request.command == "toggle_extension") {
          toggleWhitelist(tabList[request.tabId]);
        } else if (request.command == "report_website") {
          reportWebsite(
            info,
            tabList[request.tabId],
            request.anon,
            request.issueType,
            request.notes,
            sendResponse
          );
          responseSend = true;
        } else if (request.command == "refresh_page") {
          executeScript({
            tabId: request.tabId,
            func: () => {
              window.location.reload();
            },
          });
        }
      } else {
        if (request.command == "open_options_page") {
          chrome.tabs.create({
            url: chrome.runtime.getURL("/data/options.html"),
          });
        }
      }
    } else if (request == "update_settings") {
      updateSettings();
    }
    if (!responseSend) {
      sendResponse();
    }
  });

  return true;
});

function insertCSS(injection, callback) {
  const { tabId, css, file, frameId } = injection;

  if (isManifestV3) {
    chrome.scripting.insertCSS(
      {
        target: { tabId: tabId, frameIds: [frameId || 0] },
        css: css,
        files: file ? [file] : undefined,
        origin: "USER",
      },
      callback
    );
  } else {
    chrome.tabs.insertCSS(
      tabId,
      {
        file,
        code: css,
        frameId: frameId || 0,
        runAt: xmlTabs[tabId] ? "document_idle" : "document_start",
        cssOrigin: "user",
      },
      callback
    );
  }
}

function executeScript(injection, callback) {
  const { tabId, func, file, frameId } = injection;
  if (isManifestV3) {
    // manifest v3
    chrome.scripting.executeScript(
      {
        target: { tabId, frameIds: [frameId || 0] },
        files: file ? [file] : undefined,
        func,
      },
      callback
    );
  } else {
    // manifest v2
    chrome.tabs.executeScript(
      tabId,
      {
        file,
        frameId: frameId || 0,
        code: func == undefined ? undefined : "(" + func.toString() + ")();",
        runAt: xmlTabs[tabId] ? "document_idle" : "document_end",
      },
      callback
    );
  }
}

async function loadCachedRules() {
  // TODO: Load cached rules for V3 to improve speed (Requires testing to see if this actually is faster for v3)
  cachedRules = {};
}

async function initialize(checkInitialized, magic) {
  if (checkInitialized && initialized) {
    return;
  }
  loadCachedRules();
  updateSettings();
  recreateTabList(magic);
  initialized = true;
}

initialize();
