from dataclasses import  dataclass
from enum import Enum
from abc import ABC
import random

from card import *
from carddb import *

@dataclass
class Player:
    name: str
    party_leader: Card
    party: list[Card]
    hand: list[Card]
    monsters: list[Card]

    def take_cards(self, cards: list[Card]) -> list[Card]:

        remaining_cards = cards.copy()
        for card in cards:
            if card_is_monster(card):
                self.monsters.append(card)
                remaining_cards.remove(card)
            elif card.is_leader:
                if not self.party_leader:
                    self.party_leader = card
                    remaining_cards.remove(card)
            else:
                self.hand.append(card)
                remaining_cards.remove(card)
        
        return remaining_cards
    
    def declares_victory(self) -> bool:

        if len(self.monsters) >= 3:
            return True
        assert(isinstance(self.party_leader.class_type, HeroClass))
        classes: set[HeroClass] = set([self.party_leader.class_type])
        for hero in self.party:
            if isinstance(hero.class_type, HeroClass):
                classes.add(hero.class_type)
        
        return len(classes) >= 6
        

@dataclass
class Game:
    players: list[Player]
    discard_pile: list[Card]
    draw_pile: list[Card]
    monster_pile: list[Card]
    active_monsters: list[Card]
    hero_pile: list[Card]
    turn_number: int = 0
    winner: Player | None = None

@dataclass
class TurnResult:
    cost: int
    message: str
    earned_cards: list[Card]
    played_cards: list[Card]
    does_end_game: bool = False

def draw_from_pile(pile: list[Card]) -> Card:
    return pile.pop()

def draw_party_leader(pile: list[Card]) -> Card:
    return pile.pop()

def draw_monster(pile: list[Card]) -> Card:
    return pile.pop()

def card_is_monster(card: Card) -> bool:
    return card.class_type == CardType.MONSTER

def card_is_hero(card: Card) -> bool:
    return isinstance(card.class_type, HeroClass)

def get_result_of(action: str, action_points_remaining: int, draw_pile: list[Card], monster_pile: list[Card], player: Player) -> TurnResult:

    if action == "win":
        result = TurnResult(cost=0, message="You end the game.", earned_cards=[], played_cards=[], does_end_game=True)
    elif action in ["help"]:
        result = TurnResult(cost=0, message="Helpful information goes here.", earned_cards=[], played_cards=[])
    elif action in ["hand"]:
        result = TurnResult(cost=0, message="\n".join([card.name for card in player.hand]), earned_cards=[], played_cards=[])
    elif action in ["party"]:
        result = TurnResult(cost=0, message="\n".join([card.name for card in player.party]), earned_cards=[], played_cards=[])
    elif action in ["leader"]:
        result = TurnResult(cost=0, message=f"Your party leader is {player.party_leader.name} ({player.party_leader.class_type.value.lower()})", earned_cards=[], played_cards=[])
    elif action == "attack":
        result = TurnResult(cost=2, message="", earned_cards=[draw_monster(monster_pile)], played_cards=[])
    elif action == "play":
        result = TurnResult(cost=1, message="", earned_cards=[], played_cards=[player.hand.pop()])
    elif action == "cheat":
        result = TurnResult(cost=-100, message="You add +100 action points to yourself.", earned_cards=[], played_cards=[])
    elif action == "draw":
        result = TurnResult(cost=1, message="", earned_cards=[draw_from_pile(draw_pile)], played_cards=[])
    elif action == "mulligan":
        result = TurnResult(cost=3, message="", earned_cards=[draw_from_pile(draw_pile), draw_from_pile(draw_pile), draw_from_pile(draw_pile), draw_from_pile(draw_pile), draw_from_pile(draw_pile)], played_cards=[])
    else:
        result = TurnResult(cost=0, message="Helpful information goes here.", earned_cards=[], played_cards=[])
    
    if result.cost > action_points_remaining:
        result = TurnResult(0, "You don't have enough action points for that.", [], played_cards=[])

    return result

def roll() -> int:
    return random.randrange(1, 7) + random.randrange(1, 7)

def deal() -> Game:

    discard_pile: list[Card] = []
    draw_pile: list[Card] = []
    monster_pile: list[Card] = []
    active_monsters: list[Card] = []
    leader_pile: list[Card] = []

    for card in ALL_CARDS:
        if card.class_type == CardType.MONSTER:
            monster_pile.append(card)
        elif card.is_leader:
            leader_pile.append(card)
        else:
            draw_pile.append(card)
    random.shuffle(draw_pile)
    random.shuffle(monster_pile)
    random.shuffle(leader_pile)

    players: list[Player] = [
        Player("Nehla", draw_party_leader(leader_pile), [], [], []),
        Player("Sam", draw_party_leader(leader_pile), [], [], []),
        Player("Sean", draw_party_leader(leader_pile), [], [], [])
    ]

    game = Game(players, discard_pile, draw_pile, monster_pile, active_monsters, leader_pile)

    for player in game.players:
        for _ in range(5):
            player.take_cards([draw_from_pile(game.draw_pile)])
    
    return game

term_width = 40
print("HERE TO SLAY")
print("=" * term_width)

def main():

    game = deal()
    while not game.winner:
        player = game.players[game.turn_number % len(game.players)]
        action_points_remaining = 3
        print(f"It is {player.name}'s turn.")
        while action_points_remaining and (not game.winner):
            print(f"You have {action_points_remaining} action points remaining.")
            action_cost = 0
            while action_cost == 0 and (not game.winner):
                action = input("Action: ")
                action_result = get_result_of(action, action_points_remaining, game.draw_pile, game.monster_pile, player)
                action_cost = action_result.cost
                print(action_result.message)
                for card in action_result.earned_cards:
                    print(f"You take a {card.name}.")
                player.take_cards(action_result.earned_cards)
                for card in action_result.played_cards:
                    player.party.append(card)
                if player.declares_victory() or action_result.does_end_game:
                    game.winner = player
            action_points_remaining -= action_cost
        game.turn_number += 1
        print("=" * term_width)
    
    print(f"The winner is {game.winner.name}!")

main()