#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from threading import Thread
from typing import Any, Self, Optional
from Interfaces.OpoTestsInterface import OpoTestsInterface
from Models.RequestModel import RequestModel
from socket import socket as Socket
from socket import AF_INET as ADDRESS_FAMILY_IPV4
from socket import SOCK_STREAM as SOCKET_STREAM
from socket import SOL_SOCKET as SOCKET_LAYER
from socket import SO_REUSEADDR as SOCKET_REUSE_ADDRESS
from Utils.Utils import Utils
from Utils.Check import Check

class HTTPSocketDriver:

    def __init__(self:Self, ot:OpoTestsInterface, inputs:Optional[dict[str, Any|None]|tuple[Any|None]|list] = None) -> None:
        self.ot:OpoTestsInterface = ot
        self.__inputs:dict[str, Any|None] = Utils.get_dictionary(inputs)
        self.__socket:Socket|None = None
        self.__address:str = "localhost"
        self.__port:int = 8080
        self.__maximum_connections:int =  5
        self.__cache_size:int = 1024
        self.__public_path:str = "/Public"
        self.__index_files:list[str] = ["index.html", "index.htm"]
        self.__working:bool = False

    def update(self:Self) -> None:
        self.__address = self.ot.settings.get(("address", "http_address"), self.__inputs, self.__address)
        self.__port = self.ot.settings.get(("port", "http_port"), self.__inputs, self.__port)
        self.__maximum_connections = self.ot.settings.get(("maximum_connections", "http_maximum_connections"), self.__inputs, self.__maximum_connections)
        self.__cache_size = self.ot.settings.get(("cache_size", "http_cache_size"), self.__inputs, self.__cache_size)
        self.__public_path = self.ot.settings.get(("public_path", "http_public_path"), self.__inputs, self.__public_path)
        self.__index_files = self.ot.settings.get(("index_files", "http_index_files"), self.__inputs, self.__index_files)

    def __listen(self:Self) -> None:
        while self.__working:
            try:

                client:Socket
                address:str
                port:int
                data:bytes = b""
                response:Any|None
                parameters:dict[str, Any|None]

                client, (address, port) = self.__socket.accept()

                while True:

                    buffer:bytes = client.recv(self.__cache_size)

                    data += buffer
                    if len(buffer) < self.__cache_size:
                        break

                response, parameters = self.ot.routes.go(RequestModel(data.decode("utf-8"), {
                    "address" : address, 
                    "port" : port, 
                    "client" : client, 
                    "public_path" : self.__public_path, 
                    "index_files" : ["", *self.__index_files]
                }))
                
                client.sendall(
                    str(Utils.string_variables((
                        "{http_protocol}/{http_version} {http_code} {http_message}\r\n" + 
                        "Content-Type: {mime}"
                    ), {
                        "http_protocol" : "HTTP",
                        "http_version" : "1.1",
                        "http_code" : 200,
                        "http_message" : "OK",
                        "mime" : "text/plain", 
                        **parameters
                    })).encode("utf-8") + 
                    b"\r\n\r\n" + 
                    (
                        str(response).encode("utf-8") if Check.is_string(response) else 
                        response if Check.is_bytes(response) else 
                        Utils.json_encode(response).encode("utf-8") if Check.is_json_object(response) else
                    bytes(response))
                )

                client.close()

            except Exception as exception:
                self.ot.exception(exception, "opo_tests_http_error_listen", {
                    "address" : self.__address, 
                    "port" : self.__port
                })

    def start(self:Self) -> None:

        self.__working = True
        self.__socket = Socket(ADDRESS_FAMILY_IPV4, SOCKET_STREAM)

        self.update()

        try:

            self.__socket.setsockopt(SOCKET_LAYER, SOCKET_REUSE_ADDRESS, 1)
            self.__socket.bind((self.__address, self.__port))
            self.__socket.listen(self.__maximum_connections)

            Thread(target=self.__listen).start()

            self.ot.print("info", "opo_tests_http_started", {
                "address" : self.__address, 
                "port" : self.__port,
            }, "HTTP server started on '{address}:{port}'.")

        except Exception as exception:
            self.close()
            self.ot.exception(exception, "opo_tests_http_error_start", {
                "address" : self.__address, 
                "port" : self.__port
            })

    def close(self:Self) -> None:
        self.__working = False