package theorycrafter.fitting

import eve.data.*
import kotlin.test.Test


/**
 * Tests skill-related fitting functionality
 */
class SkillTest {


    /**
     * Tests a skill that bonuses all modules.
     */
    @Test
    fun testSkillEffectOnModule() = runFittingTest {
        val shipAttribute = attribute()
        val moduleAttribute = attribute()

        testSkillType(skillLevel = 5) {
            skillBonus(
                baseSkillBonusValue = 10.0,
                attribute = moduleAttribute,
                itemType = AttributeModifier.AffectedItemKind.MODULES,
            )
        }

        val moduleType = moduleType {
            attributeValue(moduleAttribute, 100.0)
            effectReference(
                effectOnShip(
                    category = Effect.Category.ONLINE,
                    modifiedAttribute = shipAttribute,
                    modifyingAttribute = moduleAttribute,
                    operation = AttributeModifier.Operation.ADD_PERCENT
                )
            )
        }

        val shipType = testShipType {
            attributeValue(shipAttribute, 200.0)
        }


        val (fit, _) = fit(shipType, moduleType)

        fit.ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = 500.0,
            message = "Incorrect skill effect application"
        )
    }


    /**
     * Tests a skill that bonuses modules in a certain group (via [AttributeModifier.groupId]).
     */
    @Test
    fun testSkillEffectWithGroupIdOnModule() = runFittingTest {
        val shipAttribute = attribute()
        val moduleAttribute = attribute()

        val affectedGroup = newTestGroup()

        testSkillType(skillLevel = 5) {
            skillBonus(
                baseSkillBonusValue = 10.0,
                attribute = moduleAttribute,
                itemType = AttributeModifier.AffectedItemKind.MODULES,
                groupId = affectedGroup.id
            )
        }

        fun makeModule(group: TypeGroup) = moduleType(
            group = group,
        ) {
            attributeValue(moduleAttribute, 100.0)
            effectReference(
                effectOnShip(
                    category = Effect.Category.ALWAYS,
                    modifiedAttribute = shipAttribute,
                    modifyingAttribute = moduleAttribute,
                    operation = AttributeModifier.Operation.ADD_PERCENT
                )
            )
        }

        val affectedModuleType = makeModule(group = affectedGroup)
        val unaffectedModuleType = makeModule(group = newTestGroup())

        val shipType = testShipType {
            attributeValue(shipAttribute, 200.0)
        }

        val (fit, affectedModule) = fit(shipType, affectedModuleType)

        affectedModule.assertPropertyEquals(
            attribute = moduleAttribute,
            expected = 150.0,
            message = "Skill effect with groupId did not apply to module correctly"
        )
        fit.ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = 500.0,
            message = "Skill effect with groupId did not apply to ship (via module) correctly"
        )

        val unaffectedModule = modify {
            fit.removeModule(affectedModule)
            fit.fitModule(unaffectedModuleType, 0)
        }
        unaffectedModule.assertPropertyEquals(
            attribute = moduleAttribute,
            expected = 100.0,
            message = "Skill effect with groupId applied to module with a different group id"
        )
        fit.ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = 400.0,  // The skill doesn't apply to unaffectedModule, but the module should still add 100%
            message = "Skill effect with groupId applied to ship via module with a different group id"
        )
    }


    /**
     * Tests a skill that bonuses modules that require the skill (via [AttributeModifier.skillTypeId]).
     */
    @Test
    fun testSkillEffectWithSkillTypeIdOnModule() = runFittingTest {
        val shipAttribute = attribute()
        val moduleAttribute = attribute()

        val affectedSkillId = reserveAvailableItemId()

        testSkillType(itemId = affectedSkillId, skillLevel = 5) {
            skillBonus(
                baseSkillBonusValue = 10.0,
                attribute = moduleAttribute,
                itemType = AttributeModifier.AffectedItemKind.MODULES,
                skillTypeId = affectedSkillId
            )
        }

        fun makeModule(requiredSkillId: Int? = null) = moduleType(flags = ModuleFlags.ACTIVE) {
            if (requiredSkillId != null)
                requiresSkill(skillRequirementIndex = 0, skillId = requiredSkillId, skillLevel = 1)

            attributeValue(moduleAttribute, 100.0)
            effectReference(
                effectOnShip(
                    category = Effect.Category.ACTIVE,
                    modifiedAttribute = shipAttribute,
                    modifyingAttribute = moduleAttribute,
                    operation = AttributeModifier.Operation.ADD_PERCENT
                )
            )
        }

        val affectedModuleType = makeModule(requiredSkillId = affectedSkillId)
        val unaffectedModuleType = makeModule(requiredSkillId = null)

        val shipType = testShipType {
            attributeValue(shipAttribute, 200.0)
        }

        val (fit, modules) = fit(shipType, affectedModuleType, unaffectedModuleType)
        val (affectedModule, unaffectedModule) = modules

        modify {
            affectedModule.setState(Module.State.ACTIVE)
            unaffectedModule.setState(Module.State.ONLINE)
        }
        affectedModule.assertPropertyEquals(
            attribute = moduleAttribute,
            expected = 150.0,
            message = "Skill effect with skillTypeId did not apply to module correctly"
        )
        fit.ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = 500.0,
            message = "Skill effect with skillTypeId did not apply to ship (via module) correctly"
        )

        modify {
            affectedModule.setState(Module.State.ONLINE)
            unaffectedModule.setState(Module.State.ACTIVE)
        }
        unaffectedModule.assertPropertyEquals(
            attribute = moduleAttribute,
            expected = 100.0,
            message = "Skill effect with skillTypeId applied to module that does require that skill"
        )
        fit.ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = 400.0,
            message = "Skill effect with skillTypeId applied to ship via module that does not require that skill"
        )
    }


    /**
     * Tests a skill that bonuses all ships.
     */
    @Test
    fun testSkillEffectOnShip() = runFittingTest {
        val shipAttribute = attribute()

        testSkillType(skillLevel = 5) {
            skillBonus(
                baseSkillBonusValue = 10.0,
                attribute = shipAttribute,
                itemType = AttributeModifier.AffectedItemKind.SHIP,
            )
        }

        val shipType = testShipType {
            attributeValue(shipAttribute, 100.0)
        }


        val (fit, _) = fit(shipType)

        fit.ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = 150.0,
            message = "Incorrect skill effect application"
        )
    }


    /**
     * Tests a skill that bonuses ships in a certain group (via [AttributeModifier.groupId]).
     */
    @Test
    fun testSkillEffectWithGroupIdOnShip() = runFittingTest {
        val shipAttribute = attribute()

        val affectedGroup = newTestGroup()

        testSkillType(skillLevel = 5) {
            skillBonus(
                baseSkillBonusValue = 10.0,
                attribute = shipAttribute,
                itemType = AttributeModifier.AffectedItemKind.SHIP,
                groupId = affectedGroup.id,
            )
        }

        fun makeShip(group: TypeGroup) = testShipType(group = group){
            attributeValue(shipAttribute, 100.0)
        }

        val affectedShip = makeShip(group = affectedGroup)
        val unaffectedShip = makeShip(group = newTestGroup())

        val (affectedFit, _) = fit(affectedShip)
        val (unaffectedFit, _) = fit(unaffectedShip)

        affectedFit.ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = 150.0,
            message = "Incorrect skill effect application"
        )
        unaffectedFit.ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = 100.0,
            message = "Skill effect with groupId applied to ship with a different group id"
        )
    }


    /**
     * Tests a skill that bonuses ships in a certain group (via [AttributeModifier.groupId]).
     */
    @Test
    fun testSkillEffectWithSkillTypeIdOnShip() = runFittingTest {
        val shipAttribute = attribute()

        val affectedSkillId = reserveAvailableItemId()

        testSkillType(skillLevel = 5) {
            skillBonus(
                baseSkillBonusValue = 10.0,
                attribute = shipAttribute,
                itemType = AttributeModifier.AffectedItemKind.SHIP,
                skillTypeId = affectedSkillId,
            )
        }

        fun makeShip(requiredSkillId: Int? = null) = testShipType{
            if (requiredSkillId != null)
                requiresSkill(skillRequirementIndex = 0, skillId = requiredSkillId, skillLevel = 1)
            attributeValue(shipAttribute, 100.0)
        }

        val affectedShip = makeShip(requiredSkillId = affectedSkillId)
        val unaffectedShip = makeShip(requiredSkillId = null)

        val (affectedFit, _) = fit(affectedShip)
        val (unaffectedFit, _) = fit(unaffectedShip)

        affectedFit.ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = 150.0,
            message = "Incorrect skill effect application"
        )
        unaffectedFit.ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = 100.0,
            message = "Skill effect with skillTypeId applied to ship that does not require that skill"
        )
    }


    /**
     * Tests a skill that bonuses a character attribute.
     */
    @Test
    fun testSkillEffectOnCharacter() = runFittingTest {
        val charAttribute = attribute()

        testSkillType(skillLevel = 5) {
            skillBonus(
                baseSkillBonusValue = 10.0,
                attribute = charAttribute,
                itemType = AttributeModifier.AffectedItemKind.CHARACTER,
            )
        }

        characterType {
            attributeValue(charAttribute, 100.0)
        }

        val shipType = testShipType()

        val (fit, _) = fit(shipType)

        fit.character.assertPropertyEquals(
            attribute = charAttribute,
            expected = 150.0,
            message = "Incorrect skill effect on character application"
        )
    }


    /**
     * Tests a skill that bonuses a character attribute via the ships.
     * This, for example, is the way the Ishtar's bonus to drone control range is implemented.
     * 1. The skill "Heavy Assault Cruisers" bonuses an attribute that all HACs (including the Ishtar) have:
     *    "eliteBonusHeavyGunship1".
     * 2. The Ishtar has an effect that modifies the character's "droneControlRange" attribute by adding the value of
     *    "droneControlRange" to it.
     * 3. Ships that do not have the attribute "eliteBonusHeavyGunship1" are unaffected.
     */
    @Test
    fun testSkillEffectOnCharacterViaShip() = runFittingTest {
        val shipAttribute = attribute()
        val charAttribute = attribute()

        testSkillType(skillLevel = 5) {
            skillBonus(
                baseSkillBonusValue = 10.0,
                attribute = shipAttribute,
                itemType = AttributeModifier.AffectedItemKind.SHIP,
            )
        }

        val affectedShip = testShipType {
            attributeValue(shipAttribute, 100.0)
            effectReference(
                effectOnCharacter(
                    modifiedAttribute = charAttribute,
                    modifyingAttribute = shipAttribute,
                    operation = AttributeModifier.Operation.ADD,
                )
            )
        }
        val unaffectedShip = testShipType()

        characterType {
            attributeValue(charAttribute, 100.0)
        }

        val (fit1, _) = fit(affectedShip)
        val (fit2, _) = fit(unaffectedShip)

        fit1.character.assertPropertyEquals(
            attribute = charAttribute,
            expected = 250.0,
            message = "Incorrect skill effect on character (via ship) application"
        )
        fit2.character.assertPropertyEquals(
            attribute = charAttribute,
            expected = 100.0,
            message = "Skill effect applied to character via ship that does not have the attribute modified by the skill"
        )
    }


    /**
     * Tests a skill that bonuses a charge attribute.
     */
    @Test
    fun testSkillEffectOnCharge() = runFittingTest{
        val chargeAttribute = attribute()

        testSkillType(skillLevel = 5) {
            skillBonus(
                baseSkillBonusValue = 10.0,
                attribute = chargeAttribute,
                itemType = AttributeModifier.AffectedItemKind.LAUNCHABLES,
            )
        }

        val chargeType = chargeType {
            attributeValue(chargeAttribute, 100.0)
        }

        val moduleType = moduleTypeLoadableWithCharges(chargeGroupId = chargeType.groupId)

        val shipType = testShipType()

        val (_, module) = fit(shipType, moduleType)
        val charge = modify {
            module.setCharge(chargeType)
        }

        charge.assertPropertyEquals(
            attribute = chargeAttribute,
            expected = 150.0,
            message = "Incorrect skill effect applied to charge"
        )
    }


}