package com.ghost.technic.server.game;

import com.ghost.technic.server.Player;
import com.ghost.technic.server.board.Battleline;
import com.ghost.technic.server.board.Flank;
import com.ghost.technic.server.card.effect.CardAbility;
import com.ghost.technic.server.card.effect.ConstantAbility;
import com.ghost.technic.server.card.keyword.BeforeFightAbility;
import com.ghost.technic.server.card.keyword.BeforeFightEffect;
import com.ghost.technic.server.card.keyword.KeywordAbility;
import com.ghost.technic.server.card.misc.House;
import com.ghost.technic.server.card.playable.PlayableCard;
import com.ghost.technic.server.card.playable.impl.UpgradeCard;
import com.ghost.technic.server.card.playable.usable.impl.CreatureCard;
import com.ghost.technic.server.game.event.*;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

@Slf4j
@Getter
@RequiredArgsConstructor
public class GameActionService {

    private final GameState gameState;

    public void forgeKey(Player player) {
        int baseCost = 6;
        int modifier = calculateKeyCostModifier(player);
        int totalCost = baseCost + modifier;

        if (player.getAmber() >= totalCost) {
            player.setAmber(player.getAmber() - totalCost);
            player.setKeysForged(player.getKeysForged() + 1);

            log.info("{} forges a key for {} æmber! Keys forged: {}", player.getName(), totalCost, player.getKeysForged());

            getEventBus().publish(new GameEvent(EventType.KEY_FORGED, Map.of(
                    "player", player,
                    "cost", totalCost
            )));
        } else {
            log.info("{} could not forge a key (needs {}, has {})", player.getName(), totalCost, player.getAmber());
        }
    }

    public int calculateKeyCostModifier(Player player) {
        int modifier = 0;
        for (ConstantAbility effect : gameState.getStaticEffects().values()) {
            modifier += effect.keyCostModifier(player, gameState);
        }
        return modifier;
    }

    public void reap(CreatureCard creature, Player controller) {
        // Check if the creature is allowed to be used at all (Rule of Six, exhausted, constant ability etc.)
        if (!gameState.canUseCard(creature) || !gameState.canReap(creature, controller)) {
            return;
        }

        // Let the player reflect the reap on their state (gain amber, etc.)
        controller.reapWithCreature(creature, getEventBus());
    }

    public void fight(CreatureCard attacker, CreatureCard defender, Player attackerControllerPlayer) {
        if (!gameState.canUseCard(attacker) | !gameState.canFight(attacker, attackerControllerPlayer)) {
            log.info("{} cannot be used (exhausted or rule of six)", attacker.getName());
            return;
        }

        attacker.setReady(false);
        log.info("{} fights {}", attacker.getName(), defender.getName());

        if (beforeFight(attacker, defender)) {
            actualFight(attacker, defender);
            afterFight(attacker, defender);
        }

        attacker.fight(attackerControllerPlayer, getEventBus()); // TODO: The timing on this needs to be double checked
    }

    private boolean beforeFight(CreatureCard attacker, CreatureCard defender) {
        List<BeforeFightEffect> effects = new ArrayList<>();

        // ASSAULT
        int assault = attacker.getKeywordValue(KeywordAbility.ASSAULT);
        if (assault > 0) {
            effects.add(new BeforeFightEffect("Assault from " + attacker.getName(), () ->
                    dealDamage(defender, assault)
            ));
        }

        // HAZARDOUS
        int hazardous = defender.getKeywordValue(KeywordAbility.HAZARDOUS);
        if (hazardous > 0) {
            effects.add(new BeforeFightEffect("Hazardous from " + defender.getName(), () ->
                    dealDamage(attacker, hazardous)
            ));
        }

        // BEFORE FIGHT ABILITIES — attacker
        for (CardAbility ability : attacker.getAbilities()) {
            if (ability instanceof BeforeFightAbility bf) {
                bf.getBeforeFightEffect(attacker, defender, this).ifPresent(effects::add);
            }
        }

        // BEFORE FIGHT ABILITIES — defender
        for (CardAbility ability : defender.getAbilities()) {
            if (ability instanceof BeforeFightAbility bf) {
                bf.getBeforeFightEffect(defender, attacker, this).ifPresent(effects::add);
            }
        }

        if (effects.isEmpty()) return true;

        List<BeforeFightEffect> ordered = gameState.getActivePlayer().chooseBeforeFightOrder(effects);

        for (BeforeFightEffect effect : ordered) {
            log.info("[Before Fight] Resolving: {}", effect.description());
            effect.action().run();

            if (attacker.isDestroyed() || defender.isDestroyed()) {
                log.info("[Before Fight] One or both creatures destroyed. Fight canceled.");
                return false;
            }
        }

        return true;
    }

