package Server;

import Server.Automaton.Game;
import Server.Cards.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;



/**
 * Represents the physical game board: the two card rows, the offering tiles,
 * the turn-order tile, and the card deck.
 *
 * Responsibilities:
 *  - Owning and mutating the top and bottom card rows.
 *  - Owning the OfferingTiles and the TurnTile.
 *  - Performing end-of-round row transitions and era-change building swaps.
 *  - Exposing visible EventCards to Game for resolution.
 *
 * NOT responsible for:
 *  - Resolving events (that is EventsSolver's job, called by Game).
 *  - Applying player rewards/penalties (that is Game's job).
 *  - Holding a reference to the player list.
 */
public class GameBoard {
    private static final Logger logger = LogManager.getLogger(GameBoard.class);
    // -------------------------------------------------------------------------
    // Fields
    // -------------------------------------------------------------------------

    private Era era;

    private final CardDeck        cardDeck;
    private final List<Card>      topRow;
    private final List<Card>      bottomRow;
    private final List<OfferingTile> offeringTiles;
    private final TurnTile        turnTile;
    private final BuildingManager buildingManager;

    // -------------------------------------------------------------------------
    // Constructor
    // -------------------------------------------------------------------------

    /**
     * Creates the board for a new game.
     * Call {@link #setupInitialRows(int)} and {@link #initOfferingTiles(int)}
     * separately after construction (mirrors the rulebook setup steps).
     */
    public GameBoard(Era startingEra, CardDeck cardDeck, int numPlayers) {
        this.era             = startingEra;
        this.cardDeck        = cardDeck;
        this.topRow          = new ArrayList<>();
        this.bottomRow       = new ArrayList<>();
        this.offeringTiles   = new ArrayList<>();
        this.turnTile        = new TurnTile(numPlayers);
        this.buildingManager = new BuildingManager();
    }

    // -------------------------------------------------------------------------
    // Setup
    // -------------------------------------------------------------------------

    /**
     * Creates and places the correct OfferingTiles for the given player count.
     *
     * Tile layout (from the rulebook):
     *   orderId 0  → tile A: 3 food      (5-player only)
     *   orderId 1  → tile B: 1 DOWN      (all)
     *   orderId 2  → tile C: 1 UP        (all)
     *   orderId 3  → tile D: 2 DOWN      (3+ players)
     *   orderId 4  → tile E: 1 DOWN 1 UP (all)
     *   orderId 5  → tile F: 2 UP        (all)
     *   orderId 6  → tile G: 1 DOWN 2 UP (4+ players)
     *
     * With n players, exactly n tiles are used, starting from tile A if
     * 5 players, otherwise starting from tile B.
     */
    public void initOfferingTiles(int numPlayers) {
        offeringTiles.clear();
        switch (numPlayers) {
            case 2:
                offeringTiles.add(new OfferingTile(1));
                offeringTiles.add(new OfferingTile(2));
                offeringTiles.add(new OfferingTile(4));
                offeringTiles.add(new OfferingTile(5));
                break;
             case 3:
                 offeringTiles.add(new OfferingTile(1));
                 offeringTiles.add(new OfferingTile(2));
                 offeringTiles.add(new OfferingTile(3));
                 offeringTiles.add(new OfferingTile(4));
                 offeringTiles.add(new OfferingTile(5));
                 break;
            case 4:
                offeringTiles.add(new OfferingTile(1));
                offeringTiles.add(new OfferingTile(2));
                offeringTiles.add(new OfferingTile(3));
                offeringTiles.add(new OfferingTile(4));
                offeringTiles.add(new OfferingTile(5));
                offeringTiles.add(new OfferingTile(6));
                break;
            case 5:
                offeringTiles.add(new OfferingTile(0));
                offeringTiles.add(new OfferingTile(1));
                offeringTiles.add(new OfferingTile(2));
                offeringTiles.add(new OfferingTile(3));
                offeringTiles.add(new OfferingTile(4));
                offeringTiles.add(new OfferingTile(5));
                offeringTiles.add(new OfferingTile(6));
                break;

        }

    }

