import ssl
import json
import sqlite3
import threading
from flask import Flask
from socketserver import ThreadingMixIn
from urllib.parse import urlparse, parse_qs, unquote
from consts import SSL_CERT_PATH, SSL_KEY_PATH, HOST, PORT
from http.server import HTTPServer, BaseHTTPRequestHandler

from server.scheduler import UpdateScheduler
from server.consts import LAKE_RELATIONS_PATH
from map_handler.add_new_lake import cut_map_handler
from map_handler.get_lake_relation import get_map_data_handler
from data_processing.input_new_data import input_new_Lidar_data
from data_processing.add_new_lidar_measurement import add_new_lidar_measurement
from map_handler.update_measurements import update_measurements_handler, add_test_data

app = Flask(__name__)
terminate_server = 0


class IceHTTPServer(ThreadingMixIn, HTTPServer):
    def __init__(self, server_address, handler_class, cursor):
        super().__init__(server_address, handler_class)
        self.cursor = cursor

    def get_request(self):
        request, client_address = super().get_request()
        return request, client_address


# Custom HTTP class
class IceHTTP(BaseHTTPRequestHandler):

    def __init__(self, request, client_address, server):
        self.cursor = server.cursor
        super().__init__(request, client_address, server)

    def do_GET(self):
        # Root path
        if self.path == '/':  # NB: temporary root path behavior
            self.send_response(200)
            self.send_header("Content-type", "text/plain")
            self.end_headers()

            self.wfile.write(b"Root path hit!")
        elif self.path == '/get_lake_names':
            try:
                with open(LAKE_RELATIONS_PATH + 'all_lake_names.json', 'r') as file:
                    lake_names = json.load(file)

                # Disable ensure_ascii to keep 'ΓΈ'
                json_data = json.dumps(lake_names, ensure_ascii=False)

                self.send_response(200)
                self.send_header('Content-type', 'application/json')
                self.end_headers()

                self.wfile.write(json_data.encode('iso-8859-1'))  # Special character encoding
            except Exception as e:
                print(f"Failed to fetch lake list: {e}")
                self.send_response(500)
                self.send_header('Content-type', 'application/json')
                self.end_headers()
        elif self.path.startswith('/update_map'):
            parsed_path = urlparse(self.path)
            query_params = parse_qs(parsed_path.query)

            lake_name_param = query_params.get('lake', [''])[0]
            lake_name = unquote(lake_name_param)  # Decode url param

            if lake_name_param:
                get_map_data_handler(self, lake_name, True)  # Get all measurements for selected lake
            else:
                self.send_response(400)
                self.send_header('Content-type', 'application/json')
                self.end_headers()
        elif self.path.startswith('/update_measurements'):
            parsed_path = urlparse(self.path)
            query_params = parse_qs(parsed_path.query)

            lake_name_param = query_params.get('lake', [''])[0]
            lake_name = unquote(lake_name_param)  # Decode url param

            if lake_name_param:
                update_measurements_handler(self, lake_name)
            else:
                self.send_response(400)
                self.send_header('Content-type', 'application/json')
                self.end_headers()
        elif self.path.startswith('/get_relation'):
            parsed_path = urlparse(self.path)
            query_params = parse_qs(parsed_path.query)

            lake_name_param = query_params.get('lake', [''])[0]
            lake_name = unquote(lake_name_param)  # Decode url param

            get_map_data_handler(self, lake_name, False)
        elif self.path.startswith('/add_new_lake'):
            parsed_path = urlparse(self.path)
            query_params = parse_qs(parsed_path.query)

            lake_name = query_params.get('lake', [None])[0]
            if lake_name is not None:
                cell_size = query_params.get('cell_size', [''])[0]
                cell_size_param = unquote(cell_size)  # Decode url param

                if cell_size_param:  # Check if cell_size_param is not an empty string
                    try:
                        # Try to convert the value to a float and the map
                        cell_size_float = float(cell_size_param)
                        cut_map_handler(self, cursor, lake_name, cell_size_float)
                    except ValueError:
                        print("Error: cell_size_param is not a valid float")
                else:
                    cut_map_handler(self, cursor, lake_name)
            else:
                self.send_response(400)
                self.send_header('Content-type', 'application/json')
                self.end_headers()
        elif self.path.startswith('/add_test_data'):
            parsed_path = urlparse(self.path)
            query_params = parse_qs(parsed_path.query)

            lake_name_param = query_params.get('lake', [''])[0]
            lake_name = unquote(lake_name_param)  # Decode url param

            add_test_data(self, lake_name)

    def do_POST(self):
        if self.path.startswith('/new_lidar_data'):
            parsed_path = urlparse(self.path)
            query_params = parse_qs(parsed_path.query)

            lake_name_param = query_params.get('lake', [''])[0]
            lake_name = unquote(lake_name_param)  # Decode url param

            if lake_name:
                input_new_Lidar_data(self.cursor, 1, lake_name)  # hardcoded body of water must change later
            else:
                self.send_response(400)
                self.send_header('Content-type', 'application/json')
                self.end_headers()

        elif self.path.startswith('/add_measurement_position'):
            parsed_path = urlparse(self.path)
            query_params = parse_qs(parsed_path.query)

            lake_name_param = query_params.get('lake', [''])[0]
            lake_name = unquote(lake_name_param)  # Decode url param
            lake_lat_param = query_params.get('latitude', [''])[0]
            lake_lon_param = query_params.get('longitude', [''])[0]

            try:
                lake_lat = unquote(lake_lat_param)  # Decode url param
                lake_lon = unquote(lake_lon_param)  # Decode url param
            except ValueError:
                self.send_response(400, 'Invalid Latitude or Longitude')

            if lake_name:
                add_new_lidar_measurement(lake_name, lake_lat, lake_lon)  # hardcoded body of water must change later
            else:
                self.send_response(400)
                self.send_header('Content-type', 'application/json')
                self.end_headers()


if __name__ == "__main__":
    """Start a server on port 8443 using self defined HTTP class"""
    try:
        # Run the automatic updating of measurement data in a dedicated thread
        #scheduler = UpdateScheduler()
        #scheduler_thread = threading.Thread(target=scheduler.start)
        #scheduler_thread.start()

        # Initialize database connection
        conn = sqlite3.connect('server/database/icedb')
        cursor = conn.cursor()

        # Load SSL certificate and private key
        ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
        ssl_context.load_cert_chain(SSL_CERT_PATH, SSL_KEY_PATH)

        # Create HTTP server with SSL support
        server = IceHTTPServer((HOST, PORT), IceHTTP, cursor)
        server.socket = ssl_context.wrap_socket(server.socket, server_side=True)

        print("Server running on port ", PORT)

        # Run server indefinitely
        server.serve_forever()

    except Exception as e:
        print(f"Server terminated: {PORT}: {e}")