    private void actualFight(CreatureCard attacker, CreatureCard defender) {
        log.info("[FIGHT] {} ↔ {}", attacker.getName(), defender.getName());

        // Attacker damages defender
        dealDamage(defender, attacker.getPower());

        // Defender damages attacker unless SKIRMISH
        if (!attacker.hasKeyword(KeywordAbility.SKIRMISH)) {
            dealDamage(attacker, defender.getPower());
        } else {
            log.info("[SKIRMISH] {} avoids return damage", attacker.getName());
        }
    }

    private void afterFight(CreatureCard attacker, CreatureCard defender) {
        // Placeholder for “After Fight” effects (triggers, events, etc.)
        // You can add card effect handling or publish AFTER_FIGHT events here
        log.info("[AFTER FIGHT] Resolving aftermath of fight.");
    }

    public void dealDamage(CreatureCard creature, int amount) {
        log.info("Dealing {} dmg to {}", amount, creature.getName());
        if (creature.getArmor() != 0) {
            damagingCreatureWithArmor(creature, amount);
        } else {
            damageDealt(creature, amount);
        }
    }

    private void damagingCreatureWithArmor(CreatureCard creature, int amount) {
        int armor = creature.getArmor();
        int difference = armor - amount;

        if (difference >= 0) {
            // All damage prevented
            log.info("{}'s armor prevents all {} dmg ({} Armor remaining)", creature.getName(), amount, difference);
            creature.setArmor(difference);

            gameState.getEventBus().publish(new GameEvent(EventType.DAMAGE_PREVENTED, Map.of(
                    "creature", creature,
                    "amount", amount
            )));
        } else {
            // Some damage goes through
            int overflow = amount - armor;

            log.info("{}'s armor prevents {} dmg (0 Armor remaining)", creature.getName(), armor);
            creature.setArmor(0);

            if (armor > 0) {
                gameState.getEventBus().publish(new GameEvent(EventType.DAMAGE_PREVENTED, Map.of(
                        "creature", creature,
                        "amount", armor
                )));
            }

            damageDealt(creature, overflow);
        }
    }

    private void damageDealt(CreatureCard creature, int amount) {
        creature.takeDamage(amount);
        log.info("{} takes {} dmg (total: {})", creature.getName(), amount, creature.getDamage());

        gameState.getEventBus().publish(new GameEvent(EventType.DAMAGE_TAKEN, Map.of(
                "creature", creature,
                "amount", amount
        )));
    }

    public void destroyCreature(CreatureCard creature) {
        Player owner = gameState.getOwnerOf(creature);
        if (owner == null) {
            log.warn("Attempted to destroy creature with unknown owner: {}", creature.getName());
            return;
        }

        // Remove from battleline
        owner.getPlayArea().getBattleline().getCreatures().remove(creature);

        // Move to discard pile
        owner.getDiscardPile().add(creature);
        log.info("{} is destroyed and moved to {}'s discard pile", creature.getName(), owner.getName());

        // Remove any static effects associated with the creature
        if (gameState.getStaticEffects().containsKey(creature)) {
            log.info("Removing static effect associated with {}", creature.getName());
            gameState.getStaticEffects().remove(creature);
        }

        // Fire destroy event
        GameEvent destroyEvent = new GameEvent(EventType.CREATURE_DESTROYED, Map.of(
                "creature", creature,
                "player", owner
        ));
        getEventBus().publish(destroyEvent);
    }

    public void adjustAmber(Player player, int delta, AmberChangeType type, PlayableCard source) {
        int original = player.getAmber();
        int newValue = Math.max(0, original + delta); // Ensure no negative amber
        int actualDelta = newValue - original;

        if (actualDelta == 0) return;

        player.setAmber(newValue);
        log.info("{} {} {} amber ({} → {}) from {}",
                player.getName(),
                (actualDelta > 0 ? "gains" : "loses"),
                Math.abs(actualDelta),
                original, newValue,
                source.getName()
        );

        AmberChangeEventData eventData = new AmberChangeEventData(player, Math.abs(actualDelta), type, source);
        gameState.getEventBus().publish(new GameEvent(EventType.AMBER_CHANGED, Map.of(
                "amberEventData", eventData
        )));
    }

    public void playCard(PlayableCard card, Player controller) {
        if (card instanceof CreatureCard creature) {
            if (creature.hasKeyword(KeywordAbility.ALPHA) && !isFirstCardPlayedThisTurn(card)) {
                flagRuleViolation(card, "Alpha cards must be the first card played this turn.");
            }

            if (creature.hasKeyword(KeywordAbility.OMEGA)) {
                markOmegaCardPlayed(card);
            }

            if (creature.hasKeyword(KeywordAbility.DEPLOY)) {
                allowCustomBattlelinePlacement(creature);
            }
        }

        // Register all triggered/static abilities
        for (CardAbility ability : card.getAbilities()) {
            ability.register(this, controller, card);
        }

        card.play(controller, getEventBus());
    }

