import socket
import threading
import json
import datetime


class Server:
    def __init__(self, port: int, multi_threading=True):
        self.port = port
        self.threads = multi_threading

    @staticmethod
    def _request_data(headers_string: str, _socket: socket.socket):
        request_obj = {}
        headers_list = headers_string.splitlines()
        request_data = headers_list.pop(0).split(' ')
        request_obj['Method'] = request_data[0]
        request_obj['Path'] = request_data[1]
        request_obj['Protocol'] = request_data[2]
        for elem in headers_list:
            if ': ' in elem:
                header_splited = elem.split(': ')
                request_obj[f'{header_splited[0]}'] = header_splited[1]
        if request_obj.get('Content-Length'):
            body = _socket.recv(int(request_obj['Content-Length']))
            request_obj['Body'] = body.decode('utf-8')
        return request_obj

    @staticmethod
    def _response(body: dict, _socket: socket.socket, request):
        real_body = json.dumps(body, indent=4)
        body_length = len(real_body)
        _date = datetime.datetime.now()
        date = _date.strftime("%a, %d %b %Y %H:%M:%S")
        headers = f'''{request['Protocol']} 200\nDate: {date}\nContent-Type: application/json\nContent-Length: {body_length}\r\n\r\n{real_body}'''
        _socket.sendall(bytes(headers, 'utf-8'))
        _socket.close()

    def _get_socket_date(self, _socket: socket.socket):
        header_data = ""
        while True:
            data = _socket.recv(1)
            header_data += data.decode('utf-8')
            if data == b'\r':
                data = _socket.recv(1)
                header_data += data.decode('utf-8')
                if data == b'\n':
                    data = _socket.recv(1)
                    header_data += data.decode('utf-8')
                    if data == b'\r':
                        data = _socket.recv(1)
                        header_data += data.decode('utf-8')
                        if data == b'\n':
                            header_data += data.decode('utf-8')
                            break
        return self._request_data(header_data, _socket)

    def _consume_request(self, _socket: socket.socket):
        data = self._get_socket_date(_socket)
        print(f"{datetime.datetime.now()} - {data.get('Method')} - {data.get('Path')}")
        self._response(data, _socket, data)

    def run(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
        s.bind(('localhost', self.port))
        s.listen(1)
        print('Server up, waiting for requests...')
        if self.threads:
            while True:
                connection, client_address = s.accept()
                threading.Thread(target=self._consume_request, args=(connection,)).start()
        while True:
            connection, client_address = s.accept()
            self._consume_request(connection)


if __name__ == '__main__':
    server = Server(8080)
    server.run()