import socket
import threading
import os
import subprocess
import datetime
#from z7 import z7 



# Limit to 16 concurrent clients
connection_limit = threading.Semaphore(16)
CHUNK_SIZE = 8192  # 8KB chunks for memory efficiency
UPLOAD_DIR = ''


def get_content_type(filepath):
    """Simple check to tell the browser what the file is."""

    ext = os.path.splitext(filepath)[1].lower()
    types = {
        '.html': 'text/html',
        '.jpg':  'image/jpeg',
        '.jpeg': 'image/jpeg',
        '.png':  'image/png',
        '.mp4':  'video/mp4',
        '.mp3':  'audio/mpeg',
        '.pdf':  'application/pdf',
    }
    # Default to 'application/octet-stream' (download) if unknown
    return types.get(ext, 'application/octet-stream')


def http_zreq(zreq_str):
    #req_code, path = zreq_str.split('/', 1)
   # print('34:', req_code, path)
    print('36:', zreq_str)
    path    = ''
    i       = zreq_str.find('/')
    if i != -1: 
        path = zreq_str[i+1:]
        zreq_str = zreq_str[:i]

    filepath = './'+path
    tokens   = zreq_str.split('++')
    req_code  = tokens[0]
    
    print('41:', i,req_code, zreq_str, path, filepath)
                
    
    ######################################################
    if req_code == ':exe':
        
        r = 'Error'
        try:   
            result = subprocess.run(' '.join(tokens[1:]), shell=True, check=True, text=True, capture_output=True)    
            if not  result.stderr:
                r = result.stdout
                
        except subprocess.CalledProcessError as e:
            r = '[Error] ' + e
            print(f"477: {"[ERROR]":16} in cmd_str(): {e}")

        print('51:', r )            

            ##################################################
    

    ######################################################
    elif req_code == ':r_file':

        print(f'135: {"[info]":16} filepath: { filepath}')
        if os.path.isfile(filepath):
            print(f'135: {"[info]":16} filepath: { filepath}')
            return ':r_file', path
            #client_conn.sendall(header.encode('utf-8'))



                #   HTTP/post 


    ##################################################
    elif req_code == ':w_file':
        
        filename = os.path.basename(path)
        filepath = os.path.join(UPLOAD_DIR, path)
        dirpath  = os.path.dirname(filepath)
        if dirpath:
            os.makedirs(dirpath, exist_ok=True)
        


    
    ##################################################
    #append data 
    #elif req_code == ':mk':
    elif req_code == ':rm':
        #filepath = (reqData.split(' ')[1]).lstrip('/') 
        
        # Check if the file exists before attempting to remove it
        if os.path.exists(filepath):
            os.remove(filepath)
            #client_conn.sendall(b"The filepath has been removed.")
        else:
            print('error')
            #client_conn.sendall(b"The filepath does not exist.")
    return '' ,''



def http_zreq(zreq_str):
    #req_code, path = zreq_str.split('/', 1)
   # print('34:', req_code, path)
    print('36:', zreq_str)
    path    = ''
    i       = zreq_str.find('/')
    if i != -1: 
        
        path     = '/'.join([_ for _ in zreq_str[i:].split('/') if _])
        zreq_str = zreq_str[:i]

    filepath = './'+path
    tokens   = zreq_str.split('++')
    req_code  = tokens[0]
    
    print('41:', i,req_code, zreq_str, path)
                
    
    ######################################################
    if req_code == ':exe':
        
        r = 'Error'
        try:   
            result = subprocess.run(' '.join(tokens[1:]), shell=True, check=True, text=True, capture_output=True)    
            if not  result.stderr:
                r = result.stdout
                
        except subprocess.CalledProcessError as e:
            r = '[Error] ' + e
            print(f"477: {"[ERROR]":16} in cmd_str(): {e}")

        print('51:', r )            

            ##################################################
    

    ######################################################
    elif req_code == ':r_file':

        print(f'135: {"[info]":16} filepath: { filepath}')
        if os.path.isfile(filepath):
            print(f'135: {"[info]":16} filepath: { filepath}')
            return ':r_file', path
            #client_conn.sendall(header.encode('utf-8'))



                #   HTTP/post 


    ##################################################
    elif req_code == ':w_file':
        
        filename = os.path.basename(path)
        filepath = os.path.join(UPLOAD_DIR, path)
        dirpath  = os.path.dirname(filepath)
        if dirpath:
            os.makedirs(dirpath, exist_ok=True)
        


    
    ##################################################
    #append data 
    #elif req_code == ':mk':
    elif req_code == ':rm':
        #filepath = (reqData.split(' ')[1]).lstrip('/') 
        
        # Check if the file exists before attempting to remove it
        if os.path.exists(filepath):
            os.remove(filepath)
            #client_conn.sendall(b"The filepath has been removed.")
        else:
            print('error')
            #client_conn.sendall(b"The filepath does not exist.")
    return '' ,''


