package org.project;

import org.project.entity.enemies.Dragon;
import org.project.entity.enemies.Enemy;
import org.project.entity.enemies.KeyType;
import org.project.entity.players.Assassin;
import org.project.entity.players.Knight;
import org.project.entity.players.Player;
import org.project.entity.players.Wizard;
import org.project.item.consumables.Consumable;
import org.project.location.Location;
import org.project.util.ConsoleColors;
import org.project.util.Dice;

import java.util.List;
import java.util.Scanner;

/**
 * The main game controller. It owns the player, the world's locations and the
 * merchant, and drives the whole flow: class selection, the exploration menu,
 * turn-based combat, progression, and the final Dragon fight.
 *
 * All the polymorphism set up in the class hierarchy pays off here — the combat
 * loop only ever calls methods on {@code Player} and {@code Enemy}, and each
 * concrete class fills in its own behaviour.
 */
public class Game {

    private final Scanner scanner = new Scanner(System.in);
    private final List<Location> locations;
    private final Merchant merchant = new Merchant(scanner);

    private Player player;
    private boolean running = true;

    public Game(List<Location> locations) {
        this.locations = locations;
    }

    // ------------------------------------------------------------------
    //  Top-level flow
    // ------------------------------------------------------------------

    public void start() {
        printPrologue();
        chooseClass();
        explorationLoop();
    }

    private void printPrologue() {
        System.out.println(ConsoleColors.bold(ConsoleColors.CYAN + "\n=== JAVA KNIGHT ===" + ConsoleColors.RESET));
        System.out.println(ConsoleColors.cyan(
                "For centuries, the land of Javanest lived in peace, until a magical Dragon\n"
                + "plunged it into darkness, cursing its people into monsters. It split the three\n"
                + "keys to its lair among them. Collect all three keys, grow strong, and slay the\n"
                + "Dragon to break the curse!\n"));
    }

    private void chooseClass() {
        System.out.print("Name your hero: ");
        String name = scanner.hasNextLine() ? scanner.nextLine().trim() : "Hero";
        if (name.isEmpty()) {
            name = "Hero";
        }

        System.out.println("\nChoose your class:");
        System.out.println("1. " + ConsoleColors.cyan("Knight")
                + "  ⚔️  Highest base damage, sturdy armor. Special: Shield Bash (stun).");
        System.out.println("2. " + ConsoleColors.purple("Wizard")
                + "  🧙  Highest HP. Special: Arcane Blast (damage + self-heal).");
        System.out.println("3. " + ConsoleColors.yellow("Assassin")
                + " 🗡️  Highest mana. Special: Vanish (dodge + guaranteed crit).");

        int choice = readChoice(1, 3);
        player = switch (choice) {
            case 1 -> new Knight(name);
            case 2 -> new Wizard(name);
            default -> new Assassin(name);
        };
        System.out.println(ConsoleColors.green("\nWelcome, " + player.getName() + " the " + player.getClassName() + "!"));
    }

    // ------------------------------------------------------------------
    //  Exploration menu
    // ------------------------------------------------------------------

    private void explorationLoop() {
        while (running && player.isAlive()) {
            Location location = locations.get(Dice.between(0, locations.size() - 1));
            Enemy enemy = location.spawnEnemy();

            System.out.println(ConsoleColors.bold("\n────────────────────────────────────────"));
            System.out.println("You arrive at " + ConsoleColors.cyan(location.getName())
                    + ". A wild " + ConsoleColors.red(enemy.getName()) + " appears!");
            printKeyProgress();

            // Decide what to do about this particular enemy.
            boolean encounterOngoing = true;
            while (encounterOngoing && running && player.isAlive()) {
                System.out.println("\nWhat will you do?");
                System.out.println("1. Fight the " + enemy.getName());
                System.out.println("2. Move to another location");
                System.out.println("3. Visit the Merchant");
                if (player.hasAllKeys()) {
                    System.out.println("4. " + ConsoleColors.bold("Go to the Castle to face the Dragon 🐉"));
                }

                int max = player.hasAllKeys() ? 4 : 3;
                int choice = readChoice(1, max);
                switch (choice) {
                    case 1 -> {
                        boolean won = combat(enemy);
                        if (!won) {
                            gameOver();
                            return;
                        }
                        afterVictory(enemy);
                        encounterOngoing = false; // move on to a new encounter
                    }
                    case 2 -> {
                        System.out.println("You slip away to find another path...");
                        encounterOngoing = false;
                    }
                    case 3 -> merchant.visit(player);
                    case 4 -> {
                        castleFight();
                        return;
                    }
                }
            }
        }
    }

