import { RankTracker } from "./RankTracker.ts";
import { assertEquals } from "../dev_deps.ts";
import { BattleListNode } from "./types.ts";
import { base64 } from "../deps.ts";
import { gameId } from "./utils.ts";
import { RankState } from "./state.ts";

const INIT_STATE: RankState = {
  gameId: await gameId(genId(0)),
  rank: "B-",
  rankPoint: 100,
};

class TestRankTracker extends RankTracker {
  testGet() {
    const { state, deltaMap } = this;
    return {
      state,
      deltaMap,
    };
  }
}

function genId(id: number, date = "20220101"): string {
  return base64.encode(
    `VsHistoryDetail-asdf:asdf:${date}T${
      id.toString().padStart(6, "0")
    }_------------------------------------`,
  );
}

function genOpenWins(
  { startId, count, udemae }: {
    startId: number;
    count: number;
    udemae: string;
  },
) {
  const result: BattleListNode[] = [];
  let id = startId;

  for (let i = 0; i < count; i++) {
    result.push({
      id: genId(id),
      udemae,
      judgement: "WIN",
      bankaraMatch: {
        earnedUdemaePoint: 8,
      },
    });
    id += 1;
  }

  return result;
}

Deno.test("RankTracker don't promo after failed challenge", async () => {
  const tracker = new TestRankTracker(undefined);
  assertEquals(tracker.testGet(), {
    state: undefined,
    deltaMap: new Map(),
  });

  const finalState = await tracker.updateState([{
    xMatchMeasurement: null,
    bankaraMatchChallenge: {
      winCount: 0,
      loseCount: 3,
      maxWinCount: 3,
      maxLoseCount: 3,
      state: "FAILED",
      isPromo: true,
      isUdemaeUp: false,
      udemaeAfter: "B+",
      earnedUdemaePoint: null,
    },
    historyDetails: {
      nodes: [{
        id: genId(1),
        udemae: "B+",
        judgement: "LOSE",
        bankaraMatch: {
          earnedUdemaePoint: null,
        },
      }, {
        id: genId(0),
        udemae: "B+",
        judgement: "LOSE",
        bankaraMatch: {
          earnedUdemaePoint: null,
        },
      }],
    },
  }]);

  assertEquals(finalState, undefined);
});

Deno.test("RankTracker autotrack after promotion", async () => {
  const tracker = new TestRankTracker(undefined);
  assertEquals(tracker.testGet(), {
    state: undefined,
    deltaMap: new Map(),
  });

  const finalState = await tracker.updateState([{
    xMatchMeasurement: null,
    bankaraMatchChallenge: {
      winCount: 3,
      loseCount: 0,
      maxWinCount: 3,
      maxLoseCount: 3,
      state: "SUCCEEDED",
      isPromo: true,
      isUdemaeUp: true,
      udemaeAfter: "A-",
      earnedUdemaePoint: -233,
    },
    historyDetails: {
      nodes: [{
        id: genId(1),
        udemae: "B+",
        judgement: "WIN",
        bankaraMatch: {
          earnedUdemaePoint: null,
        },
      }, {
        id: genId(0),
        udemae: "B+",
        judgement: "WIN",
        bankaraMatch: {
          earnedUdemaePoint: null,
        },
      }],
    },
  }]);

  const gameId1 = await gameId(genId(1));
  assertEquals(finalState, {
    gameId: gameId1,
    rank: "A-",
    rankPoint: 200,
    timestamp: 1640995201,
  });

  assertEquals(await tracker.getRankStateById(genId(1)), {
    before: {
      gameId: await gameId(genId(0)),
      rank: "B+",
      rankPoint: 433,
      timestamp: 1640995200,
    },
    after: {
      gameId: gameId1,
      rank: "A-",
      rankPoint: 200,
      timestamp: 1640995201,
    },
  });
});

