import json
import socket
import sys
import threading
from Classes.Game.Player import Player
from Classes.System.GameManager import GameManager

from Classes.System.Network.EventHandler import UDPEventHandler, TCPEventHandler
from Classes.System.World import World

class NetworkManager:

#class UDP:
#    __Addr:str
#    __Port:str
#    __BufferSize:int = 1024
#    __udpSocket:socket
#    __users:dict
#    __eventHandler: UDPEventHandler
#    __UDPClientThread:threading.Thread
#    __gameManager:GameManager
#
#    def __init__(self, Addr:str, Port:str, gameManager:GameManager):
#        self.__Addr = Addr
#        self.__Port = int(Port)
#        self.__gameManager = gameManager
#        self.__users = {}
#        self.__eventHandler = {}
#        self.__udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#        
#        try:
#            self.__udpSocket.bind((self.__Addr, self.__Port))
#        except OSError as e:
#            print(f"Error binding UDP socket: {e}")
#        
#        self.__UDPClientThread = threading.Thread(target=self.receive)
#        self.__UDPClientThread.start()
#
#    # handles incoming udp data
#    def receive(self):
#        while True:
#            data, address = self.__udpSocket.recvfrom(self.__BufferSize)
#            message = data.decode()
#            messageJson = json.loads(message)
#            user = messageJson.get("user")
#
#            if user not in self.__users:
#                self.__users[user] = address  # Storing new user in dictionary
#
#            # Process the message and handle accordingly
#            self.__eventHandler.handleUDPEvents(messageJson, self.__serverWorld)
#            print(f"Received message from {address}: {message}")
#
#    def broadcast(self, payload:dict):
#        for user_address in self.__users.values():
#            self.__udpSocket.sendto(json.dumps(payload).encode(), user_address)
#
#    def send(self, payload:dict, user:str):
#        if user in self.__users:
#            self.__udpSocket.sendto(json.dumps(payload).encode(), self.__users[user])
#            
    class TCP:
        __Addr:str
        __Port:str
        __BufferSize:int = 1024
        __tcpSocket:socket
        __eventHandler: dict   
        __users: dict     
        __TCPClientThread:threading.Thread
        __gameManager:GameManager
        
        def __init__(self, Addr:str, Port:str, gameManager:GameManager):
            self.running = True
            self.__Addr = Addr
            self.__Port = int(Port)
            self.__gameManager = gameManager
            self.__eventHandler = {}
            self.__tcpSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.__tcpSocket.bind((self.__Addr, self.__Port))
            self.__tcpSocket.listen()
                                    
            self.__TCPClientThread = threading.Thread(target=self.accept_connections)
            self.__TCPClientThread.start()

        def accept_connections(self):
            while self.running:
                try: 
                    client_tcp_socket, client_address = self.__tcpSocket.accept()
                    print(f"Connected with {client_address}")
                    self.__gameManager.getPlayers()[client_address] = client_tcp_socket
                    self.__eventHandler[client_address] = TCPEventHandler(client_tcp_socket)
                    client_handler_thread = threading.Thread(
                        target=self.receive,
                        args=(client_tcp_socket, client_address)
                    )
                    client_handler_thread.start()
                except Exception as e:
                    print(f"tcp socket failed to accept connection due to error: {e}")
                    pass

        def receive(self, client_socket, client_address):
            while self.running:
                try:
                    data = client_socket.recv(self.__BufferSize)
                    if not data:
                        print(f"Connection with {client_address} closed.")
                        break 

                    message = data.decode()
                    messageJson = json.loads(message)
                    user = messageJson.get("user")

                    # creates a user and counts how many currently are connected to  the server
                    # if enough users for a round are connected the server has to start the game
                    if user not in self.__gameManager.getPlayers():
                        if messageJson["event"] == "login":
                            self.__gameManager.addPlayers(Player(messageJson["username"], messageJson["deck"]), client_socket, client_address) 
                            print(f"connected users {len(self.__gameManager.getPlayers())}")

                    self.__eventHandler[client_address].handleTCPEvents(messageJson, self.__gameManager, client_address)
                    print(f"Received message from {client_address}: {message}")

                except socket.error as e:
                    if e.errno == 10054:
                        print(f"Connection with {client_address} forcibly closed by remote host.")
                        break
                    print(f"Socket error receiving data from {client_address}: {e}")
                except json.JSONDecodeError as e:
                    print(f"JSON decoding error receiving data from {client_address}: {e}")
                except Exception as e:
                    print(f"Error receiving data from {client_address}: {e}")

        def broadcast(self, payload:dict):
            for user in self.__gameManager.getPlayers().values():
                user["socket"].send(json.dumps(payload).encode())

        def send(self, payload: dict, user: str):
            players = self.__gameManager.getPlayers()

            if user in players and "socket" in players[user]:
                players[user]["socket"].send(json.dumps(payload).encode())
            else:
                print(f"user '{user}' or socket was not found 'socket' failed to send data.")

    tcp: TCP
    # udp: UDP

    def __init__(self, Addr:str, TCPPort:str, UDPPort:str, gameManager:GameManager):
        self.tcp = self.TCP(Addr, TCPPort, gameManager)
        # self.udp = self.UDP(Addr, UDPPort, gameManager)