import { addSeconds } from 'date-fns/addSeconds';
import { Cache, InMemoryStorage } from './lib/cache';
import { calcDistance, calcLoyalty } from './lib/tw';
import { createTranslationFunc } from './utils';
import {
  TWHelpClient,
  Ennoblement,
  UnitInfo,
  ServerConfig,
  AxiosHttpRequestWithTimeout,
} from './lib/twhelp';

declare global {
  interface Window {
    TWMap: {
      popup: {
        loadVillage: (villageId: string) => void;
        _loadVillage: (villageId: string) => void;
        displayForVillage: (
          village: { id: string },
          x: number,
          y: number
        ) => void;
        _displayForVillage: (
          village: { id: string },
          x: number,
          y: number
        ) => void;
      };
    };
  }
}

const t = createTranslationFunc({
  pl_PL: {
    'Ennobled at': 'Ostatnio przejęta',
    Loyalty: 'Poparcie',
    'Can send a nobleman': 'Można wysłać szlachica',
    Yes: 'Tak',
    No: 'Nie',
    Never: 'Nigdy',
  },
});

class TWHelpConnector {
  private static readonly SERVER_CONFIG_CACHE_KEY =
    'extended_map_popup_server_config_v2';
  private static readonly UNIT_INFO_CACHE_KEY =
    'extended_map_popup_unit_info_v2';
  private static readonly VILLAGE_CACHE_KEY_PREFIX =
    'extended_map_popup_village_v2_';

  private readonly localStorageCache = new Cache(localStorage);
  private readonly inMemoryCache = new Cache(new InMemoryStorage());

  private readonly client: TWHelpClient;

  constructor(
    readonly baseUrl: string,
    private readonly version: string,
    private readonly server: string
  ) {
    this.client = new TWHelpClient(
      {
        BASE: baseUrl,
      },
      AxiosHttpRequestWithTimeout
    );
  }

  serverConfig() {
    return this.localStorageCache.load(
      TWHelpConnector.SERVER_CONFIG_CACHE_KEY,
      3600,
      async () => {
        return (
          await this.client.servers.getServerConfig({
            serverKey: this.server,
            versionCode: this.version,
          })
        ).data;
      }
    );
  }

  unitInfo() {
    return this.localStorageCache.load(
      TWHelpConnector.UNIT_INFO_CACHE_KEY,
      3600,
      async () => {
        return (
          await this.client.servers.getUnitInfo({
            serverKey: this.server,
            versionCode: this.version,
          })
        ).data;
      }
    );
  }

  async latestEnnoblement(id: number, cacheOnly?: boolean) {
    const key = TWHelpConnector.VILLAGE_CACHE_KEY_PREFIX + id.toString();

    if (cacheOnly) {
      return this.inMemoryCache.get<Ennoblement | null>(key);
    }

    return this.inMemoryCache.load(key, 86400, async () => {
      const ennoblements =
        await this.client.ennoblements.listVillageEnnoblements({
          versionCode: this.version,
          serverKey: this.server,
          sort: ['createdAt:DESC'],
          villageId: id,
          limit: 1,
        });
      return ennoblements.data.length > 0 ? ennoblements.data[0] : null;
    });
  }
}

class Popup {
  private static readonly ID_ENNOBLED_AT = 'extended_map_popup_ennobled_at';
  private static readonly ID_LOYALTY = 'extended_map_popup_loyalty';

  constructor(
    private readonly config: ServerConfig,
    private readonly unitInfo: UnitInfo,
    private readonly currentVillage: Window['game_data']['village'],
    private readonly connector: TWHelpConnector
  ) {}

  addHandlers() {
    window.TWMap.popup._loadVillage = window.TWMap.popup.loadVillage;
    window.TWMap.popup.loadVillage = this.loadVillage.bind(this);
    window.TWMap.popup._displayForVillage =
      window.TWMap.popup.displayForVillage;
    window.TWMap.popup.displayForVillage = this.display.bind(this);
  }

  private async loadVillage(villageId: string) {
    window.TWMap.popup._loadVillage(villageId);

    await this.displayLatestEnnoblementAndLoyalty(parseInt(villageId), false);
  }

