import math
from typing import Any, Dict, List
import pandas as pd
import itertools
from copy import deepcopy

from fantasy_data.services import load_teams_data


MAX_PLAYERS_PER_TEAM = 3

TEAMS = load_teams_data()


def get_team_name(team_id):
    """
    Returns the team name for a given team ID.
    """
    team = TEAMS[team_id - 1]
    return {"id": team["id"], "name": team["short_name"], "code": team["code"]}


def format_transfer(player):
    df = pd.DataFrame([player])
    df = df[
        [
            "id",
            "web_name",
            "element_type",
            "team",
            "now_cost",
            "total_points",
            "status",
            "opta_code",
            "score",
            "Difficulty",
            "XP",
        ]
    ]
    df["team"] = df["team"].apply(lambda x: get_team_name(x))

    return df.iloc[0].to_dict()


def validate_trasfer_selection(player, old_XP, budget,ids,count,is_availble = True):
    """
    Validates a transfer selection:
      - Checks if the player Xp is not less than the Old.
      - Checks if the player cost is within the budget.
    """
    # Add Check For Duplicate Player
    if player["id"] in ids or count.get(player["team"], 0) >= MAX_PLAYERS_PER_TEAM:
        return False

    #flexability = 1 if is_availble else 2
    if player["XP"] < old_XP :

        # raise ValueError(f"Player {player['web_name']} is not in the transfer list.")
        return False
    if player["now_cost"] > budget:
        #raise ValueError(f"Player {player['web_name']} exceeds the budget.")
        return False
    if player["availability"] != 1:
        #raise ValueError(f"Player {player['web_name']} exceeds the budget.")
        return False

    return True
# Assume the following functions and constants are imported or defined elsewhere:
# - validate_trasfer_selection
# - format_transfer
# - MAX_PLAYERS_PER_TEAM

def get_transfer_suggestions_for_player(
    player_out: Dict[str, Any],
    all_players: List[Dict[str, Any]],
    team: List[int],
    team_count: Dict[int, int],
    budget: int,
):
    """
    For a single player to transfer out, return the next batch of best transfer suggestions.
    Suggestions are sorted by XP, then price. Returns up to batch_size suggestions starting from 'start'.
    """
    # Filter and sort candidates
    
    suggestion = {}
    suggested_players_ids = set(team)
    team_player_count = team_count.copy()
    player_selling_price = player_out["now_cost"]
    player_price_change = player_out["cost_change_start"]
    if player_price_change > 0:
        player_selling_price = ((player_selling_price * 10 - player_price_change ) + math.floor(player_selling_price * 0.5)) / 10
    bank = budget + player_selling_price
    candidates = [p for p in all_players if p["element_type"] == player_out["element_type"] and p["XP"] >= player_out["XP"] and p["now_cost"] <= bank]
    candidates = sorted(candidates, key=lambda x: (x["XP"], x["now_cost"]), reverse=True)
    for candidate in candidates:
        if candidate["id"] in suggested_players_ids:
            continue
        if validate_trasfer_selection(
            candidate,
            player_out["XP"],
            bank,
            suggested_players_ids,
            team_player_count,
            player_out["status"] in "ad",
        ):
            gain = candidate["XP"] - player_out["XP"]
            if gain > 0:
                suggestion[player_out["web_name"]] = format_transfer(candidate)
                suggestion['gain'] = round(gain,2)
                suggestion['bank'] = round(bank - candidate["now_cost"],1)
            return suggestion
    # Return the requested batch
    return {}
def get_ai_transfer_suggestions(
    players: List[Dict[str, Any]],
    all_players: List[Dict[str, Any]],
    team: List[int],
    team_count: Dict[int, int],
    budget: int,

):
    '''
        get best ai suggestion for each player in one  on  one fashion if a transfer is possible
        then return the best suggestion based on total gain and remaining budget in case of 2 suggestions having the same gain
    '''
    suggestion_result = []

    for player in players:
        suggestion = get_transfer_suggestions_for_player(player, all_players, team, team_count, budget)
        if suggestion:
            suggestion_result.append(suggestion)
    suggestion_result.sort(key=lambda x: (x.get('gain',0), x.get('bank',0)), reverse=True)
    return suggestion_result[0] if suggestion_result else []
def get_transfer_suggestions_for_players(
        
    players_out: List[Dict[str, Any]],
    all_players: List[Dict[str, Any]],
    team: List[int],
    team_count: Dict[int, int],
    budget: float,
    start: int = 0,
    batch_size: int = 3,
):
    """
    For multiple players to transfer out, return the next batch of best transfer combinations.
    Each combination is a mapping of player_out to best valid replacement.
    Results are sorted by total XP gain, then remaining budget.
    """
    # Prepare candidates for each player out
    candidates_per_out = []
    for player_out in players_out:
        candidates = candidates = [
    p for p in all_players
    if p["element_type"] == player_out["element_type"] and p["XP"] >= player_out["XP"] and p["now_cost"] <= budget
]
        candidates = sorted(candidates, key=lambda x: (x["XP"], x["now_cost"]), reverse=True)
        candidates_per_out.append(candidates)

    # Generate all possible replacement combinations (cartesian product)
    all_combos = list(itertools.product(*candidates_per_out))
    results = []
    for combo in all_combos:
        valid = True
        selection = {}
        used_ids = set(team)
        team_player_count = deepcopy(team_count)
        bank = budget
        total_gain = 0
        #used_ids = set()
        for idx, candidate in enumerate(combo):
            player_out = players_out[idx]
            # Ensure no duplicate replacements
            if candidate["id"] in used_ids:
                valid = False
                break
            if not validate_trasfer_selection(
                candidate,
                player_out["XP"],
                bank,
                used_ids,
                team_player_count,
                player_out["status"] in "ad",
            ):
                valid = False
                break
            selection[player_out["web_name"]] = format_transfer(candidate)
            # suggested_players_ids.add(candidate["id"])
            used_ids.add(candidate["id"])
            team_player_count[candidate["team"]] = team_player_count.get(candidate["team"], 0) + 1
            bank -= candidate["now_cost"]
            total_gain += candidate["XP"] - player_out["XP"]
        if valid and len(selection) == len(players_out):
            results.append({
                f"transfers": selection,
                "rem_balance": round(bank, 1),
                "total_gain": round(total_gain, 2),
                "combo": [p["id"] for p in combo],
            })
    # Sort by total_gain desc, then rem_balance desc
    results.sort(key=lambda x: (x["total_gain"], x["rem_balance"]), reverse=True)
    print(f" Total Available Combos for {len(players_out)} players : {len(all_combos)}")
    print(f" Total Valid Suggestions : {len(results)}")
    # Return the requested batch
    return results[:30]
