import axios, { AxiosInstance } from 'axios';
import random from 'lodash/random';
import { wait } from '../../utils';

type InADayScore = {
  player: {
    id: number;
    name: string;
    profileUrl: string;
  };
  tribe: {
    id: number;
    tag: string;
    profileUrl: string;
  } | null;
  rank: number;
  score: number;
  date: string;
};

class InADayParser {
  private readonly doc: Document;
  constructor(html: string) {
    this.doc = new DOMParser().parseFromString(html, 'text/html');
  }

  parse() {
    const results: InADayScore[] = [];

    for (const row of this.doc.querySelectorAll(
      '#in_a_day_ranking_table tbody tr'
    )) {
      if (!(row instanceof HTMLTableRowElement)) {
        continue;
      }

      const res = this.parseRow(row);
      if (!res) {
        continue;
      }

      results.push(res);
    }

    return results;
  }

  private parseRow(row: HTMLTableRowElement): InADayScore | null {
    const cells = [...row.children].filter(
      (cell): cell is HTMLTableCellElement =>
        cell instanceof HTMLTableCellElement
    );
    if (cells.length !== 5) {
      return null;
    }

    const playerProfileUrl = cells[1].querySelector('a')?.href;
    if (!playerProfileUrl) {
      return null;
    }

    const tribeProfileUrl = cells[2].querySelector('a')?.href;

    return {
      player: {
        id: this.getIdFromUrl(playerProfileUrl),
        name: cells[1].innerText.trim(),
        profileUrl: playerProfileUrl,
      },
      ...(tribeProfileUrl
        ? {
            tribe: {
              id: this.getIdFromUrl(tribeProfileUrl),
              tag: cells[2].innerText.trim(),
              profileUrl: tribeProfileUrl,
            },
          }
        : {
            tribe: null,
          }),
      rank: parseInt(cells[0].innerText.trim()),
      score: this.parseScore(cells[3].innerText.trim()),
      date: cells[4].innerText.trim(),
    };
  }

  private getIdFromUrl(u: string) {
    return parseInt(new URL(u).searchParams.get('id') ?? '');
  }

  private parseScore(s: string): number {
    return parseInt(s.trim().replace(/\./g, ''));
  }
}

type InADayParams = {
  name?: string;
  page?: number;
};

export type InADayPlayerScore = {
  rank: number;
  score: number;
  date: string;
};

export type InADayPlayer = {
  id: number;
  name: string;
  profileUrl: string;
  tribe: {
    id: number;
    tag: string;
    profileUrl: string;
  } | null;
  scores: {
    killAtt: InADayPlayerScore | null;
    killDef: InADayPlayerScore | null;
    killSup: InADayPlayerScore | null;
    lootRes: InADayPlayerScore | null;
    lootVil: InADayPlayerScore | null;
    scavenge: InADayPlayerScore | null;
    conquer: InADayPlayerScore | null;
  };
};

export class InADayClient {
  client: AxiosInstance;
  constructor(timeout = 10000) {
    this.client = axios.create({
      timeout,
    });
  }

  async player(name: string): Promise<InADayPlayer | null> {
    const params: InADayParams = {
      name,
    };

    const urlsAndKeys: {
      url: string;
      key: keyof InADayPlayer['scores'];
    }[] = [
      {
        url: this.buildUrl('kill_att', params),
        key: 'killAtt',
      },
      {
        url: this.buildUrl('kill_def', params),
        key: 'killDef',
      },
      {
        url: this.buildUrl('kill_sup', params),
        key: 'killSup',
      },
      {
        url: this.buildUrl('loot_res', params),
        key: 'lootRes',
      },
      {
        url: this.buildUrl('loot_vil', params),
        key: 'lootVil',
      },
      {
        url: this.buildUrl('scavenge', params),
        key: 'scavenge',
      },
      {
        url: this.buildUrl('conquer', params),
        key: 'conquer',
      },
    ];
    const scores: {
      key: keyof InADayPlayer['scores'];
      score: InADayScore;
    }[] = [];
    for (const { url, key } of urlsAndKeys) {
      const resp = await this.client.get(url);

      const score = new InADayParser(resp.data).parse()[0];
      if (score && score.player.name === name) {
        scores.push({
          key,
          score,
        });
      }

      if (key !== urlsAndKeys[urlsAndKeys.length - 1].key) {
        await wait(random(200, 400));
      }
    }

    if (scores.length === 0) {
      return null;
    }

    const player: InADayPlayer = {
      id: 0,
      name: '',
      profileUrl: '',
      tribe: null,
      scores: {
        conquer: null,
        killAtt: null,
        killDef: null,
        killSup: null,
        lootRes: null,
        lootVil: null,
        scavenge: null,
      },
    };
    scores.forEach(({ score, key }) => {
      if (!player.id) {
        player.id = score.player.id;
      }
      if (!player.name) {
        player.name = score.player.name;
      }
      if (!player.profileUrl) {
        player.profileUrl = score.player.profileUrl;
      }
      if (!player.tribe && score.tribe) {
        player.tribe = score.tribe;
      }

      player.scores[key] = {
        rank: score.rank,
        score: score.score,
        date: score.date,
      };
    });

    return player;
  }

  private buildUrl(type: string, params?: InADayParams) {
    return window.TribalWars.buildURL('GET', 'ranking', {
      mode: 'in_a_day',
      type,
      ...(params?.name
        ? {
            name: params.name,
          }
        : {}),
      ...(params?.page
        ? {
            offset: ((params.page - 1) * 25).toString(),
          }
        : {
            offset: '0',
          }),
    });
  }
}