    // Find the oddertingTile selected by Player
    public OfferingTile getOfferingTile(Player p){
        OfferingTile offering = offeringTiles.stream().filter(o -> p.equals(o.getOccupant())).findFirst().orElse(null);
        return offering;
    }
    /**
     * Draws the initial two rows of cards according to rulebook setup steps 4-5:
     *
     * Bottom row: draw cards one at a time until (numPlayers + 1) Character cards
     *             have been placed. Any Event card drawn is placed in the top row instead.
     * Top row:    fill to (numPlayers + 4) with additional draws (accounting for
     *             any events already placed there from the bottom-row draw).
     */
    public void setupInitialRows(int numPlayers) {
        topRow.clear();
        bottomRow.clear();

        // Draw bottom row — events are bumped to top row
        while (bottomRow.size() < numPlayers + 1) {
            Card card = cardDeck.drawTribeOne();
            if (card instanceof EventCard) {
                topRow.add(card);
                logger.info("Setup: Event card " + card.getCardId() + " moved to top row.");
            } else {
                bottomRow.add(card);
            }
        }

        // Fill top row up to numPlayers + 4
        int needed = (numPlayers + 4) - topRow.size();
        if (needed > 0) {
            topRow.addAll(cardDeck.drawTribe(needed));
        }

        // Add all Building Era.I on top row 2P 1   3-4-5 2
        topRow.add(cardDeck.drawBuildingOne(Era.I));
       if (numPlayers  > 2){
            topRow.add(cardDeck.drawBuildingOne(Era.I));
       }
    }

    // -------------------------------------------------------------------------
    // End-of-round row management (rulebook "Fine del Round" steps 2-4)
    // -------------------------------------------------------------------------

    /**
     * Performs the three row-management steps at the end of every round:
     *
     *   Step 2 — Discard all Character and Event cards from the bottom row.
     *            Building cards stay.
     *   Step 3 — Move all Character and Event cards from the top row down to
     *            the bottom row. Building cards stay in the top row.
     *   Step 4 — Draw (numPlayers + 4) new cards into the top row.
     *
     * @return true if the newly drawn cards contain a card from the next Era,
     *         signalling that {@link #triggerEraChange()} should be called.
     */
    public boolean advanceRows(int numPlayers) {

        Era eraBeforeDraw = this.era;

        // Step 2: discard non-building cards from the bottom row
        bottomRow.removeIf(
                c -> !(c instanceof BuildingCard)
        );

        // Step 3: move non-building cards from top row down to bottom row
        Iterator<Card> it = topRow.iterator();
        while (it.hasNext()) {
            Card c = it.next();
            if (!(c instanceof BuildingCard)) {
                logger.debug("move card from top to bottom  " + c);
                bottomRow.add(c);
                it.remove();
            }
        }

        // Step 4: draw fresh cards into the top row
        List<Card> newCards = cardDeck.drawTribe(numPlayers + 4);
        topRow.addAll(newCards);
        logger.info("NEW CARDS ON TOP {} " , newCards);
        return isNewEraRevealed(eraBeforeDraw, newCards);
    }

    /**
     * Returns true if any of the newly drawn cards belongs to an era
     * that is strictly later than the current one.
     */
    private boolean isNewEraRevealed(Era currentEra, List<Card> newCards) {
        for (Card c : newCards) {
            Era cardEra = eraOf(c);
            if (cardEra != null && cardEra.ordinal() > currentEra.ordinal()) {
                logger.info("FOUND NEW ERA {} " , cardEra);
                return true;
            }
        }
        return false;
    }

    // -------------------------------------------------------------------------
    // Era change (rulebook "Inizio della Nuova Era")
    // -------------------------------------------------------------------------

    /**
     * Advances the era and performs the three building-row transitions:
     *
     *   Step 1 — (Era III only) Discard all Building cards from the bottom row.
     *   Step 2 — Move all Building cards from the top row to the bottom row
     *            (to the right of the Tribe cards). [Era II and III]
     *   Step 3 — Place the new era's Building cards face-up in the top row.
     *            [Era II and III]
     *
     * @return the new Era after the transition
     */
    public Era triggerEraChange(int numPlayers) {
        era = era.next();   // Era.I → II → III → FINAL
        logger.info("ERA CHANGED → {} ", era);

        // Step 1 (Era III only): remove old era buildings from the bottom row
        if (era == Era.III) {
            bottomRow.removeIf(c -> c instanceof BuildingCard);
        }

        // Step 2: move buildings from top row to bottom row
        List<BuildingCard> descending = new ArrayList<>();
        Iterator<Card> it = topRow.iterator();
        while (it.hasNext()) {
            Card c = it.next();
            if (c instanceof BuildingCard) {
                descending.add((BuildingCard) c);
                it.remove();
            }
        }
        bottomRow.addAll(descending);
        logger.info("event=MOVED_BUILING era={} count={} cards={}", era, descending.size(), descending);

        // Step 3: place the new era's building cards in the top row
        int n = BuildingRules.getBuildingCards(numPlayers, era);
        List<Card> newEraBuildings = cardDeck.drawBuilding(n , era);
        topRow.addAll(newEraBuildings);

        logger.info("event=ADD_BUILDINGS era={} count={} cards={}", era, n, newEraBuildings);
        return era;
    }

