// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT

import { describe, expect, it } from "vitest";

import { calculateSpellPrice } from "../../../../src/documents/item/spell/calculate-spell-price";

import type { CooldownDuration, DS4SpellDataSourceData } from "../../../../src/documents/item/spell/spell-data-source";

const defaultData: DS4SpellDataSourceData = {
  description: "",
  equipped: false,
  spellType: "spellcasting",
  spellModifier: {
    numerical: 0,
    complex: "",
  },
  allowsDefense: false,
  spellGroups: {
    lightning: false,
    earth: false,
    water: false,
    ice: false,
    fire: false,
    healing: false,
    light: false,
    air: false,
    transport: false,
    damage: false,
    shadow: false,
    protection: false,
    mindAffecting: false,
    demonology: false,
    necromancy: false,
    transmutation: false,
    area: false,
  },
  maxDistance: {
    value: "",
    unit: "meter",
  },
  effectRadius: {
    value: "",
    unit: "meter",
  },
  duration: {
    value: "",
    unit: "custom",
  },
  cooldownDuration: "0r",
  minimumLevels: {
    healer: null,
    wizard: null,
    sorcerer: null,
  },
};

type TestCase = {
  minimumLevel: number | null;
  expected: number | null;
};

type CombinedTestCase = {
  minimumLevels: DS4SpellDataSourceData["minimumLevels"];
  expected: number | null;
  description: string;
};

const testCases: Record<keyof DS4SpellDataSourceData["minimumLevels"], TestCase[]> = {
  healer: [
    { minimumLevel: null, expected: null },
    { minimumLevel: 1, expected: 10 },
    { minimumLevel: 2, expected: 45 },
    { minimumLevel: 3, expected: 80 },
    { minimumLevel: 4, expected: 115 },
    { minimumLevel: 5, expected: 150 },
    { minimumLevel: 6, expected: 185 },
    { minimumLevel: 7, expected: 220 },
    { minimumLevel: 8, expected: 255 },
    { minimumLevel: 9, expected: 290 },
    { minimumLevel: 10, expected: 325 },
    { minimumLevel: 11, expected: 360 },
    { minimumLevel: 12, expected: 395 },
    { minimumLevel: 13, expected: 430 },
    { minimumLevel: 14, expected: 465 },
    { minimumLevel: 15, expected: 500 },
    { minimumLevel: 16, expected: 535 },
    { minimumLevel: 17, expected: 570 },
    { minimumLevel: 18, expected: 605 },
    { minimumLevel: 19, expected: 640 },
    { minimumLevel: 20, expected: 675 },
  ],
  sorcerer: [
    { minimumLevel: null, expected: null },
    { minimumLevel: 1, expected: 10 },
    { minimumLevel: 2, expected: 75 },
    { minimumLevel: 3, expected: 140 },
    { minimumLevel: 4, expected: 205 },
    { minimumLevel: 5, expected: 270 },
    { minimumLevel: 6, expected: 335 },
    { minimumLevel: 7, expected: 400 },
    { minimumLevel: 8, expected: 465 },
    { minimumLevel: 9, expected: 530 },
    { minimumLevel: 10, expected: 595 },
    { minimumLevel: 11, expected: 660 },
    { minimumLevel: 12, expected: 725 },
    { minimumLevel: 13, expected: 790 },
    { minimumLevel: 14, expected: 855 },
    { minimumLevel: 15, expected: 920 },
    { minimumLevel: 16, expected: 985 },
    { minimumLevel: 17, expected: 1050 },
    { minimumLevel: 18, expected: 1115 },
    { minimumLevel: 19, expected: 1180 },
    { minimumLevel: 20, expected: 1245 },
  ],
  wizard: [
    { minimumLevel: null, expected: null },
    { minimumLevel: 1, expected: 10 },
    { minimumLevel: 2, expected: 60 },
    { minimumLevel: 3, expected: 110 },
    { minimumLevel: 4, expected: 160 },
    { minimumLevel: 5, expected: 210 },
    { minimumLevel: 6, expected: 260 },
    { minimumLevel: 7, expected: 310 },
    { minimumLevel: 8, expected: 360 },
    { minimumLevel: 9, expected: 410 },
    { minimumLevel: 10, expected: 460 },
    { minimumLevel: 11, expected: 510 },
    { minimumLevel: 12, expected: 560 },
    { minimumLevel: 13, expected: 610 },
    { minimumLevel: 14, expected: 660 },
    { minimumLevel: 15, expected: 710 },
    { minimumLevel: 16, expected: 760 },
    { minimumLevel: 17, expected: 810 },
    { minimumLevel: 18, expected: 860 },
    { minimumLevel: 19, expected: 910 },
    { minimumLevel: 20, expected: 960 },
  ],
};

