import type {
  BHArticleCategories,
  BHFullArticle,
  FullLegend,
  FullWeapon,
  PlayerRanked,
  PlayerStats,
  BHArticleFullCategory,
  BHArticle,
} from "./types";
import { legends, PR } from "./types";
import {
  article_categories,
  levels,
  modes,
  rankedTiers,
  regions,
  region_emojis,
  special_mode,
  Weapon,
} from "./constants";
import axios from "axios";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime"

function generateRandomUsername() {
  // Arrays of words
  const firstWords = ["Happy", "Funny", "Mad"];
  const secondWords = [
    "Brawler",
    "Bodvar",
    "Cassidy",
    "Orion",
    "Vraxx",
    "Gnash",
    "Nai",
    "Lucien",
    "Hattori",
    "Roland",
    "Scarlet",
    "Thatch",
    "Ada",
    "Sentinel",
    "Teros",
    "Ember",
    "Brynn",
    "Asuri",
    "Barraza",
    "Ulgrim",
    "Azoth",
    "Koji",
    "Diana",
    "Jhala",
    "Kor",
    "Shang",
    "Val",
    "Ragnir",
    "Cross",
    "Mirage",
    "Nix",
    "Mordex",
    "Yumiko",
    "Artemis",
    "Caspian",
    "Sidra",
    "Xull",
    "Isaiah",
    "Kaya",
    "Jiro",
    "Fei",
    "Zariel",
    "Rayman",
    "Dusk",
    "Fait",
    "Thor",
    "Petra",
    "Vector",
    "Volkov",
    "Onyx",
    "Jaeyun",
    "Mako",
    "Magyar",
    "Reno",
    "Munin",
    "Arcadia",
    "Ezio",
    "Thea",
    "Tezca"
];
  // Get current time
  const currentTime = new Date();
  const timeString = currentTime.getTime().toString();

  // Generate random numbers based on current time
  const randomNumbers = timeString.slice(-4);

  // Choose random words
  const randomIndex1 = Math.floor(Math.random() * firstWords.length);
  const randomIndex2 = Math.floor(Math.random() * secondWords.length);
  const word1 = firstWords[randomIndex1];
  const word2 = secondWords[randomIndex2];

  // Combine words and append random numbers
  const username = word1 + word2 + randomNumbers;

  return username;
}
const trusted_domains = [
  "stathalla.com",
  "gmail.com",
  "hotmail.com",
  "yahoo.com",
  "outlook.com",
  "aol.com",
  "icloud.com",
  "mail.com",
  "protonmail.com",
  "zoho.com",
  "yandex.com",
  "gmx.com",
  "fastmail.com",
  "tutanota.com",
  "rediffmail.com",
  "inbox.com",
  "live.com",
  "me.com",
  "icloud.com",
  "ymail.com",
  "rocketmail.com",
  "mail.ru",
  "cox.net",
  "att.net",
  "bellsouth.net",
  "verizon.net",
  "sbcglobal.net",
  "optonline.net",
  "earthlink.net",
  "shaw.ca",
  "sympatico.ca",
  "telus.net",
  "rogers.com",
  "comcast.net",
  "charter.net",
  "frontier.com",
  "juno.com",
  "windstream.net",
  "netzero.net",
  "centurylink.net",
  "blueyonder.co.uk",
  "ntlworld.com",
  "talktalk.net",
  "virginmedia.com",
  "btinternet.com",
  "libero.it",
  "alice.it",
  "tiscali.it",
  "orange.fr",
  "free.fr",
  "wanadoo.fr",
  "web.de",
  "arcor.de",
  "t-online.de",
  "alice-dsl.de",
  "seznam.cz",
  "wp.pl",
  "interia.pl",
];

function isEmailAllowed(email: string): boolean {
    // Email format regex pattern
    const emailFormatRegex = /^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6})*$/;
  
    // Check if the email matches the format
    if (!emailFormatRegex.test(email)) {
      return false;
    }
  
    // Extract the domain from the email address
    const domain = email.split("@")[1].toLowerCase();
    // console.log(domain)
   // Check if the domain (without country code) is in the list of trusted domains
    return trusted_domains.includes(domain);
  }
/**
 * Human readable elapsed or remaining time (example: 3 minutes ago)
 * @param  {Date|Number|String} date A Date object, timestamp or string parsable with Date.parse()
 * @param  {Date|Number|String} [nowDate] A Date object, timestamp or string parsable with Date.parse()
 * @param  {Intl.RelativeTimeFormat} [trf] A Intl formater
 * @return {string} Human readable elapsed or remaining time
 * @author github.com/victornpb
 * @see https://stackoverflow.com/a/67338038/938822
 */
