package org.project.entity.players;

import org.project.entity.Entity;
import org.project.entity.enemies.KeyType;
import org.project.item.armors.Armor;
import org.project.item.consumables.Consumable;
import org.project.item.weapons.Weapon;
import org.project.util.ConsoleColors;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;

/**
 * Abstract base for all playable heroes (Knight, Wizard, Assassin).
 *
 * This class holds everything the three classes share: health/mana bars, the
 * weapon and armor, the level/XP system, coins, the inventory of consumables,
 * and the collected keys. It also implements four of the five combat actions,
 * because Light/Heavy Attack, Defend and Heal work the same for every class.
 * Only {@link #specialAbility(Entity)} is left abstract so each subclass can
 * provide its own unique ultimate (polymorphism).
 */
public abstract class Player implements Entity, ICombatActions {

    // Mana/stamina cost of each action. Special is deliberately the highest.
    public static final int HEAVY_COST = 8;
    public static final int DEFEND_COST = 6;
    public static final int HEAL_COST = 12;
    public static final int SPECIAL_COST = 15;

    protected String name;
    private int hp;
    private int maxHP;
    private int mp;
    private int maxMP;
    private int baseDamage;

    protected Weapon weapon;
    protected Armor armor;

    // Progression
    private int level = 1;
    private int xp = 0;
    private int xpToNext = 50;
    private int coins = 0;

    private final List<Consumable> inventory = new ArrayList<>();
    private final Set<KeyType> keys = EnumSet.noneOf(KeyType.class);

    // Temporary combat states, all triggered by actions and consumed on the
    // next relevant hit. Kept in the base class so takeDamage/dealDamage stay
    // generic while subclasses just flip the flags.
    protected boolean defending = false;
    protected boolean dodgeNext = false;
    protected boolean guaranteedCrit = false;

    protected Player(String name, int maxHP, int maxMP, int baseDamage, Weapon weapon, Armor armor) {
        this.name = name;
        this.maxHP = maxHP;
        this.hp = maxHP;
        this.maxMP = maxMP;
        this.mp = maxMP;
        this.baseDamage = baseDamage;
        this.weapon = weapon;
        this.armor = armor;
    }

    /** The class name shown in menus, e.g. "Knight". */
    public abstract String getClassName();

    /** The display name of the special ability, used to build the turn menu. */
    public abstract String getSpecialName();

    // ------------------------------------------------------------------
    //  Combat actions (ICombatActions)
    // ------------------------------------------------------------------

    /** Total raw damage of a basic hit = class base damage + weapon damage. */
    public int getAttackPower() {
        return baseDamage + weapon.getDamage();
    }

    /** The generic Entity attack maps to the free Light Attack. */
    @Override
    public void attack(Entity target) {
        lightAttack(target);
    }

    @Override
    public void lightAttack(Entity target) {
        System.out.println(ConsoleColors.cyan("⚔️ " + name + " (" + getClassName() + ") used Light Attack! (0 Mana)"));
        dealDamage(target, getAttackPower());
    }

    @Override
    public void heavyAttack(Entity target) {
        spendMana(HEAVY_COST);
        System.out.println(ConsoleColors.cyan("⚔️ " + name + " used Heavy Attack! " + manaTag(HEAVY_COST)));
        dealDamage(target, getAttackPower() * 2);
    }

    @Override
    public void defend() {
        spendMana(DEFEND_COST);
        defending = true;
        System.out.println(ConsoleColors.yellow("🛡️ " + name + " braces for the next hit! " + manaTag(DEFEND_COST)));
    }

    @Override
    public void healAction() {
        spendMana(HEAL_COST);
        int amount = (int) (maxHP * 0.4);
        heal(amount);
        System.out.println(ConsoleColors.green("✨ " + name + " heals for " + amount + " HP! " + manaTag(HEAL_COST)));
        System.out.println(ConsoleColors.green(name + " now has " + hp + "/" + maxHP + " HP."));
    }

    // specialAbility is abstract -> each class supplies its own ultimate.

    /**
     * Shared damage routine for the player's attacks. Applies a guaranteed crit
     * if the Assassin set one up, then reports the result.
     */
    protected void dealDamage(Entity target, int amount) {
        if (guaranteedCrit) {
            amount *= 2;
            guaranteedCrit = false;
            System.out.println(ConsoleColors.purple("🌙 Critical hit from the shadows!"));
        }
        target.takeDamage(amount);
        System.out.println(ConsoleColors.red(target.getName() + " took " + amount + " damage!"));
        System.out.println(target.getName() + " has " + target.getHp() + "/" + target.getMaxHP() + " HP remaining.");
        System.out.println(ConsoleColors.blue(name + " Mana: " + mp + "/" + maxMP));
    }

    // ------------------------------------------------------------------
    //  Taking damage (with overloading for the Dragon's true damage)
    // ------------------------------------------------------------------