  private async display(village: { id: string }, x: number, y: number) {
    window.TWMap.popup._displayForVillage(village, x, y);

    this.displayArrivalTimes(x, y);
    this.displayCanSendNobleman(x, y);
    await this.displayLatestEnnoblementAndLoyalty(
      parseInt(village.id),
      window.game_data.features.Premium.active
    );
  }

  private displayArrivalTimes(x: number, y: number) {
    const dist = calcDistance({ x, y }, this.currentVillage);
    if (dist <= 0) {
      return;
    }

    const imgs = document.querySelectorAll(
      '#map_popup #info_content tbody img[src*="unit/unit_"]'
    );
    if (imgs.length === 0) {
      return;
    }

    const tbody = imgs[0].closest('tbody');
    if (!(tbody instanceof HTMLTableSectionElement)) {
      return;
    }

    const tr = document.createElement('tr');
    tr.classList.add('center');

    imgs.forEach((img, idx) => {
      if (!(img instanceof HTMLImageElement)) {
        return;
      }

      for (const [unit, config] of Object.entries(this.unitInfo)) {
        if (!img.src.includes(unit)) {
          continue;
        }

        const td = document.createElement('td');
        td.style.padding = '2px';
        td.style.backgroundColor = idx % 2 === 0 ? '#F8F4E8' : '#DED3B9';
        td.style.maxWidth = '70px';
        td.innerText = addSeconds(
          window.Timing.getCurrentServerTime(),
          Math.round(dist * config.speed * 60)
        ).toLocaleString();
        tr.appendChild(td);

        break;
      }
    });

    tbody.appendChild(tr);
  }

  private displayCanSendNobleman(x: number, y: number) {
    const dist = calcDistance({ x, y }, this.currentVillage);
    if (dist <= 0) {
      return;
    }

    const tbody = document.querySelector('#map_popup #info_content tbody');
    if (!(tbody instanceof HTMLTableSectionElement)) {
      return;
    }

    const tr = document.createElement('tr');

    tr.innerHTML = `
      <td>${t('Can send a nobleman')}:</td>
      <td>${dist <= this.config.snob.maxDist ? t('Yes') : t('No')}</td>
    `;

    tbody.appendChild(tr);
  }

  private async displayLatestEnnoblementAndLoyalty(
    id: number,
    cacheOnly: boolean
  ) {
    const ennoblement = await this.connector.latestEnnoblement(id, cacheOnly);

    const tbody = document.querySelector('#map_popup #info_content tbody');
    if (!(tbody instanceof HTMLTableSectionElement)) {
      return;
    }

    [
      {
        id: Popup.ID_ENNOBLED_AT,
        title: t('Ennobled at'),
        value: ennoblement
          ? new Date(ennoblement.createdAt).toLocaleString()
          : t('Never'),
      },
      {
        id: Popup.ID_LOYALTY,
        title: t('Loyalty'),
        value: calcLoyalty(ennoblement?.createdAt ?? 0, this.config.speed),
      },
    ].forEach(({ id, title, value }) => {
      let tr = tbody.querySelector('#' + id);
      if (!tr) {
        tr = document.createElement('tr');
        tr.id = id;
        tbody.appendChild(tr);
      }

      tr.innerHTML = `
       <td>${title}:</td>
       <td>${value}</td>
`;
    });
  }
}

class ExtendedMapPopup {
  connector: TWHelpConnector;
  constructor(baseApiUrl: string) {
    this.connector = new TWHelpConnector(
      baseApiUrl,
      window.game_data.market,
      window.game_data.world
    );
  }

  async run() {
    const [config, unitInfo] = await Promise.all([
      this.connector.serverConfig(),
      this.connector.unitInfo(),
    ]);

    new Popup(
      config,
      unitInfo,
      window.game_data.village,
      this.connector
    ).addHandlers();
  }
}

(async () => {
  if (window.game_data.screen !== 'map' || window.game_data.mode !== null) {
    return;
  }

  await new ExtendedMapPopup(process.env.TWHELP_API_BASE_URL ?? '')
    .run()
    .catch((err) => {
      console.log(err);
    });
})();