function buildCombinedTestCases(): CombinedTestCase[] {
  const combinedTestCases: CombinedTestCase[] = [];

  // permutation test cases
  const isRelevantPermutationTestCase = (t: TestCase) =>
    ([null, 1, 10, 20] as (number | null)[]).includes(t.minimumLevel);

  for (const healerTestCase of testCases.healer.filter(isRelevantPermutationTestCase)) {
    for (const sorcererTestCase of testCases.sorcerer.filter(isRelevantPermutationTestCase)) {
      for (const wizardTestCase of testCases.wizard.filter(isRelevantPermutationTestCase)) {
        const expected =
          healerTestCase.expected !== null || sorcererTestCase.expected !== null || wizardTestCase.expected !== null
            ? Math.min(
                healerTestCase.expected ?? Infinity,
                sorcererTestCase.expected ?? Infinity,
                wizardTestCase.expected ?? Infinity,
              )
            : null;
        const minimumLevels = {
          healer: healerTestCase.minimumLevel,
          sorcerer: sorcererTestCase.minimumLevel,
          wizard: wizardTestCase.minimumLevel,
        };
        const description = JSON.stringify(minimumLevels);
        combinedTestCases.push({
          minimumLevels,
          expected,
          description,
        });
      }
    }
  }

  // single test cases
  const isRelevantSingleTestCase = (t: TestCase) => t.minimumLevel !== null;

  for (const spellCasterClass of ["healer", "sorcerer", "wizard"] as const) {
    for (const testCase of testCases[spellCasterClass].filter(isRelevantSingleTestCase)) {
      const minimumLevels = {
        ...defaultData.minimumLevels,
        [spellCasterClass]: testCase.minimumLevel,
      };
      const description = JSON.stringify(minimumLevels);
      combinedTestCases.push({
        minimumLevels,
        expected: testCase.expected,
        description,
      });
    }
  }

  return combinedTestCases;
}

describe("calculateSpellPrice", () => {
  const cooldownDurations: { cooldownDuration: CooldownDuration; factor: number }[] = [
    { cooldownDuration: "0r", factor: 1 },
    { cooldownDuration: "1r", factor: 1 },
    { cooldownDuration: "2r", factor: 1 },
    { cooldownDuration: "5r", factor: 1 },
    { cooldownDuration: "10r", factor: 1 },
    { cooldownDuration: "100r", factor: 1 },
    { cooldownDuration: "1d", factor: 2 },
    { cooldownDuration: "d20d", factor: 3 },
  ];

  describe.each(cooldownDurations)(
    "with cooldown duration set to $cooldownDuration",
    ({ cooldownDuration, factor }) => {
      const dataWithCooldownDuration = {
        ...defaultData,
        cooldownDuration,
      };

      it.each(buildCombinedTestCases())(
        `returns ${factor} × $expected if the minimum leves are $description`,
        ({ minimumLevels, expected }) => {
          // given
          const data: DS4SpellDataSourceData = {
            ...dataWithCooldownDuration,
            minimumLevels,
          };

          // when
          const spellPrice = calculateSpellPrice(data);

          // then
          expect(spellPrice).toBe(expected !== null ? expected * factor : expected);
        },
      );
    },
  );
});
