package theorycrafter.fitting

import eve.data.AttributeModifier
import eve.data.BoosterType
import eve.data.Effect
import kotlin.test.Test
import kotlin.test.assertFails


/**
 * Tests booster-related fitting functionality.
 */
class BoosterTest {


    /**
     * Tests a booster's effect on the character.
     */
    @Test
    fun testBoosterEffectOnCharacter() = runFittingTest {
        val charAttribute = attribute()
        characterType {
            attributeValue(charAttribute, 100.0)
        }

        val boosterAttribute = attribute()
        val boosterType = boosterType {
            attributeValue(boosterAttribute, 2.0)
            effectReference(
                effectOnCharacter(
                    category = Effect.Category.ALWAYS,
                    modifiedAttribute = charAttribute,
                    modifyingAttribute = boosterAttribute,
                    operation = AttributeModifier.Operation.POST_MULTIPLY
                )
            )
        }

        val shipType = testShipType()

        val (fit, _) = fit(shipType)
        modify {
            fit.fitBooster(boosterType)
        }

        val character = fit.character
        character.assertPropertyEquals(
            attribute = charAttribute,
            expected = 200.0,
            message = "Incorrect booster on character effect application"
        )
    }


    /**
     * Tests a booster's effect on the ship.
     */
    @Test
    fun testBoosterEffectOnShip() = runFittingTest {
        val shipAttribute = attribute()
        val shipType = testShipType {
            attributeValue(shipAttribute, 100.0)
        }

        val boosterAttribute = attribute()
        val boosterType = boosterType {
            attributeValue(boosterAttribute, 2.0)
            effectReference(
                effectOnShip(
                    category = Effect.Category.ALWAYS,
                    modifiedAttribute = shipAttribute,
                    modifyingAttribute = boosterAttribute,
                    operation = AttributeModifier.Operation.POST_MULTIPLY
                )
            )
        }

        val (fit, _) = fit(shipType)
        modify {
            fit.fitBooster(boosterType)
        }

        val ship = fit.ship
        ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = 200.0,
            message = "Incorrect booster on ship effect application"
        )
    }


    /**
     * Tests a booster's effect on a module.
     */
    @Test
    fun testBoosterEffectOnModule() = runFittingTest {
        val moduleAttribute = attribute()
        val moduleType = moduleType {
            attributeValue(moduleAttribute, 50.0)
        }

        val shipType = testShipType()

        val boosterAttribute = attribute()
        val boosterType = boosterType {
            attributeValue(boosterAttribute, 2.0)
            effectReference(
                effectOnModules(
                    modifiedAttribute = moduleAttribute,
                    modifyingAttribute = boosterAttribute,
                    operation = AttributeModifier.Operation.POST_MULTIPLY
                )
            )
        }

        val (fit, module1) = fit(shipType, moduleType)
        modify {
            fit.fitBooster(boosterType)
        }

        module1.assertPropertyEquals(
            attribute = moduleAttribute,
            expected = 100.0,
            message = "Incorrect booster on module effect application"
        )

        val module2 = modify {
            fit.fitModule(moduleType, 1)
        }
        module2.assertPropertyEquals(
            attribute = moduleAttribute,
            expected = 100.0,
            message = "Incorrect booster on module (fitted afterwards) effect application"
        )

    }


    /**
     * Tests a booster's effect on charges.
     */
    @Test
    fun testBoosterEffectOnCharge() = runFittingTest {
        val chargeGroup = newTestGroup()
        val chargeAttribute = attribute()

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

        val moduleType = moduleTypeLoadableWithCharges(
            chargeGroupId = chargeGroup.id
        )

        val boosterAttribute = attribute()
        val boosterType = boosterType {
            attributeValue(boosterAttribute, 2.0)
            effectReference(
                effectOnCharges(
                    category = Effect.Category.ALWAYS,
                    modifiedAttribute = chargeAttribute,
                    modifyingAttribute = boosterAttribute,
                    operation = AttributeModifier.Operation.POST_MULTIPLY
                )
            )
        }

        val shipType = testShipType()

        val (fit, _) = fit(shipType)
        val (module, charge1) = modify {
            fit.fitBooster(boosterType)
            val module = fit.fitModule(moduleType, 0)
            val charge = module.setCharge(chargeType)
            module to charge
        }

        charge1.assertPropertyEquals(
            attribute = chargeAttribute,
            expected = 200.0,
            message = "Incorrect booster on charge effect application"
        )

        val charge2 = modify {
            module.removeCharge()
            module.setCharge(chargeType)
        }
        charge2.assertPropertyEquals(
            attribute = chargeAttribute,
            expected = 200.0,
            message = "Incorrect booster on charge (fitted afterwards) effect application"
        )
    }


    /**
     * Tests fitting a booster into an already taken slot.
     */
    @Test
    fun testFittingBoosterIntoTakenSlot() = runFittingTest {
        val shipType = testShipType()

        val boosterType = boosterType(slot = 1)

        val (fit, _) = fit(shipType)
        modify {
            fit.fitBooster(boosterType)
        }

        assertFails {
            modify {
                fit.fitBooster(boosterType)
            }
        }
    }


    /**
     * Tests a disabled booster's affects.
     */
    @Test
    fun testDisabledBooster() = runFittingTest {
        val shipAttribute = attribute()
        val shipType = testShipType {
            attributeValue(shipAttribute, 100.0)
        }

        val boosterAttribute = attribute()
        val boosterType = boosterType {
            attributeValue(boosterAttribute, 2.0)
            effectReference(
                effectOnShip(
                    category = Effect.Category.ALWAYS,
                    modifiedAttribute = shipAttribute,
                    modifyingAttribute = boosterAttribute,
                    operation = AttributeModifier.Operation.POST_MULTIPLY
                )
            )
        }

        val (fit, _) = fit(shipType)
        val booster = modify {
            fit.fitBooster(boosterType).also {
                it.setEnabled(false)
            }
        }

        val ship = fit.ship
        ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = shipType.attributeValue(shipAttribute),
            message = "Disabled booster has effect on ship"
        )

        modify {
            booster.setEnabled(true)
        }

        ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = 200.0,
            message = "(Re)enabled booster has wrong on ship effect"
        )

        modify {
            booster.setEnabled(false)
        }
        ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = shipType.attributeValue(shipAttribute),
            message = "Disabled booster has effect on ship"
        )
    }


    /**
     * Tests booster side effects.
     */
    @Test
    fun testBoosterSideEffects() = runFittingTest {
        val shipAttribute = attribute()
        val boosterAttribute = attribute()

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

        val effect = effectOnShip(
            category = Effect.Category.ALWAYS,
            modifiedAttribute = shipAttribute,
            modifyingAttribute = boosterAttribute,
            operation = AttributeModifier.Operation.ADD_PERCENT
        )
        val sideEffect = BoosterType.SideEffect(
            effectId = effect.id,
            penalizedAttribute = shipAttribute,
            penalizingAttribute = boosterAttribute,
        )

        val boosterType = boosterType(
            sideEffects = listOf(sideEffect)
        ) {
            attributeValue(boosterAttribute, -20.0)
            effectReference(effect)
        }

        val (fit, _) = fit(shipType)
        val booster = modify {
            fit.fitBooster(boosterType).also {
                it.setSideEffectActive(sideEffect, false)
            }
        }

        val ship = fit.ship
        ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = 100.0,
            message = "Booster side effect applied despite being inactive"
        )

        modify {
            booster.setSideEffectActive(sideEffect, true)
        }

        ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = 80.0,
            message = "Booster side effect didn't apply despite being active"
        )
    }


}