function fromNow(
  date: number,
): string|undefined {
  dayjs.extend(relativeTime)
  return dayjs(date).fromNow()
}

function getRegionEmoji(
  region: string | string[] | undefined
): string | undefined {
  const r: string | undefined = region?.toString()?.toLowerCase();
  if (r == undefined) return r;
  return region_emojis?.[r] || r;
}

function getStarredPlayers():
  | Array<{
      name: string;
      starredAt: number;
      id: number | string;
    }>
   {
  const starred = localStorage.getItem("starred");
  if (!starred) return [];
  try {
    const starredJSON = JSON.parse(starred);
    return starredJSON;
  } catch (e) {
    setStarredPlayers([]);
    return [];
  }
}
function getStarredClans():
  | Array<{
      name: string;
      starredAt: number;
      id: number | string;
    }>
   {
  const starred = localStorage.getItem("starred_clans");
  if (!starred) return [];
  try {
    const starredJSON = JSON.parse(starred);
    return starredJSON;
  } catch (e) {
    setStarredPlayers([]);
    return [];
  }
}
function setStarredPlayers(players:Object): void {
  localStorage.setItem("starred", JSON.stringify(players));
}
function setStarredClans(players:Object): void {
  localStorage.setItem("starred_clans", JSON.stringify(players));
}

function format_time(unixTime: string | number): string {
  return new Date(Number(unixTime) * 1000)
    .toISOString()
    .slice(0, 19)
    .replace(/T/, " ")
    .replace(/-/g, ".");
}
/**
 Get the elo reset .dmg
* @returns Estimate .dmg of elo on season reset
* @param elo
* @example
* ```js
* let newElo = bh.getEloReset.dmg(2000)
* console.log(newElo)
* // 1742
* ```
*/
function getEloReset(elo: number | string): number {
  elo = Number(elo);
  let new_elo = elo;
  if (elo >= 1400)
    new_elo = Math.floor(1400 + (elo - 1400.0) / (3.0 - (3000 - elo) / 800.0));
  return new_elo;
}
/**
Get the team/legend elo reset .dmg
* @returns Estimate .dmg of elo on season reset
* @param elo
* @example
* ```js
* let newTeamElo = bh.getTeamEloReset(2000)
* console.log(newTeamElo)
* // 1583
* @example
* ```js
* let newLegendElo = bh.getTeamEloReset(2000)
* console.log(newLegendElo)
* // 1583
* ```
*/
function getTeamEloReset(elo: number | string): number {
  elo = Number(elo);
  if (elo < 2000) return Math.floor((elo + 375) / 1.5);
  return Math.floor(1583 + (elo - 2000) / 10);
}
/**
Get the glory from wins
* @returns Estimate .dmg of glory from wins on season reset
* @param wins
* @param has_played_10_games
* @example
* ```js
* let gloryFromWins = bh.getGloryFromWins(67)
* console.log(gloryFromWins)
* // 1340
* ```
*/
function getGloryFromWins(
  wins: number | string,
  games: string | number
): number {
  if (Number(games) < 10) return 0;
  wins = Number(wins);
  if (wins <= 150) return 20 * wins;
  return Math.floor(10 * (45 * Math.pow(Math.log10(wins * 2), 2)) + 245);
}
/**
   Get the glory from best rating
 * @returns Estimate .dmg of glory from wins on season reset
 * @param best_rating
 * @example
 * ```js
 * let gloryFromRating = bh.getGloryFromBestRating(1900)
 * console.log(gloryFromRating)
 * // 3941
 * ```
 */