    // -------------------------------------------------------------------------
    // Offering tiles
    // -------------------------------------------------------------------------

    /**
     * Attempts to place a player's totem on an offering tile.
     *
     * @return true if successful; false if the tile was already occupied
     */
    public boolean placeTotem(Player player, OfferingTile tile, TurnTile turntile) {
        if (!tile.isEmpty()) return false;
        tile.setOccupant(player);
        turntile.leaveTurnTile(player);
        return true;
    }

    /**
     * Removes all totems from offering tiles. Call at the start of each new round.
     */
    public void clearOfferingTiles() {
        for (OfferingTile tile : offeringTiles) {
            tile.removeOccupant();
        }
    }

    // -------------------------------------------------------------------------
    // Card removal (called by Game when a player takes a card)
    // -------------------------------------------------------------------------

    /**
     * Removes a card from the top row by ID and returns it.
     *
     * @return the card, or null if no card with that ID exists in the top row
     */
    public Card takeFromTopRow(int cardId) {
        return takeFromRow(topRow, cardId);
    }

    /**
     * Removes a card from the bottom row by ID and returns it.
     *
     * @return the card, or null if no card with that ID exists in the bottom row
     */
    public Card takeFromBottomRow(int cardId) {
        return takeFromRow(bottomRow, cardId);
    }

    private Card takeFromRow(List<Card> row, int cardId) {
        Iterator<Card> it = row.iterator();
        while (it.hasNext()) {
            Card c = it.next();
            if (c.getCardId() == cardId) {
                it.remove();
                return c;
            }
        }
        return null;
    }

    // -------------------------------------------------------------------------
    // Event visibility
    // -------------------------------------------------------------------------

    /**
     * Returns all EventCards currently in the bottom row, sorted by era
     * (ascending) so they are resolved in the correct order.
     * Sustainment is NOT sorted last here — that is Game's responsibility.
     */
    public List<EventCard> getVisibleEvents() {
        List<EventCard> events = new ArrayList<>();
        for (Card c : bottomRow) {
            if (c instanceof EventCard) {
                events.add((EventCard) c);
            }
        }
        events.sort((a, b) -> a.getEra().ordinal() - b.getEra().ordinal());
        return events;
    }

    /**
     * Returns EventCards from BOTH rows.
     * Used during the final round when all visible events must be resolved.
     */
    public List<EventCard> getAllVisibleEvents() {
        List<EventCard> events = new ArrayList<>();
        for (Card c : bottomRow) {
            if (c instanceof EventCard) events.add((EventCard) c);
        }
        for (Card c : topRow) {
            if (c instanceof EventCard) events.add((EventCard) c);
        }
        events.sort((a, b) -> a.getEra().ordinal() - b.getEra().ordinal());
        return events;
    }


    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------

    /** Extracts the Era from a card, returning null for building cards. */
    private Era eraOf(Card c) {
        if (c instanceof CharacterCard) return ((CharacterCard) c).getEra();
        if (c instanceof EventCard)     return ((EventCard) c).getEra();
        return null;
    }

    // -------------------------------------------------------------------------
    // Getters
    // -------------------------------------------------------------------------

    public Era                   getEra()             { return era; }
    public List<Card>            getTopRow()           { return topRow; }
    public List<Card>            getBottomRow()        { return bottomRow; }
    public List<OfferingTile>    getOfferingTiles()    { return offeringTiles; }
    public TurnTile              getTurnTile()         { return turnTile; }
    public BuildingManager       getBuildingManager()  { return buildingManager; }
    public CardDeck              getCardDeck()         { return cardDeck; }



    @Override
    public String toString() {
        return "GameBoard{" +
                "era=" + era +
                ",\n offeringTiles=" + offeringTiles +
                ",\n turnTile=" + turnTile +
                '}';
    }
}