import chalk from 'chalk';
import CliTable3 from 'cli-table3';
import { HTTPError } from 'ky';
import { DateTime } from 'luxon';
import prompts from 'prompts';
import apiUtils from '../utils/api/index.js';
import argvUtils from '../utils/argv.js';
import appConfig from '../utils/config.js';
import exitUtils from '../utils/exit.js';
import logger from '../utils/logger.js';
import termPrettyUtils from '../utils/termPretty.js';

async function mainCmdHandler() {
  const cfg = appConfig.network.api.akEndfield;
  // const channelStr = String(cfg.channel.osWinRel);

  let needRetrieveToken = false;
  let oauth2TokenPreRsp = null;
  if (!('token' in argvUtils.getArgv()) || !argvUtils.getArgv()['token']) {
    const tokenUserRsp: string = await (async () => {
      logger.warn('Gryphline account service token has not been specified. Requesting ...');
      const onCancelFn = () => {
        logger.error('Aborted');
        exitUtils.exit(1, null, false);
      };
      return (
        await prompts(
          { name: 'value', type: 'password', message: 'Enter Gryphline account service token' },
          { onCancel: onCancelFn },
        )
      ).value;
    })();
    if (tokenUserRsp === '') {
      needRetrieveToken = true;
    } else {
      argvUtils.setArgv({ ...argvUtils.getArgv(), token: tokenUserRsp });
    }
  }
  logger.info('Authorization in progress ...');
  if (needRetrieveToken === false) {
    try {
      logger.debug('Retrieving account service OAuth 2.0 code ...');
      oauth2TokenPreRsp = await apiUtils.akEndfield.accountService.user.oauth2.v2.grant(
        cfg.appCode.accountService.osWinRel,
        argvUtils.getArgv()['token'],
      );
    } catch (err) {
      if (err instanceof HTTPError) {
        if ((await err.response.json()).status === 3) needRetrieveToken = true;
      } else {
        throw err;
      }
    }
  }
  if (needRetrieveToken) {
    await (async () => {
      const onCancelFn = () => {
        logger.error('Aborted');
        exitUtils.exit(1, null, false);
      };
      if (!('email' in argvUtils.getArgv())) {
        logger.warn('Gryphline account email has not been specified. Requesting ...');
        const emailRsp: number = (
          await prompts(
            {
              ...{ name: 'value', type: 'text', message: 'Enter Gryphline account email' },
              validate: (value) => (Boolean(value) ? true : 'Invalid value'),
            },
            { onCancel: onCancelFn },
          )
        ).value;
        argvUtils.setArgv({ ...argvUtils.getArgv(), email: emailRsp });
      }
      if (!('password' in argvUtils.getArgv())) {
        // logger.warn('Gryphline account password has not been specified. Requesting ...');
        const pwdRsp: number = (
          await prompts(
            {
              ...{ name: 'value', type: 'password', message: 'Enter Gryphline account password' },
              validate: (value) => (Boolean(value) ? true : 'Invalid value'),
            },
            { onCancel: onCancelFn },
          )
        ).value;
        argvUtils.setArgv({ ...argvUtils.getArgv(), password: pwdRsp });
      }
    })();
    logger.debug('Retrieving account service token ...');
    const accSrvTokenRsp = await apiUtils.akEndfield.accountService.user.auth.v1.tokenByEmailPassword(
      argvUtils.getArgv()['email'],
      argvUtils.getArgv()['password'],
    );
    argvUtils.setArgv({ ...argvUtils.getArgv(), token: accSrvTokenRsp.data.token });
  }

  oauth2TokenPreRsp === null ? logger.debug('Retrieving account service OAuth 2.0 code ...') : undefined;
  const oauth2TokenRsp =
    oauth2TokenPreRsp === null
      ? await apiUtils.akEndfield.accountService.user.oauth2.v2.grant(
          cfg.appCode.accountService.osWinRel,
          argvUtils.getArgv()['token'],
        )
      : oauth2TokenPreRsp;
  const oauth2TokenBindRsp = await apiUtils.akEndfield.accountService.user.oauth2.v2.grant(
    cfg.appCode.accountService.binding,
    argvUtils.getArgv()['token'],
    1,
  );
  logger.debug('Retrieving u8 access token ...');
  const u8TokenRsp = await apiUtils.akEndfield.u8.user.auth.v2.tokenByChToken(
    cfg.appCode.u8.osWinRel,
    cfg.channel.osWinRel,
    oauth2TokenRsp.data.code,
  );
  // logger.debug('Retrieving u8 OAuth 2.0 code ...');
  // const u8OAuth2Rsp = await apiUtils.apiAkEndfield.u8.user.auth.v2.grant(u8TokenRsp.data.token);
  logger.info('Authentication successful!');

  logger.info('Retrieving user information data ...');

  logger.debug('Retrieving user account data ...');
  const userAccData = await apiUtils.akEndfield.accountService.user.info.v1.basic(
    cfg.appCode.accountService.osWinRel,
    argvUtils.getArgv()['token'],
  );
  logger.debug('Retrieving user game server data ...');
  const userGameData = await apiUtils.akEndfield.u8.game.server.v1.serverList(u8TokenRsp.data.token);
  const userGameBindingData = await apiUtils.akEndfield.binding.account.binding.v1.bindingList(
    oauth2TokenBindRsp.data.token,
  );

  logger.debug('Retrieving gacha record ...');
  const selectedServerId = await (async () => {
    const selectedServerAccData = userGameBindingData.data.list
      .find((f) => f.appCode === 'endfield')!
      .bindingList[0]!.roles.filter((e) => e.isBanned === false)
      .sort((a, b) => b.level - a.level)[0];
    if (!selectedServerAccData) throw new Error('Game account not found');
    const id = selectedServerAccData.serverId;
    logger.debug('Confirming server availability ...');
    const confirmServerRsp = await apiUtils.akEndfield.u8.game.role.v1.confirmServer(
      u8TokenRsp.data.token,
      parseInt(id),
    );
    if (confirmServerRsp.status !== 0)
      throw new Error('Game server availability error: ' + JSON.stringify(confirmServerRsp));
    return id;
  })();
  const gachaRecordRsp = await (async () => {
    const overallRsp = await (async () => {
      const poolTypeList = [
        'E_CharacterGachaPoolType_Standard',
        'E_CharacterGachaPoolType_Beginner',
        'E_CharacterGachaPoolType_Special',
      ] as const;
      const recordArr = [];
      for (const poolTypeEntry of poolTypeList) {
        let seqId: string | null = null;
        while (true) {
          const rsp = await apiUtils.akEndfield.webview.record.char(
            u8TokenRsp.data.token,
            parseInt(selectedServerId),
            poolTypeEntry,
            seqId,
          );
          recordArr.push(...rsp.data.list.map((e) => ({ poolType: poolTypeEntry, ...e })));
          logger.trace(`Loaded: ${poolTypeEntry}, ${recordArr.length} entries, hasMore=${rsp.data.hasMore}`);
          if (rsp.data.hasMore === false) break;
          if (!rsp.data.list.at(-1)) break;
          seqId = rsp.data.list.at(-1)!.seqId;
        }
      }
      return recordArr;
    })();
    return overallRsp.toReversed();
  })();
  const gachaPoolInfoList = await (async () => {
    logger.debug('Retrieving gacha pool info ...');
    const arr = [];
    const poolIdList = [...new Set(gachaRecordRsp.map((e) => e.poolId))];
    for (const poolId of poolIdList) {
      const rsp = await apiUtils.akEndfield.webview.content(parseInt(selectedServerId), poolId, 'ja-jp');
      arr.push({ poolId, ...rsp.data.pool });
    }
    return arr;
  })();
  logger.info('Data retrieval completed!');

  (() => {
    const table = new CliTable3(termPrettyUtils.cliTableConfig.rounded);
    table.push(
      // [{ colSpan: 2, hAlign: 'center', content: chalk.bold('Account Info') }],
      ...[
        ['Hypergryph ID', userAccData.data.hgId],
        ['OAuth Grant ID', oauth2TokenRsp.data.uid],
        ['Game Overall UID', userGameBindingData.data.list.find((e) => e.appCode === 'endfield')!.bindingList[0]!.uid],
        ['Email', userAccData.data.realEmail],
        ['Nickname', userAccData.data.nickName === '' ? chalk.dim('(none)') : userAccData.data.nickName],
        ['Age Region', userAccData.data.ageGate.regionInfo['en-us']],
        [
          'Registered',
          DateTime.fromSeconds(
            userGameBindingData.data.list.find((e) => e.appCode === 'endfield')!.bindingList[0]!.registerTs,
          ).toFormat('yyyy/MM/dd HH:mm:ss'),
        ],
      ].map((e) => [chalk.dim(e[0]), e[1]]),
    );
    console.log(table.toString());
  })();

  (() => {
    const table = new CliTable3(termPrettyUtils.cliTableConfig.rounded);
    table.push(
      ...[
        ['ID', 'Time', 'Name', 'Domain', 'Port'].map((e) => chalk.dim(e)),
        ...userGameData.data.serverList.map((e) => [
          e.serverId,
          'UTC' + (JSON.parse(e.extension).offsetSeconds < 0 ? '' : '+') + JSON.parse(e.extension).offsetSeconds / 3600,
          e.serverName,
          JSON.parse(e.serverDomain)[0].host,
          JSON.parse(e.serverDomain)[0].port,
        ]),
      ],
    );
    console.log(table.toString());
  })();

  (() => {
    const table = new CliTable3(termPrettyUtils.cliTableConfig.rounded);
    table.push(
      ...[
        ['ID', 'UID', 'Lv', 'Found', 'Default', 'Nickname', 'Registered'].map((e) => chalk.dim(e)),
        ...userGameData.data.serverList.map((e) => [
          e.serverId,
          e.roleId,
          { hAlign: 'right' as const, content: e.level },
          Boolean(
            userGameBindingData.data.list
              .find((f) => f.appCode === 'endfield')!
              .bindingList[0]!.roles.find((f) => f.serverId === e.serverId),
          ),
          e.defaultChoose,
          userGameBindingData.data.list
            .find((f) => f.appCode === 'endfield')!
            .bindingList[0]!.roles.find((f) => f.serverId === e.serverId)?.nickName,
          userGameBindingData.data.list
            .find((f) => f.appCode === 'endfield')!
            .bindingList[0]!.roles.find((f) => f.serverId === e.serverId)?.registerTs
            ? DateTime.fromSeconds(
                userGameBindingData.data.list
                  .find((f) => f.appCode === 'endfield')!
                  .bindingList[0]!.roles.find((f) => f.serverId === e.serverId)?.registerTs!,
              ).toFormat('yyyy/MM/dd HH:mm:ss')
            : '',
        ]),
      ],
    );
    console.log(table.toString());
  })();

  (() => {
    const table = new CliTable3(termPrettyUtils.cliTableConfig.rounded);
    table.push(
      ...[
        ['Pool ID', 'Pool Name', 'Pulls', '*6', '*5', '*4', 'Pity *6', '*5', 'Latest'].map((e) => chalk.dim(e)),
        ...gachaPoolInfoList.map((e) => [
          e.poolId,
          e.pool_name ?? gachaRecordRsp.find((f) => f.poolId === e.poolId)!.poolName,
          ...[
            gachaRecordRsp.filter((f) => f.poolId === e.poolId).length,
            gachaRecordRsp.filter((f) => f.poolId === e.poolId && f.rarity === 6).length,
            gachaRecordRsp.filter((f) => f.poolId === e.poolId && f.rarity === 5).length,
            gachaRecordRsp.filter((f) => f.poolId === e.poolId && f.rarity === 4).length,
            (() => {
              let counter = 0;
              for (const pullEntry of gachaRecordRsp.filter((f) => f.poolId === e.poolId).toReversed()) {
                if (pullEntry.rarity >= 6) break;
                counter++;
              }
              return String(counter);
            })(),
            (() => {
              let counter = 0;
              for (const pullEntry of gachaRecordRsp.filter((f) => f.poolId === e.poolId).toReversed()) {
                if (pullEntry.rarity >= 5) break;
                counter++;
              }
              return String(counter);
            })(),
          ].map((f) => ({ hAlign: 'right' as const, content: f })),
          (() => {
            const latestRecord = gachaRecordRsp.filter((f) => f.poolId === e.poolId).at(-1)!;
            const color = latestRecord.rarity === 6 ? chalk.yellow : chalk.magenta;
            return color(`*${latestRecord.rarity} ${latestRecord.charName}`);
          })(),
        ]),
      ],
    );
    console.log(table.toString());
  })();

  (() => {
    const tableData: (string | number)[][] = [];
    tableData.push(['Pool ID', 'Pool Name', 'Pulled', 'Pity', 'Character'].map((e) => chalk.dim(e)));
    for (const [gachaPoolInfoIndex, gachaPoolInfoEntry] of Object.entries(gachaPoolInfoList)) {
      const records = gachaRecordRsp.filter((e) => e.poolId === gachaPoolInfoEntry.poolId);
      const tableSubData: typeof tableData = [];
      let pityR6: number = 0;
      let pityR5: number = 0;
      for (const record of records) {
        pityR6++;
        pityR5++;
        if (record.rarity >= 5) {
          if (record.rarity === 6) {
            tableSubData.push([
              gachaPoolInfoEntry.poolId,
              gachaPoolInfoEntry.pool_name ?? records[0]!.poolName,
              DateTime.fromMillis(parseInt(record.gachaTs)).toFormat('yyyy/MM/dd hh:mm:ss'),
              record.rarity === 6 ? pityR6 : pityR5,
              record.rarity === 6
                ? chalk.yellow(`*${record.rarity} ${record.charName}`)
                : chalk.magenta(`*${record.rarity} ${record.charName}`),
            ]);
          }
          if (record.rarity === 6) pityR6 = 0;
          pityR5 = 0;
        }
      }
      tableData.push(...tableSubData.toReversed());
      if (parseInt(gachaPoolInfoIndex) < gachaPoolInfoList.length - 1) tableData.push(Array(4).fill(''));
    }
    const table = new CliTable3(termPrettyUtils.cliTableConfig.rounded);
    table.push(...tableData);
    console.log(table.toString());
  })();
}

export default mainCmdHandler;