function getGloryFromBestRating(best_rating: number | string): number {
  let val = 0;
  best_rating = Number(best_rating);
  if (best_rating < 1200) val = 250;
  if (best_rating >= 1200 && best_rating < 1286)
    val = 10 * (25 + 0.872093023 * (86 - (1286 - best_rating)));
  if (best_rating >= 1286 && best_rating < 1390)
    val = 10 * (100 + 0.721153846 * (104 - (1390 - best_rating)));
  if (best_rating >= 1390 && best_rating < 1680)
    val = 10 * (187 + 0.389655172 * (290 - (1680 - best_rating)));
  if (best_rating >= 1680 && best_rating < 2000)
    val = 10 * (300 + 0.428125 * (320 - (2000 - best_rating)));
  if (best_rating >= 2000 && best_rating < 2300)
    val = 10 * (437 + 0.143333333 * (300 - (2300 - best_rating)));
  if (best_rating >= 2300)
    val = 10 * (480 + 0.05 * (400 - (2700 - best_rating)));
  return Math.floor(val);
}
/**
Get the wins, best rating, and total glory
* @returns Estimate .dmg of glory from wins on season reset
* @param best_rating
* @param wins
* @param has_played_10_games
* @example
* ```js
* let gloryFromRating = bh.getGlory(1900, 100)
* console.log(gloryFromRating)
* // {
* // wins: 2000,
* // rating: 3941,
* // total: 5941,
* // }
* ```
*/
function getGlory(
  best_rating: number | string,
  wins: number | string,
  games: number | string
): {
  wins: number;
  rating: number;
  total: number;
} {
  let wins_glory = getGloryFromWins(wins, games);
  let rating_glory = getGloryFromBestRating(best_rating);
  let total_glory = wins_glory + rating_glory;
  let glory = {
    wins: wins_glory,
    rating: rating_glory,
    total: total_glory,
  };
  return glory;
}

/**
Converts JSON to string
* @returns stringified JSON
* @param json
* @example
* ```js
* let string = stringifyJSON([...])
* console.log(string)
* // '[...]'
* ```
*/
function stringifyJSON(json: object): string {
  return JSON.stringify(json);
}

/**
Converts string to JSON
* @returns JSON
* @param json
* @example
* ```js
* let json = parseJSON('[...]')
* console.log(json)
* // [...]
* ```
*/
function parseJSON(json: string): object {
  return JSON.parse(json);
}
/**
Converts seconds to date
* @returns Returns a formatted date
* @param seconds
* @example
* ```js
* let date = secondsToDhms(1038992)
* console.log(date)
* // 12 days, 36 minutes, 32 seconds
* ```
*/
function secondsToDhms(seconds: string | number): string {
  seconds = Number(seconds);
  if (seconds <= 0) return "0 minutes";
  var d = Math.floor(seconds / (3600 * 24));
  var h = Math.floor((seconds % (3600 * 24)) / 3600);
  var m = Math.floor((seconds % 3600) / 60);
  // var s = Math.floor(seconds % 60);

  var dDisplay = d > 0 ? d + (d == 1 ? " day, " : " days, ") : "";
  var hDisplay = h > 0 ? h + (h == 1 ? " hour, " : " hours, ") : "";
  var mDisplay = m > 0 ? m + (m == 1 ? " minute " : " minutes ") : "";
  // var sDisplay = s > 0 ? s + (s == 1 ? " second" : " seconds") : "";
  return dDisplay + hDisplay + mDisplay;
}
const arrayToMap = <T, Key extends keyof T>(
  arr: (T & { [k in Key]: string | number | symbol })[],
  key: Key
  // @ts-expect-error ts doesn't know that T[Key] is a string | number | symbol
): Record<T[Key], T> =>
  // @ts-expect-error ts doesn't know that T[Key] is a string | number | symbol
  arr.reduce<Record<T[Key], T>>(
    (obj, item) => ((obj[item[key]] = item), obj),
    // @ts-expect-error ts doesn't know that T[Key] is a string | number | symbol
    {}
  );