    public void playCreature(Player player, CreatureCard creature, Flank preferredFlank) {
        // Check if the creature is allowed to be used at all (Rule of Six, exhausted, constant ability etc.)
        if (!gameState.canUseCard(creature)) {
            return;
        }
        Battleline battleline = player.getPlayArea().getBattleline();

        // Add creature to the appropriate side
        battleline.addToFlank(creature, preferredFlank);

        // Compute effective flank(s)
        Set<Flank> effectiveFlanks = battleline.getEffectiveFlanks(creature);

        // Publish CREATURE_ENTERED_PLAY event
        gameState.getEventBus().publish(new GameEvent(EventType.CREATURE_ENTERED_PLAY, Map.of(
                "creature", creature,
                "player", player,
                "flanks", effectiveFlanks,
                "gameState", gameState
        )));

        // Register all triggered/static abilities
        for (CardAbility ability : creature.getAbilities()) {
            ability.register(this, player, creature);
        }

        creature.play(player, effectiveFlanks, getEventBus());
        creature.setReady(false);
    }

    public void playUpgrade(Player player, UpgradeCard upgrade, CreatureCard target) {
        // Check rule-of-six or usage limits if applicable
        if (!gameState.canUseCard(upgrade)) {
            log.info("Upgrade {} cannot be used due to Rule of Six.", upgrade.getName());
            return;
        }

        // Attach the upgrade to the target creature
        target.getUpgrades().add(upgrade);
        upgrade.play(player, getEventBus());
        gameState.setOwner(upgrade, player);

        // Register upgrade abilities
        for (CardAbility ability : upgrade.getAbilities()) {
            ability.register(this, player, upgrade);
        }

        log.info("{} attaches upgrade {} to {}", player.getName(), upgrade.getName(), target.getName());
    }

    public void archiveCard(PlayableCard card, Player player) {
        if (card != null) {
            player.getArchives().add(card);
        }
    }

    public PlayableCard discordTopCardOfDeck(Player player) {
        return discardCard(player.takeTopCardOfDeck(), player);
    }

    public PlayableCard discardCard(PlayableCard card, Player player) {
        if (card != null) {
            player.getDiscardPile().add(card);
            return card;
        } else {
            return null;
        }
    }

    public void chooseActiveHouse(Player player) {
        Set<House> identityHouses = player.getAvailableHouses();
        Set<House> restricted = gameState.getRestrictedHouses(player);

        Set<House> valid = new HashSet<>(identityHouses);
        valid.removeAll(restricted);

        House chosen = player.chooseHouse(valid); // Delegate UI/input to player
        log.info("{} chooses house: {}", player.getName(), chosen);

        gameState.getEventBus().publish(new GameEvent(EventType.HOUSE_CHOSEN, Map.of(
                "player", player,
                "house", chosen
        )));
        gameState.clearHouseRestrictions(player);
    }

    public House chhoseAHouse() {
        return null;
    }

    public EventBus getEventBus() {
        return gameState.getEventBus();
    }

    // Placeholder method signatures
    public void ignoreRetaliatoryDamage(CreatureCard attacker) {
    }

    public boolean isFirstAttackAgainst(CreatureCard defender) {
        return true;
    }

    public void preventDamageTo(CreatureCard creature) {
    }

    public void markAsPoisonous(CreatureCard creature) {
    }

    public void dealHazardousDamage(CreatureCard from, CreatureCard to, int amount) {
    }

    public void dealAssaultDamage(CreatureCard from, CreatureCard to, int amount) {
    }

    public void dealSplashDamage(CreatureCard from, CreatureCard primaryTarget, int splashAmount) {
    }

    public boolean isFirstCardPlayedThisTurn(PlayableCard card) {
        return true;
    }

    public void flagRuleViolation(PlayableCard card, String reason) {
        System.out.println("[RULE] " + card.getName() + ": " + reason);
    }

    public void markOmegaCardPlayed(PlayableCard card) {
    }

    public void allowCustomBattlelinePlacement(CreatureCard creature) {
    }

    public boolean isPlayedFromDiscard(PlayableCard card) {
        // Check discard context or play source
        return false;
    }

    public void resolveDestroyedAbilities(CreatureCard creature) {
        // Trigger destroyed abilities from effects or cards
    }

    public void resolveScrapEffect(PlayableCard card) {
        // Trigger scrap ability from card text or linked ability
    }
}
