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

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

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

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

const defaultData: DS4SpellDataSourceData = {
    description: "",
    equipped: false,
    spellType: "spellcasting",
    bonus: "",
    spellCategory: "unset",
    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);
                },
            );
        },
    );
});