/**
Converts seconds to hours and minutes
* @returns Returns a formatted hours and minutes
* @param seconds
* @example
* ```js
* let date = secondsTohm(1038992)
* console.log(date)
* // 540h 35m
* ```
*/
function secondsTohm(seconds: number | string): string {
  seconds = Number(seconds);
  // var d = Math.floor(seconds / (3600 * 24));
  var h = Math.floor(seconds / 3600);
  var m = Math.floor((seconds % 3600) / 60);
  // var s = Math.floor(seconds % 60);

  // var dDisplay = d > 0 ? d + (d == 1 ? " day, " : " days, ") : "";
  // var hDisplay = h > 0 ? h + (h == 1 ? " hour, " : " hours, ") : "";
  // var mDisplay = m > 0 ? m + (m == 1 ? " minute, " : " minutes, ") : "";
  // var sDisplay = s > 0 ? s + (s == 1 ? " second" : " seconds") : "";
  return `${h || 0}h ${m || 0}m`;
}
/**
Returns highest from lowest value from provided Value
* @returns sorted array
* @param legends Array of legends
* @example
* ```js
* let newLegends = sortLegendsArrayBy([...], "rating")
* console.log(newLegends)
* // [
* // {rating: "1999", ...},
* // ...
* // ]
* ```
*/
const sortLegendsArrayValue = (
  legends: any[] = [],
  type: string = "rating"
) => {
  // Sort legends array from highest to lowest from type
  return legends.sort(function (a, b) {
    return b[type] - a[type];
  });
};
// interface sortLegendsArrayValueProps {
//     legends?: object,
//     type?: string
// }
/**
Returns a string of a unique id
* @returns a string of a unique id
* @example
* ```js
* console.log(generateUUID(true))
* // xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
* // 
* // Without dashes
* console.log(generateUUID(false))
* // xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx
* ```
*
*/
function generateUUID(dashes: boolean = true) {
  var d = new Date().getTime();
  var d2 =
    (typeof performance !== "undefined" &&
      performance.now &&
      performance.now() * 1000) ||
    0;
  let string = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
    /[xy]/g,
    function (c) {
      var r = Math.random() * 16; //random number between 0 and 16
      if (d > 0) {
        r = (d + r) % 16 | 0;
        d = Math.floor(d / 16);
      } else {
        r = (d2 + r) % 16 | 0;
        d2 = Math.floor(d2 / 16);
      }
      return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
    }
  );
  return dashes ? string : string.replace(/-/g, "");
}

async function get_PR(mode: string, region: string = "NA"): Promise<PR[]> {
  let PR_URL = "https://api.brawltools.com/pr";
  const { data } = await axios.post(PR_URL, {
    gameMode: mode === "1v1" ? 1 : 2,
    maxResults: 100,
orderBy: "powerRanking ASC",
page:1,
region: region.toUpperCase(),
  });
  let j = data["prPlayers"]
  let orderedData = j.map((player) => {
    console.log(player)
    return {
      rank: player["powerRanking"],
      name: player["playerName"],
      earnings: "$" + player["earnings"],
      raw_earnings: player["playerName"],
      t1: player["gold"],
      t2: player["silver"],
      t3: player["bronze"],
      t8: player["top8"],
      t32: player["top32"],
      brawlhalla_id: player["playerId"],
    }
  })
  console.log(orderedData)
  return orderedData;
}

const getLegendById = (id: number | string): FullLegend | undefined => {
  return legends.find((legend) => Number(legend.legend_id) === Number(id));
};

const getFullLegends = (
  stats?: PlayerStats["legends"],
  ranked?: PlayerRanked["legends"]
) => {
  const statsMap = arrayToMap(stats ?? [], "legend_id");
  const rankedMap = arrayToMap(ranked ?? [], "legend_id");
  const fullLegends = legends.reduce<Record<number, FullLegend>>(
    (acc, legend) => (
      (acc[legend.legend_id] = {
        ...legend,
        stats: statsMap[legend.legend_id],
        ranked: rankedMap[legend.legend_id],
      }),
      acc
    ),
    {}
  );

  return Object.values(fullLegends);
};

const getFullWeapons = (legends: FullLegend[]): FullWeapon => {
  const legendsMap = arrayToMap(legends, "legend_id");
  const weaponsMap = legends.reduce((acc, legend) => {
    const legendData = legendsMap[legend.legend_id];
    acc[legendData.weapon_one] = [
      ...(acc[legendData.weapon_one] ?? []),
      legend,
    ];
    acc[legendData.weapon_two] = [
      ...(acc[legendData.weapon_two] ?? []),
      legend,
    ];
    return acc;
  }, {} as Record<Weapon, FullLegend[]>);
  let total_stats = {
    dmg: 0,
    dmg_taken: 0,
    matchtime: 0,
    kos: 0,
    teamkos: 0,
    falls: 0,
    suicides: 0,
  };
  for (let i = 0; i < legends.length; i++) {
    const legend = legends[i];
    // Apply stats
    total_stats.dmg += parseInt(legend?.stats?.damagedealt || "0");
    total_stats.dmg_taken += parseInt(legend?.stats?.damagetaken || "0");
    total_stats.matchtime += legend.stats?.matchtime || 0;
    total_stats.kos += legend.stats?.kos || 0;
    total_stats.teamkos += legend.stats?.teamkos || 0;
    total_stats.falls += legend.stats?.falls || 0;
    total_stats.suicides += legend.stats?.suicides || 0;
  }

  const weapons = Object.entries(weaponsMap)
    .map(([weapon, legends]) => {
      let dmg: number = 0;
      let timeheld: number = 0;
      let kos: number = 0;
      let wins: number = 0;
      let games: number = 0;
      for (let i = 0; i < legends.length; i++) {
        const legend = legends[i];
        let weapon_num: "one" | "two" | undefined;
        // Get weapon_num
        if (legend.weapon_one === weapon) weapon_num = "one";
        else weapon_num = "two";
        dmg += parseInt(legend?.stats?.[`damageweapon${weapon_num}`] || "0");
        timeheld += legend.stats?.[`timeheldweapon${weapon_num}`] || 0;
        kos += legend.stats?.[`koweapon${weapon_num}`] || 0;
        wins += legend.stats?.wins || 0;
        games += legend.stats?.games || 0;
      }
      return {
        weapon,
        legends,
        dmg,
        timeheld,
        kos,
        wins,
        games,
      };
    })
    .sort((a, b) => b.dmg - a.dmg);
  return {
    ...total_stats,
    weapons: [...weapons],
  };
};
function get_tier_from_rating(r: number | string) {
  // Use rankedTiers to get the tier from the rating
  // example: r = 1500, return "Gold 3"
  // Example 2: r = 2500, return "Diamond"
  // Example 3: r = 150, return "Tin 0"
  // Example 4: r = 900, return "Bronze 0"
  // Example 5: r = -1, return "Tin 0"
  r = Number(r)
  if (r <= 200) {
    return "Tin 0";
  }
  for (let i = 0; i < rankedTiers.length; i++) {
    const tier = rankedTiers[i];
    if (r >= tier[1]) {
      return tier[0];
    }
  }
  return "Tin 0";
}