def handle_client(client_conn, addr):
    with connection_limit:
        # STEP 1: Set the timer (5 seconds)
        client_conn.settimeout(5.0) 
        
        print(f"066: {"[SLOT TAKEN]":16} {addr} is using 1 of 16 slots.")
        try:
            # Read the request header
            reqData = client_conn.recv(1024).decode('utf-8', errors='ignore')
            print(f'070:', '#'*48)
            #print(f'034: {"[req]":16}  {reqData.strip()}')
            if not reqData: return
            
            #this use in http req 
            headers = reqData.split("\r\n")
            first_line = headers[0]
            
            ##################################################
            #zreq_str   parse 
            req_code  = '' 
            tokens   = [] 
            path     = ''
            filepath = '' 
            if reqData.startswith(':'):
                zreq_str = reqData.split('\n')[0]
                print('36:', zreq_str)


                tokens  = zreq_str.split(' ')
                req_code  = tokens[0]
                if tokens[-1].startswith('/'):
                    path     = '/'.join([_ for _ in tokens[-1].split('/') if _])
                filepath = './'+path
               
                print('222: zreq_str: ', zreq_str)
                print('222: req_code : ', req_code )
                print('223: path    : ',   path)


            

            ######################################################
            if req_code == ':exe':
                
                r = ':error'
                try:   
                    result = subprocess.run(' '.join(tokens[1:]), shell=True, check=True, text=True, capture_output=True)    
                    if not  result.stderr:
                        r = result.stdout
                        
                except subprocess.CalledProcessError as e:
                    r = ':error ' + e
                    print(f"477: {"[ERROR]":16} in cmd_str(): {e}")

                client_conn.sendall(r.encode('utf-8'))
                print('51:', r )            

                    ##################################################
            

            ######################################################
            elif req_code == ':r_file':

                print(f'135: {"[info]":16} filepath: { filepath}')
                if os.path.isfile(filepath):
                    print(f'135: {"[info]":16} filepath: { filepath}')
                    

                    # STREAMING LOGIC: Read and send in chunks
                    client_conn.sendall(b':[send_file]\n')
                    with open(filepath, 'rb') as f:
                        while True:
                            chunk = f.read(CHUNK_SIZE)
                            if not chunk: break
                            client_conn.sendall(chunk)

                else:
                    
                    client_conn.sendall(b":[error] filePath not exit")



            ##################################################
            elif req_code == ':w_file':
                
                filename = os.path.basename(path)
                filepath = os.path.join(UPLOAD_DIR, path)
                dirpath  = os.path.dirname(filepath)
                if dirpath:
                    os.makedirs(dirpath, exist_ok=True)
                


            
            ##################################################
            #append data 
            #elif req_code == ':mk':
            elif req_code == ':rm':
                #filepath = (reqData.split(' ')[1]).lstrip('/') 
                
                # Check if the file exists before attempting to remove it
                if os.path.exists(filepath):
                    os.remove(filepath)
                    #client_conn.sendall(b"The filepath has been removed.")
                else:
                    print('error')
                    #client_conn.sendall(b"The filepath does not exist.")

            

            ##################################################
            #   HTTP/post 
            elif first_line.startswith("POST"):
                # Simple logic: extract filename from path /upload/filename.ext
                path = first_line.split(' ')[1]
                filename = os.path.basename(path)
                filepath = os.path.join(UPLOAD_DIR, filename)

                # Find where the actual file data starts (after \r\n\r\n)
                # Note: Real HTTP uploads are complex; this is the 'Simple' version
                with open(filepath, 'wb') as f:
                    print(f"Receiving upload: {filename}...")
                    while True:
                        chunk = client_conn.recv(CHUNK_SIZE)
                        if not chunk: break
                        f.write(chunk)
                
                client_conn.sendall(b"HTTP/1.1 200 OK\r\n\r\nUpload Complete")
            

            ##################################################
            #   http/get_file
            elif first_line.startswith("GET"):
                # Extract path (e.g., "GET /zfs/video.mp4 HTTP/1.1" -> "/zfs/video.mp4")
                s = reqData.split(' ')[1]
                filepath = s.lstrip('/') 
                req_code  = ''
                print(f'106: {"[info]":16} {s} filepath: { filepath}')
                if s.startswith('/:'):
                    req_code, filepath = http_zreq(s[1:])
                    
                # Security: Remove leading slash for OS path joining
                


                print(f'044: {"[info]":16} {req_code} filepath: {  filepath}')

                if req_code == ':r_file': 
                    content_type = get_content_type(filepath)
                    filesize = os.path.getsize(filepath)
                    
                    #old header 
                    #header = (
                    #    f"HTTP/1.1 200 OK\r\n"
                    #    f"Content-Type: application/octet-stream\r\n"
                    #    f"Content-Length: {filesize}\r\n"
                    #    f"Accept-Ranges: bytes\r\n\r\n"
                    #)
                        # The 'Content-Type' is the magic key for the browser
                    header = (
                        f"HTTP/1.1 200 OK\r\n"
                        f"Content-Type: {content_type}\r\n"
                        f"Content-Length: {filesize}\r\n"
                        f"Accept-Ranges: bytes\r\n"
                        f"Connection: close\r\n\r\n"
                    )
                    client_conn.sendall(header.encode('utf-8'))

                    # STREAMING LOGIC: Read and send in chunks
                    with open(filepath, 'rb') as f:
                        while True:
                            chunk = f.read(CHUNK_SIZE)
                            if not chunk: break
                            client_conn.sendall(chunk)

                else:
                    client_conn.sendall(b"HTTP/1.1 404 Not Found\r\n\r\nFile Not Found")

            
            ######################################################
            elif reqData.startswith("01"):

                r = 'Error'
                try:   
                    result = subprocess.run(reqData[2:], shell=True, check=True, text=True, capture_output=True)    
                    if not  result.stderr:
                        r = result.stdout
                except subprocess.CalledProcessError as e:
                    r = '[Error] ' + e
                    print(f"477: {"[ERROR]":16} in cmd_str(): {e}")

                
                #print(f'095: {"[RES_DATA]":16} {r.strip()}')

                client_conn.sendall(r.encode('utf-8'))

            ##################################################
            elif reqData.startswith("12"):      #r_ifile 

                filepath = (reqData.split(' ')[1]).lstrip('/') 
                
                print(f'135: {"[info]":16} filepath: { filepath}')

                if os.path.isfile(filepath):
                    print(f'135: {"[info]":16} filepath: { filepath}')
                    
                    #client_conn.sendall(header.encode('utf-8'))

                    # STREAMING LOGIC: Read and send in chunks
                    client_conn.sendall(b'file_send_it')
                    with open(filepath, 'rb') as f:
                        while True:
                            chunk = f.read(CHUNK_SIZE)
                            if not chunk: break
                            client_conn.sendall(chunk)

                else:
                    client_conn.sendall(b"[Error] filePath not exit")
                        #   HTTP/post 


            ##################################################
            elif reqData.startswith("13"):      #w_ifile 
                print('156:', reqData)
                # Simple logic: extract filename from path /upload/filename.ext
                path     = reqData.split(' ')[1]
                filename = os.path.basename(path)
                filepath = os.path.join(UPLOAD_DIR, path)
                dirpath  = os.path.dirname(filepath)
                if dirpath:
                    os.makedirs(dirpath, exist_ok=True)
                

                print('163-5:', filepath)
                with open(filepath, 'wb') as f:
                    print(f"Receiving upload: {filename}...")
                    while True:
                        chunk = client_conn.recv(CHUNK_SIZE)
                        if not chunk: break
                        f.write(chunk)
                
                client_conn.sendall(b"upload file successfully")
            
            ##################################################
            #append data 
            elif reqData.startswith("15"):      #rm_ifile 
                filepath = (reqData.split(' ')[1]).lstrip('/') 
                
                # Check if the file exists before attempting to remove it
                if os.path.exists(filepath):
                    os.remove(filepath)
                    client_conn.sendall(b"The filepath has been removed.")
                else:
                    client_conn.sendall(b"The filepath does not exist.")

            # 2. Handle Local Protocol
            else:
                client_conn.sendall(b"Local Protocol: Received your data")

        ############################################################

        except FileNotFoundError:
            print(f"File not found: ")
        except IOError as e:
            print(f"An I/O error occurred: {e}")
        except socket.timeout:
            # STEP 3: Handle the 'Slow' client
            print(f"022: {"[TIMEOUT]":16} {addr} was too slow. Kicking them out to free a slot.")        
        except Exception as e:
            print(f"083: {"[ERROR]":16} {addr}: {e}")
        finally:
            client_conn.close()
            print(f"088: {"[SLOT FREE]":16} {addr} disconnected.")


def start_server():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(('0.0.0.0', 7856))
    server.listen(20)
    print("Server online at port 7856. Ready to stream files.")
    
    while True:
        conn, addr = server.accept()
        threading.Thread(target=handle_client, args=(conn, addr)).start()


if __name__ == "__main__":
    # Create the folder if it doesn't exist for testing
    if not os.path.exists('zfs'): os.makedirs('zfs')
    start_server()