    // ------------------------------------------------------------------
    //  Combat
    // ------------------------------------------------------------------

    /** Runs a full turn-based battle. Returns true if the player survives. */
    private boolean combat(Enemy enemy) {
        System.out.println(ConsoleColors.bold("\nYou chose to FIGHT!"));

        while (player.isAlive() && enemy.isAlive()) {
            printCombatStatus(enemy);
            playerTurn(enemy);

            if (enemy.isAlive()) {
                enemyTurn(enemy);
            }
        }
        return player.isAlive();
    }

    private void printCombatStatus(Enemy enemy) {
        System.out.println();
        System.out.println(ConsoleColors.green("[" + player.getName() + " (" + player.getClassName() + ") - "
                + player.getHp() + "/" + player.getMaxHP() + " HP | "
                + player.getMp() + "/" + player.getMaxMP() + " Mana]  Lv." + player.getLevel()));
        System.out.println(ConsoleColors.red("[" + enemy.getName() + " - "
                + enemy.getHp() + "/" + enemy.getMaxHP() + " HP]"));
        System.out.println("---");
    }

    /** Handle exactly one player action (loops until a valid one is chosen). */
    private void playerTurn(Enemy enemy) {
        System.out.println(ConsoleColors.bold("\nYour Turn:"));
        System.out.println("1. Light Attack | 2. Heavy Attack " + ConsoleColors.blue("(-" + Player.HEAVY_COST + " Mana)")
                + " | 3. Defend " + ConsoleColors.blue("(-" + Player.DEFEND_COST + " Mana)")
                + " | 4. Heal " + ConsoleColors.blue("(-" + Player.HEAL_COST + " Mana)")
                + " | 5. " + player.getSpecialName() + " " + ConsoleColors.blue("(-" + Player.SPECIAL_COST + " Mana)")
                + " | 6. Use Item");

        while (true) {
            int choice = readChoice(1, 6);
            switch (choice) {
                case 1 -> {
                    player.lightAttack(enemy);
                    return;
                }
                case 2 -> {
                    if (requireMana(Player.HEAVY_COST)) {
                        player.heavyAttack(enemy);
                        return;
                    }
                }
                case 3 -> {
                    if (requireMana(Player.DEFEND_COST)) {
                        player.defend();
                        return;
                    }
                }
                case 4 -> {
                    if (requireMana(Player.HEAL_COST)) {
                        player.healAction();
                        return;
                    }
                }
                case 5 -> {
                    if (requireMana(Player.SPECIAL_COST)) {
                        player.specialAbility(enemy);
                        return;
                    }
                }
                case 6 -> {
                    if (useItemMenu()) {
                        return;
                    }
                }
            }
        }
    }

    private boolean requireMana(int cost) {
        if (player.canAfford(cost)) {
            return true;
        }
        System.out.println(ConsoleColors.blue("Not enough Mana! Pick another action."));
        return false;
    }

    /**
     * Show the inventory and use a chosen consumable. Returns true if an item was
     * used (which consumes the turn), false if the player had nothing / cancelled.
     */
    private boolean useItemMenu() {
        List<Consumable> inv = player.getInventory();
        if (inv.isEmpty()) {
            System.out.println("Your inventory is empty!");
            return false;
        }
        System.out.println("Choose an item to use (0 to cancel):");
        for (int i = 0; i < inv.size(); i++) {
            System.out.println((i + 1) + ". " + inv.get(i).getName());
        }
        int choice = readChoice(0, inv.size());
        if (choice == 0) {
            return false;
        }
        player.useConsumable(choice - 1);
        return true;
    }