async function get_articles(category?: BHArticleCategories) {
  const POSTS_URL = "https://www.brawlhalla.com/wp-json/wp/v2/posts";
  let categoryParam =
    typeof category !== "undefined" ? `?categories=${category}` : "";
  const URL = `${POSTS_URL}${categoryParam}${
    typeof category !== "undefined" ? "&" : "?"
  }per_page=30`;
  const articles = await axios.get(URL);
  let newArray: Array<BHArticle> = [];
  if (articles?.data) {
    articles.data.forEach((article: BHFullArticle) => {
      let date = dayjs(article.date).format("DD.MM.YYYY");
      let title = article.title.rendered;
      let src = article.yoast_head_json.og_image[0].url;
      let href = article.link;
      let categories: Array<BHArticleFullCategory> = [];
      article.categories.forEach((category: number) => {
        let cat = article_categories.find((c) => c.id === category);
        if (cat === undefined) return;
        categories.push(cat);
      });
      let patchObject = { date, title, src, href, categories };
      newArray.push(patchObject);
    });
  }

  return newArray;
}

function allowedToTrack() {
  return !(
    localStorage.getItem("allowedToTrack") !== null &&
    localStorage.getItem("allowedToTrack") !== "yes" &&
    localStorage.getItem("allowedToTrack") === "no"
  );
}
function getRankedMatchElo(RA: number, RB: number, win: boolean): number {
  let EA: number = 1 / (1 + Math.pow(10, (RB - RA) / 400));
  let NRA: number = Math.round(RA + 32 * ((win ? 1 : 0) - EA));
  return NRA;
}
function getLevelFromXP(xp: number): number {
  var level: number = 1;

  for (let i = 0; i < levels?.length; i++) {
    let lvl = levels[levels.length - 1 - i];
    // Check if matched level is 100 and xp is bigger than level xp
    if (lvl[0] === 100 && xp >= lvl[1]) {
      level = Math.floor((xp / levels[levels.length - 1][1]) * 100);
      break;
    }
    if (xp >= lvl[1]) {
      level = lvl[0];
      break;
    }
  }
  return level;
}
export {
  getGlory,
  stringifyJSON,
  parseJSON,
  secondsToDhms,
  secondsTohm,
  sortLegendsArrayValue,
  generateUUID,
  regions,
  modes,
  special_mode,
  getEloReset,
  getTeamEloReset,
  getGloryFromBestRating,
  getGloryFromWins,
  getFullLegends,
  getFullWeapons,
  legends,
  getRankedMatchElo,
  arrayToMap,
  get_PR,
  getLegendById,
  get_tier_from_rating,
  get_articles,
  allowedToTrack,
  getLevelFromXP,
  fromNow,
  getRegionEmoji,
  getStarredPlayers,
  setStarredPlayers,
  format_time,
  getStarredClans,
  setStarredClans,
  isEmailAllowed,
  trusted_domains,
  generateRandomUsername
};
