from websockets.sync.server import serve, ServerConnection, Server
from websockets.exceptions import ConnectionClosedOK
import requests
import json

from .project import Project
from .backend import uploadOrder

import logging
logger = logging.getLogger(__name__)

def listen(host: str, port: int, callback) -> Server:
    '''
    Start a WebSockets server on the specified host and port.

    Parameters
    ----------
    host : str
        The hostname or IP address to bind the server to.
    port : int
        The port number to bind the server to.
    callback : callable
        A function that will be called for each new connection. It should take a `ServerConnection` object as its only argument.

    Returns
    -------
    Server : websockets.sync.server.Server

    Notes
    -----
    This function does not block; it returns immediately after starting the server.

    See Also
    --------
    websockets.sync.server.serve : The underlying function used to start the server.
    '''
    usePort = port
    while usePort - port <= 4:
        try:
            return serve(callback, host, usePort)
        except OSError as error:
            logger.warning(f'Failed to start server ({host}:{usePort}), try using next port number: %s', error)
        usePort += 1

    raise OSError(f'Failed to start server ({host}:{port}) as all port numbers in range ({port}-{port+5}) are already in use')

def handleConnection(websocket: ServerConnection, project: Project, host: str, thread) -> None:
    '''
    Handles a new connection to the WebSockets server.

    Parameters
    ----------
    websocket : ServerConnection
        The newly established connection.
    project : Project
        The current project being worked on.
    host : str
        The hostname or IP address of the server.
    thread :
        A thread object that manages the WebSocket server.


    Returns
    -------
    None

    Notes
    -----
    This function is called for each new connection to the server.
    It handles authentication, sends the project data, and updates the order when a new one is submitted.
    Finally, it confirms the new order with the client.


    See Also
    --------
    websockets.sync.server.serve : The underlying function used to start the server.
    '''
    try:
        sendProject(websocket, project)

        token = getAuthentificationToken(websocket)
        try:
            modifiedOrder = awaitNewOrder(websocket)
        except ConnectionClosedOK:
            # user closed the page
            return

        project.update(modifiedOrder)

        try:
            uploadOrder(project, host, token)
        except requests.exceptions.HTTPError as error:
            match error.response.status_code:
                case 400:
                    logger.error(error.response.content)
                    
            raise

        confirmNewOrder(websocket)
        thread.closeWebsocketServer()
    except:
        sendErrorMessage(websocket)
        thread.closeWebsocketServer()
        raise

def getAuthentificationToken(websocket: ServerConnection) -> str:
    """
    Retrieves an authentication token from the client.

    Parameters
    ----------
    websocket : ServerConnection
        The WebSocket connection object.

    Returns
    -------
    str
        The authentication token.
    """
    websocket.send(json.dumps({'type': 'getToken'}))
    response = websocket.recv()
    data = json.loads(response)
    if data['type'] == 'token':
        return data['token']

def sendProject(websocket: ServerConnection, project: Project) -> None:
    """
    Sends project information to the client.

    Parameters
    ----------
    websocket : ServerConnection
        The WebSocket connection object.
    project : Project
        The project object.
    """
    message = {
        'type': 'projectInformation',
        'untrackedElements': [{
            'type': None,
            'material': None,
            'color': None,
            'height': 0,
            'comment': '',
            'file': path.name,
            'teeth': [],
            'amenities': []
        } for path in project.untrackedFiles],
        'sceneFilePresent': project.sceneFilePresent,
        'order': project.order
    }
    websocket.send(json.dumps(message))

def awaitNewOrder(websocket: ServerConnection) -> object:
    """
    Waits for a new order message from the client and returns the order data.

    Parameters
    ----------
    websocket : ServerConnection
        The WebSocket connection object.

    Returns
    -------
    object
        The order data.
    """
    confirmed = False
    while not confirmed:
        request = websocket.recv()
        data = json.loads(request)
        if data['type'] != 'createOrder':
            continue
        
        return data['order']

def confirmNewOrder(websocket: ServerConnection) -> None:
    """
    Confirms a new order with the client.

    Parameters
    ----------
    websocket : ServerConnection
        The WebSocket connection object.
    """
    websocket.send(json.dumps({
        'type': 'confirmNewOrder'
    }))

def sendErrorMessage(websocket: ServerConnection) -> None:
    websocket.send(json.dumps({
        'type': 'error'
    }))