    @Override
    public void takeDamage(int damage) {
        // Normal hit: goes through armor and any defensive stance.
        takeDamage(damage, false);
    }

    /**
     * Overloaded version. When {@code ignoreDefenses} is true the hit ignores
     * armor and the defend stance – this is how the Dragon's fiery breath
     * bypasses shields.
     */
    public void takeDamage(int damage, boolean ignoreDefenses) {
        if (dodgeNext) {
            dodgeNext = false;
            System.out.println(ConsoleColors.purple("💨 " + name + " was invisible and dodged the attack completely!"));
            return;
        }

        int incoming = damage;
        if (!ignoreDefenses) {
            if (defending) {
                defending = false;
                incoming = incoming / 4; // brace reduces the blow to 25%
                System.out.println(ConsoleColors.yellow("🛡️ " + name + " blocks most of the blow!"));
            }
            incoming -= armor.getDefense();
            armor.absorbHit();
        }
        if (incoming < 1) {
            incoming = 1; // a hit always stings at least a little
        }

        hp -= incoming;
        if (hp < 0) {
            hp = 0;
        }
        System.out.println(ConsoleColors.red(name + " took " + incoming + " damage!"));
        System.out.println(name + " has " + hp + "/" + maxHP + " HP remaining.");
    }

    // ------------------------------------------------------------------
    //  Resource helpers
    // ------------------------------------------------------------------

    /** Spend mana if affordable. Returns false when there is not enough. */
    protected boolean spendMana(int cost) {
        if (mp < cost) {
            return false;
        }
        mp -= cost;
        return true;
    }

    public boolean canAfford(int cost) {
        return mp >= cost;
    }

    private String manaTag(int cost) {
        return ConsoleColors.blue("(-" + cost + " Mana)");
    }

    @Override
    public void heal(int health) {
        hp += health;
        if (hp > maxHP) {
            hp = maxHP;
        }
    }

    @Override
    public void fillMana(int mana) {
        mp += mana;
        if (mp > maxMP) {
            mp = maxMP;
        }
    }

    /** Refill HP and mana fully after a won battle. */
    public void restoreAfterBattle() {
        hp = maxHP;
        mp = maxMP;
    }

    // ------------------------------------------------------------------
    //  Leveling
    // ------------------------------------------------------------------

    /** Award XP and level up as many times as the total allows. */
    public void gainXP(int amount) {
        xp += amount;
        System.out.println(ConsoleColors.purple("✨ Gained " + amount + " XP! (" + xp + "/" + xpToNext + ")"));
        while (xp >= xpToNext) {
            xp -= xpToNext;
            levelUp();
        }
    }

    private void levelUp() {
        level++;
        xpToNext = (int) (xpToNext * 1.5);
        maxHP += 10;
        maxMP += 5;
        baseDamage += 2;
        hp = maxHP;
        mp = maxMP;
        System.out.println(ConsoleColors.bold(ConsoleColors.GREEN
                + "⬆️  LEVEL UP! " + name + " is now level " + level + "!" + ConsoleColors.RESET));
        System.out.println(ConsoleColors.green(
                "   Max HP -> " + maxHP + " | Max Mana -> " + maxMP + " | Base Damage -> " + baseDamage));
    }

    // ------------------------------------------------------------------
    //  Inventory, coins and keys
    // ------------------------------------------------------------------

    public void addConsumable(Consumable c) {
        inventory.add(c);
    }

    public List<Consumable> getInventory() {
        return inventory;
    }

    /** Use (and remove) the consumable at the given index on this player. */
    public void useConsumable(int index) {
        Consumable c = inventory.remove(index);
        c.use(this);
    }

    public void addCoins(int amount) {
        coins += amount;
    }

    public boolean spendCoins(int amount) {
        if (coins < amount) {
            return false;
        }
        coins -= amount;
        return true;
    }

    public int getCoins() {
        return coins;
    }

    public void addKey(KeyType key) {
        keys.add(key);
    }

    public boolean hasKey(KeyType key) {
        return keys.contains(key);
    }

    public boolean hasAllKeys() {
        return keys.size() == KeyType.values().length;
    }

    public Set<KeyType> getKeys() {
        return keys;
    }

    // ------------------------------------------------------------------
    //  Simple getters
    // ------------------------------------------------------------------

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getHp() {
        return hp;
    }

    @Override
    public int getMaxHP() {
        return maxHP;
    }

    @Override
    public int getMp() {
        return mp;
    }

    @Override
    public int getMaxMP() {
        return maxMP;
    }

    @Override
    public boolean isAlive() {
        return hp > 0;
    }

    public int getLevel() {
        return level;
    }

    public Weapon getWeapon() {
        return weapon;
    }

    public void setWeapon(Weapon weapon) {
        this.weapon = weapon;
    }

    public Armor getArmor() {
        return armor;
    }
}