Deno.test("RankTracker issue #36", async () => {
  const tracker = new TestRankTracker(undefined);
  assertEquals(tracker.testGet(), {
    state: undefined,
    deltaMap: new Map(),
  });

  const finalState = await tracker.updateState([{
    xMatchMeasurement: null,
    bankaraMatchChallenge: {
      winCount: 3,
      loseCount: 0,
      maxWinCount: 3,
      maxLoseCount: 3,
      state: "SUCCEEDED",
      isPromo: true,
      isUdemaeUp: true,
      udemaeAfter: "S+20",
      earnedUdemaePoint: -3450,
    },
    historyDetails: {
      nodes: [{
        id: genId(2),
        udemae: "S+19",
        judgement: "WIN",
        bankaraMatch: {
          earnedUdemaePoint: null,
        },
      }, {
        id: genId(1),
        udemae: "S+19",
        judgement: "WIN",
        bankaraMatch: {
          earnedUdemaePoint: null,
        },
      }, {
        id: genId(0),
        udemae: "S+19",
        judgement: "WIN",
        bankaraMatch: {
          earnedUdemaePoint: null,
        },
      }],
    },
  }]);

  const gameId2 = await gameId(genId(2));
  assertEquals(finalState, {
    gameId: gameId2,
    rank: "S+20",
    rankPoint: 300,
    timestamp: 1640995202,
  });

  assertEquals(await tracker.getRankStateById(genId(0)), undefined);
  assertEquals(await tracker.getRankStateById(genId(1)), undefined);

  assertEquals(await tracker.getRankStateById(genId(2)), {
    before: {
      gameId: await gameId(genId(1)),
      rank: "S+19",
      rankPoint: 3750,
      timestamp: 1640995201,
    },
    after: {
      gameId: gameId2,
      rank: "S+20",
      rankPoint: 300,
      timestamp: 1640995202,
    },
  });
});

Deno.test("RankTracker tracks promotion, ignoring INPROGRESS", async () => {
  const INIT_STATE = {
    gameId: await gameId(genId(0)),
    rank: "B+",
    rankPoint: 850,
    timestamp: 1640995201,
  };

  const tracker = new TestRankTracker(INIT_STATE);
  assertEquals(tracker.testGet(), {
    state: INIT_STATE,
    deltaMap: new Map(),
  });

  const finalState = await tracker.updateState([{
    xMatchMeasurement: null,
    bankaraMatchChallenge: {
      winCount: 2,
      loseCount: 0,
      maxWinCount: 3,
      maxLoseCount: 3,
      state: "INPROGRESS",
      isPromo: true,
      isUdemaeUp: true,
      udemaeAfter: "A-",
      earnedUdemaePoint: null,
    },
    historyDetails: {
      nodes: [{
        id: genId(1),
        udemae: "B+",
        judgement: "WIN",
        bankaraMatch: {
          earnedUdemaePoint: null,
        },
      }, {
        id: genId(0),
        udemae: "B+",
        judgement: "WIN",
        bankaraMatch: {
          earnedUdemaePoint: null,
        },
      }],
    },
  }]);

  assertEquals(finalState, {
    gameId: await gameId(genId(1)),
    rank: "B+",
    rankPoint: 850,
    timestamp: 1640995201,
  });
});

Deno.test("RankTracker tracks promotion", async () => {
  const INIT_STATE = {
    gameId: await gameId(genId(0)),
    rank: "B+",
    rankPoint: 850,
  };

  const tracker = new TestRankTracker(INIT_STATE);
  assertEquals(tracker.testGet(), {
    state: INIT_STATE,
    deltaMap: new Map(),
  });

  const finalState = await tracker.updateState([{
    xMatchMeasurement: null,
    bankaraMatchChallenge: {
      winCount: 3,
      loseCount: 0,
      maxWinCount: 3,
      maxLoseCount: 3,
      state: "SUCCEEDED",
      isPromo: true,
      isUdemaeUp: true,
      udemaeAfter: "A-",
      earnedUdemaePoint: null,
    },
    historyDetails: {
      nodes: [{
        id: genId(2),
        udemae: "B+",
        judgement: "WIN",
        bankaraMatch: {
          earnedUdemaePoint: null,
        },
      }, {
        id: genId(1),
        udemae: "B+",
        judgement: "WIN",
        bankaraMatch: {
          earnedUdemaePoint: null,
        },
      }, {
        id: genId(0),
        udemae: "B+",
        judgement: "WIN",
        bankaraMatch: {
          earnedUdemaePoint: null,
        },
      }],
    },
  }]);

  assertEquals(finalState, {
    gameId: await gameId(genId(2)),
    rank: "A-",
    rankPoint: 200,
    timestamp: 1640995202,
  });
});