    private void enemyTurn(Enemy enemy) {
        System.out.println(ConsoleColors.bold("\n" + enemy.getName() + "'s Turn:"));
        if (enemy.isStunned()) {
            enemy.clearStun();
            System.out.println(ConsoleColors.yellow("😵 " + enemy.getName() + " is stunned and skips its turn!"));
            return;
        }
        enemy.attack(player);
    }

    // ------------------------------------------------------------------
    //  Post-combat rewards & progression
    // ------------------------------------------------------------------

    private void afterVictory(Enemy enemy) {
        System.out.println(ConsoleColors.bold(ConsoleColors.GREEN
                + "\n🏆 The " + enemy.getName() + " is defeated!" + ConsoleColors.RESET));

        player.gainXP(enemy.getXpReward());

        int coins = Math.max(0, Dice.between(enemy.getCoinReward() - 4, enemy.getCoinReward() + 4));
        player.addCoins(coins);
        System.out.println(ConsoleColors.yellow("💰 Found " + coins + " coins (total: " + player.getCoins() + ")."));

        tryKeyDrop(enemy);

        player.restoreAfterBattle();
        System.out.println(ConsoleColors.green("You catch your breath — HP and Mana fully restored."));
    }

    /** Roll for the enemy's unique key, respecting the one-key-per-species rule. */
    private void tryKeyDrop(Enemy enemy) {
        KeyType key = enemy.getKeyType();
        if (key == null || player.hasKey(key)) {
            return; // Dragon has no key, or we already own this one
        }
        if (Dice.chance(enemy.getKeyDropChance())) {
            player.addKey(key);
            System.out.println(ConsoleColors.bold(ConsoleColors.PURPLE
                    + "🗝️  The " + enemy.getName() + " dropped the " + key.getDisplayName() + "!" + ConsoleColors.RESET));
            printKeyProgress();
        }
    }

    private void printKeyProgress() {
        StringBuilder sb = new StringBuilder("Keys: ");
        for (KeyType key : KeyType.values()) {
            sb.append(player.hasKey(key) ? ConsoleColors.green("[" + key.getDisplayName() + "] ")
                    : ConsoleColors.red("[???] "));
        }
        System.out.println(sb.toString().trim());
    }

    // ------------------------------------------------------------------
    //  Endgame
    // ------------------------------------------------------------------

    private void castleFight() {
        System.out.println(ConsoleColors.bold(ConsoleColors.RED
                + "\nYou march to the Castle. The ground trembles as the Dragon awakens...\n" + ConsoleColors.RESET));
        Dragon dragon = new Dragon();

        boolean won = combat(dragon);
        if (won) {
            System.out.println(ConsoleColors.bold(ConsoleColors.GREEN
                    + "\n🎉 VICTORY! The Dragon falls, the curse breaks, and peace returns to Javanest!"
                    + ConsoleColors.RESET));
            System.out.println(ConsoleColors.green("You are the Java Knight. 🛡️"));
        } else {
            gameOver();
        }
        running = false;
    }

    private void gameOver() {
        System.out.println(ConsoleColors.bold(ConsoleColors.RED
                + "\n💀 GAME OVER — " + player.getName() + " has fallen. Darkness consumes Javanest..."
                + ConsoleColors.RESET));
        running = false;
    }

    // ------------------------------------------------------------------
    //  Input helper
    // ------------------------------------------------------------------

    /** Read an integer choice in [min, max], re-prompting on bad input. */
    private int readChoice(int min, int max) {
        while (true) {
            System.out.print(ConsoleColors.bold("> "));
            if (!scanner.hasNextLine()) {
                return min; // no more input: pick the safe default so we don't loop forever
            }
            try {
                int value = Integer.parseInt(scanner.nextLine().trim());
                if (value >= min && value <= max) {
                    return value;
                }
            } catch (NumberFormatException ignored) {
                // fall through to re-prompt
            }
            System.out.println("Please enter a number between " + min + " and " + max + ".");
        }
    }
}