Deno.test("RankTracker tracks challenge charge", async () => {
  const tracker = new TestRankTracker(INIT_STATE);
  assertEquals(tracker.testGet(), {
    state: INIT_STATE,
    deltaMap: new Map(),
  });

  const finalState = await tracker.updateState([{
    xMatchMeasurement: null,
    bankaraMatchChallenge: {
      winCount: 1,
      loseCount: 0,
      maxWinCount: 5,
      maxLoseCount: 3,
      isPromo: false,
      isUdemaeUp: false,
      udemaeAfter: null,
      earnedUdemaePoint: null,
      state: "INPROGRESS",
    },
    historyDetails: {
      nodes: [
        {
          id: genId(2),
          udemae: "B-",
          judgement: "WIN",
          bankaraMatch: {
            earnedUdemaePoint: null,
          },
        },
      ],
    },
  }, {
    xMatchMeasurement: null,
    bankaraMatchChallenge: null,
    historyDetails: {
      nodes: genOpenWins({
        startId: 0,
        count: 1,
        udemae: "B-",
      }),
    },
  }]);

  assertEquals(tracker.testGet().state, INIT_STATE);

  assertEquals(finalState, {
    gameId: await gameId(genId(2)),
    rank: "B-",
    rankPoint: 45,
    timestamp: 1640995202,
  });
});

Deno.test("RankTracker", async () => {
  const tracker = new TestRankTracker(INIT_STATE);
  assertEquals(tracker.testGet(), {
    state: INIT_STATE,
    deltaMap: new Map(),
  });

  const finalState = await tracker.updateState([{
    xMatchMeasurement: null,
    bankaraMatchChallenge: null,
    historyDetails: {
      nodes: [...genOpenWins({
        startId: 0,
        count: 19,
        udemae: "B-",
      })].reverse(),
    },
  }]);

  assertEquals(tracker.testGet().state, INIT_STATE);

  assertEquals(finalState, {
    gameId: await gameId(genId(18)),
    rank: "B-",
    rankPoint: 244,
    timestamp: 1640995218,
  });

  assertEquals((await tracker.getRankStateById(genId(1)))?.after, {
    gameId: await gameId(genId(1)),
    rank: "B-",
    rankPoint: 108,
    timestamp: 1640995201,
  });

  assertEquals((await tracker.getRankStateById(genId(17)))?.after, {
    gameId: await gameId(genId(17)),
    rank: "B-",
    rankPoint: 236,
    timestamp: 1640995217,
  });

  tracker.setState(finalState);

  assertEquals(tracker.testGet().state, finalState);

  // history goes too far
  const finalState2 = await tracker.updateState([{
    xMatchMeasurement: null,
    bankaraMatchChallenge: null,
    historyDetails: {
      nodes: [...genOpenWins({
        startId: 30,
        count: 1,
        udemae: "B-",
      })].reverse(),
    },
  }]);
  assertEquals(finalState2, undefined);

  await tracker.updateState([{
    xMatchMeasurement: null,
    bankaraMatchChallenge: null,
    historyDetails: {
      nodes: [...genOpenWins({
        startId: 0,
        count: 30,
        udemae: "B-",
      })].reverse(),
    },
  }]);

  assertEquals((await tracker.getRankStateById(genId(29)))?.after, {
    gameId: await gameId(genId(29)),
    rank: "B-",
    rankPoint: 332,
    timestamp: 1640995229,
  });
});

Deno.test("RankTracker clears state when season changes", async () => {
  const firstDay = new Date("2022-09-09T00:00:00+00:00").getTime() / 1000;
  const firstSeason = {
    ...INIT_STATE,
    timestamp: firstDay,
  };
  const tracker = new TestRankTracker(firstSeason);
  assertEquals(tracker.testGet(), {
    state: firstSeason,
    deltaMap: new Map(),
  });

  const afterState = await tracker.updateState([{
    xMatchMeasurement: null,
    bankaraMatchChallenge: {
      winCount: 3,
      loseCount: 0,
      maxWinCount: 3,
      maxLoseCount: 3,
      state: "SUCCEEDED",
      isPromo: true,
      isUdemaeUp: true,
      udemaeAfter: "B-",
      earnedUdemaePoint: 1,
    },
    historyDetails: {
      nodes: [{
        id: genId(1, "20221209"),
        udemae: "B-",
        judgement: "WIN",
        bankaraMatch: {
          earnedUdemaePoint: 8,
        },
      }, {
        id: genId(0),
        udemae: "B-",
        judgement: "WIN",
        bankaraMatch: {
          earnedUdemaePoint: 8,
        },
      }],
    },
  }]);

  assertEquals(afterState, undefined);
});
