# main.py

import shutil
import uvicorn
from fastapi import FastAPI, UploadFile, File, HTTPException, Query, Request
from fastapi.responses import PlainTextResponse, JSONResponse, FileResponse, Response
from fastapi.middleware.cors import CORSMiddleware

from urllib.parse import urlencode
import time
import os
import stat
import fnmatch
import re
from typing import Iterator, List
from glob import glob
from datetime import datetime


#from ums_db import *

import ls_html
import pwa as pwa 

# --- Setup ---
# Create a FastAPI app instance
app = FastAPI(
    title="zshlo",
    description="zshlo server",
    version="1.1.0"
)

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # Allows all origins, you can specify a list of allowed origins
    allow_credentials=True,
    allow_methods=["*"],  # Allows all methods (GET, POST, etc.)
    allow_headers=["*"],  # Allows all headers
)


# Define the directory where uploaded files will be stored
ZFS_DIR = '../'   #Path("./")   #'./'
#ZFS_DIR = '../'   #Path("./")   #'./'
log_file = './log.txt'

# Ensure the upload directory exists
#ZFS_DIR.mkdir(parents=True, exist_ok=True)
fileTypes = {
    #img file 
    '.png' : 'img',
    '.jpg' : 'img',
    '.jpeg': 'img',
    '.svg' : 'img',
    '.webp': 'img',
                
    #codeFile     
    '.js'   : 'txt', 
    '.css'  : 'txt', 
    '.html' : 'txt', 
    '.md'   : 'txt',
    '.json' : 'txt', 
    '.txt'  : 'txt', 
    '.py'   : 'txt',
    '.csv' : 'txt',
    '.dir':'dir', 
}

def log(msg):
    timestamp = datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
    log_entry = f"{timestamp}; {msg}\n"
    with open(log_file, 'a') as file:
        file.write(log_entry)
        

#this code use for file browser-index9 we dont use it 
def get_ztxt(dirPath):
    # this code is under draft 
    #ztxt   //ztxt also send those data so sever receive less request 
    #   /zz             primary info 
    #   /dir            //contain all entry in thi dir 
    #   /thread.zls     //contain all thread list file 
    #   /zls            default zls also include 
    #   /*.zls          secondary zls 
    #   /icon.*         icon for z-file base64 encoding 
    #   Note:- max z-file size 1MB 
    print('342:', dirPath)
    #   ztxt[0]= zz
    #   ztxt[1]= /dir 
    #   ztxt[2]= thread 
    #   ztxt[3]= *entry 
    
    ztxt = ['','','','']
    tmp  = []
    for entryName in os.listdir(dirPath):
        abs_path = dirPath+'/'+entryName
        tmp.append('\n-'+ entryName)
        #print('347:', abs_path, )
        if os.path.isdir(abs_path): tmp.append('/'); continue
                    
        elif entryName == 'zz':
            with open(abs_path, 'r') as file:
                txt  = file.read()
                ztxt[0] = txt
                            
        elif (entryName == 'zls' or entryName.endswith('..zls') ): 
            #print('355:', abs_path)
            with open(abs_path, 'r') as file:
                txt  = file.read()
                ztxt.append('\n/'+entryName+'\n')
                ztxt.append(txt)

    ztxt[1] = '\n/zdir\n'+''.join(tmp)


    return ''.join(ztxt )


@app.get("/{PATH:path}")
@app.post("/{PATH:path}")
async def core(reqData: Request, PATH: str, file: UploadFile = File(None)):
    
    if PATH == 'favicon.ico': return ''
    #if path == 'null' or route == 'favicon.ico': return ''

    try:
        

        contentType = reqData.headers.get('Content-Type')
        host        = reqData.headers.get('host')
        ref_url     = reqData.headers.get('referer')
        args        = dict(reqData.query_params)
        queryParams = '?' + urlencode(reqData.query_params._dict) if args else ''
        path_tokens = [token for token in PATH.replace('@','/@').split('/') if token]
        formData    = await reqData.form()

        print('94:  ', '#'*48) 
        #print('95:  headers     :', reqData.headers)
        #print('95:  PATH0       :', PATH)
        #print('97:  contentType :', contentType)
        #print('98:  args        :', args)
        #print('96:  query       :', reqData.query_params)
        #print('99:  queryParams :', queryParams)
        #print('100: path_tokens :', path_tokens)
        #print('123: formData    :', formData)
        #print('123: ref_url     :', ref_url)
        #print('101:  ',  '#'*48)



        if not args and  contentType == 'application/json':  args = await reqData.json()
        if args == None : args ={}
        #print('137:', args)

        zreq= {
            'FID'       : 0,            
            'RID'       : 0,
            'size'      : 0,    
            'iblock'    : 0,    
            'reqType'   : 0,    
            'zpath'     : '/'.join(path_tokens),  
        }
        
        print('165:', zreq)

        if path_tokens[0].startswith(':') : 
            zreq['reqType'] = path_tokens[0]
            zreq['zpath']   = '/'.join(path_tokens[1:])
            print('170:', zreq)



        if 1:
            #parse path  
            token0 = path_tokens[0] if path_tokens else ''   
            token1 = path_tokens[1] if len(path_tokens)>1 else ''   
            ztxt   = 0 
            print('122: token0     :', token0  )
            if   token0 == '' and  not 'ls' in args: path_tokens = ['zfs', 'oba', 'zshlo.oba', 'index.html']
            elif PATH.endswith('.oba')  :path_tokens = ['zfs', 'oba', PATH       , 'index.html']
            elif PATH.endswith('.oba/') :path_tokens = ['zfs', 'oba', PATH       , 'index.html']
            elif token0.endswith('.oba'):path_tokens = ['zfs', 'oba']+path_tokens
            elif token0.endswith('.zu') :ztxt=1; path_tokens = ['zfs', 'u', token0] +path_tokens[1:]
            elif token0.endswith('.zs') :ztxt=1; path_tokens = ['zfs', 's', token0] +path_tokens[1:]
            elif token0.endswith('.z')  :ztxt=1; path_tokens.insert(0, 'zfs')    
            elif token0 in 'urs'        :ztxt=1; path_tokens.insert(0, 'zfs')   #  /u  /r  /x  /z 
            elif token1.endswith('.zu') :path_tokens = ['zfs', 'u', ] +path_tokens[1:]
            elif token1.endswith('.zs') :path_tokens = ['zfs', 's', ] +path_tokens[1:]
        
        #if 'ls' in args:  path_tokens.insert(0, 'zfs')
        if len(path_tokens)>1:
            if path_tokens[1] == 'u' and not path_tokens[2].endswith('.zu'): path_tokens[2] += '.zu'
            if path_tokens[1] == 's' and not path_tokens[2].endswith('.zs'): path_tokens[2] += '.zs'
            



        #print('178: token0     :', token0  )
        #print('179: ztxt       :', ztxt  )
        #print('180: path_tokens:', path_tokens )
        
        for k,v in zreq.items():
            print(f'203: {k:12}: {v}')
 

        ###################################################################
        if token0 == 'ums':  #url management system
            return JSONResponse(content={'ERROR': 'File not found'}, status_code=404)
            #draft mode 
            print('157:', args)
            if  args:            return ums(args)
            #   kTable.oba to view ums data 
            filePath = './v2FS/oba/kTable.oba/'
            if not path: filePath+= 'index.html'
            else        :  filePath+= path 
            print('157:', filePath)
            if os.path.isfile(filePath): 
                return FileResponse(filePath)
            else:
                return JSONResponse(content={'ERROR': 'File not found'}, status_code=404)
                
            

        ###################################################################
        
        elif path_tokens[-1] in [ 'manifest.json', 'pwa.js', 'pwa_sw.js']:
            print('174:---')
            txt, mime = pwa.pwa(path_tokens, PATH)
            return Response(content=txt, media_type=mime)        
        
        #   /zfs 
        elif path_tokens and path_tokens[0] =='zfs':
            path = ZFS_DIR+'/'.join(path_tokens[1:])
            
            #if path_tokens[-1][0]  == '@': path+='/zz'

            filePath, dirPath = path, path 
            print('157: ZFS     :- ', ztxt,  path_tokens )
            print('157: filePath:- ', filePath )
            print('157: filePath:- ', args )
            #print('207:', os.path.isfile(filePath))
            #print('207:', os.path.isdir(ZFS_DIR+'/'.join(path_tokens[1:1])))

            
            if  'uploadFile' in args:
                fileName = file.filename
                #zz file upload 
                if fileName.endswith('.zx'):
                    header     = file.file.read(128)
                    path       = header.split(b'\n')[0].decode().strip()
                    upload_dir = ZFS_DIR + path.replace('@', '/@')
                    file_path  = upload_dir+ '/default.zx'
                    file.file.seek(0)
                    os.makedirs(upload_dir, exist_ok=True)

                #file upload 
                else:
                    upload_dir = ZFS_DIR + formData.get('path', 'upload').replace('@', '/@')
                    file_path  = upload_dir+'/'+file.filename
                    #check upload_dir exit
                    if not os.path.isdir(upload_dir):
                        return {"error": f"dir '{upload_dir}' not exit "}

                print('235:', upload_dir, '\n236:', file)

                #check max fileSize 1MB 
                # Use shutil to copy the file from the in-memory buffer to the disk
                # The file.file attribute is a file-like object
                with open(file_path, "wb") as buffer:
                    shutil.copyfileobj(file.file, buffer)
                    
                return {"filename": file.filename, "msg": "File uploaded successfully!"}
            
            #mkdir  in ZFS 
            elif 'zmk' in args:
                
                if os.path.isdir(dirPath): return {'msg': f"Directory '{path}' already exists."}
                #if path.endswith('.z'): os.makedirs(dir_path, exist_ok=True)
                os.makedirs(dirPath, exist_ok=True)
                if os.path.isdir(dirPath): return {'msg': f"Directory '{path}' created successfully."}

            #create file in ZFS 
            elif 'zcf' in args:    
                print('224: zcf',)
                
                if os.path.isfile(filePath): return {'msg': f"Directory '{path}' already exists."}
                #if path.endswith('.z'): os.makedirs(dir_path, exist_ok=True)
                open(filePath, 'a').close()
                if os.path.isfile(filePath): return {'msg': f"Directory '{path}' created successfully."}

            elif 'ls' in args: 
                print('273:', dirPath)
                #entry return with dir char '/'
                if os.path.isdir(dirPath):  return  [_+'/' if os.path.isdir(dirPath+'/'+_) else _ for _ in os.listdir(path)] #os.listdir(tmp_path)
                else                      :  return [] 
            
            elif 'ls.json' in args:
                print('250:', path)                
                dirPath = path 
                #paths = [dir_path+'/'+entry for entry in os.listdir(dir_path)]

                #print('246:', paths)
                #entries = []
                #if path_list = os.listdir(path)
                
                entries = []
                for entry in os.listdir(dirPath):

                    entry_path  = dirPath+'/'+entry    
                    #entry       = os.path.basename(entry_path)
                    entry_stat  = os.stat(entry_path)
                    permissions = stat.filemode(entry_stat.st_mode)
                    fileExt = '.dir' if os.path.isdir(entry_path) else os.path.splitext(entry)[1]
                    

                    entry_info = {
                        'title'   : entry,
                        'type'   : fileTypes.get(fileExt, ''), 
                        'fileExt': fileExt,  #|| '.file', 
                        'size'   : os.path.getsize(entry_path),
                        'mtime'  : time.ctime(entry_stat.st_mtime),
                        'access' : permissions,
                    }

                    #check dir contain thumb for dir 
                    if fileExt == '.dir' and os.path.isfile(f'{entry_path}/thumb.jpg'): 
                        entry_info['thumb'] = 'thumb.jpg'


                    #if entry is token than send all tokens list 
                    if entry == 't':
                        entry_info['thread_list'] = os.listdir(entry_path)
                        
                    #send list  file 
                    #if entry == 'ls':      
                    #    content = ''           
                    #    with open(path+'/ls', 'r') as file:
                    #        content = file.read()
                    #    if content: 
                    #        entries.append({'ls':content})

                    entries.append(entry_info)
                    print('105:', entry_info)
                
                return entries

            #progresive web app
            if 'pwa' in args and ztxt==0: 

                if not os.path.isfile(filePath): 
                    return JSONResponse(content={'ERROR': 'File not found'}, status_code=404)
               
                html_txt = 'index file not found'
                print('287:', filePath)

                #readFile index.html
                with open(filePath, 'r') as file:
                    html_txt  = file.read()


                head     = '<link rel="manifest" href="./manifest.json"></head>'
                body     = '<script src="./pwa.js"></script></body>'                
                html_txt = html_txt.replace('</head>', head)
                html_txt = html_txt.replace('</body>', body)

                return Response(content=html_txt, media_type='text/html')

            elif ztxt==0 and os.path.isfile(filePath)      : 
                return FileResponse(filePath)
            
            elif ztxt: 
                
                
                html_filePath = ZFS_DIR+'oba/zshlo.oba/index.html'
                ztxt     = '' 
                html_txt = 'index file not found'
                print('364:', html_filePath)
                print('365:', filePath)
                

                #readFile index.html
                if os.path.isfile(html_filePath):
                    with open(html_filePath, 'r') as file:
                        html_txt  = file.read()

                #read .zz
                if os.path.isfile(filePath):
                    with open(filePath, 'r') as file:
                        ztxt  = file.read()

                elif os.path.isfile(filePath+'.zz'):
                    with open(filePath+'.zz', 'r') as file:
                        ztxt  = file.read()
                #read /zz
                elif os.path.isfile(filePath+'/zz'):    
                    ztxt = get_ztxt(filePath)

                print('385:',   html_txt[:256])
                print('386:',   ztxt[:256])
                            
                #progressive web app
                if 'pwa' in args: 
                    tmp      = '/'.join(path_tokens[1:])
                    head     = '<link rel="manifest" href="/'+ PATH.strip('/')+'/manifest.json"></head>'
                    body     = '<script src="./pwa.js"></script></body>'                
                    html_txt = html_txt.replace('</head>', head)
                    html_txt = html_txt.replace('</body>', body)

                #ztxt file 
                if len(ztxt):
                    print('387: ztxt_len', len(ztxt) )
                    html_txt = html_txt.replace(
                        "</body>",
                        "<script>"
                            "window.onload = () => {"
                                "const s3 = `" + ztxt + "`; "
                                "z = zparse(s3); zrender(z); "
                            "}"
                        "</script>"
                    )

                return Response(content=html_txt, media_type='text/html')
            
            elif os.path.isfile(filePath+'.zc'): return FileResponse(filePath+'.zc')
            elif os.path.isfile(filePath+'.zz'): return FileResponse(filePath+'.zz')
            elif os.path.isdir(dirPath) and os.path.isfile(dirPath+'/zz'):
                
                
                return Response(content=get_ztxt(dirPath), media_type='text/text')


            else:
                return JSONResponse(content={'ERROR': 'File not found'}, status_code=404)


        #elif zreq['reqType'] == 'exe_cmd':


        #elif zreq['reqType'] == 'r_ifile':
        #elif zreq['reqType'] == 'w_ifile':
        #elif zreq['reqType'] == 'rm_ifile':
        #elif zreq['reqType'] == 'mk_ifile':

        #elif zreq['reqType'] == 'r_rec':
        #elif zreq['reqType'] == 'r_block':
        #elif zreq['reqType'] == 'r_file':
        
        #elif zreq['reqType'] == 'w_rec':
        #elif zreq['reqType'] == 'w_block':
        #elif zreq['reqType'] == 'w_file':
        #elif zreq['reqType'] == 'append':



        #elif zreq['reqType'] == 'rm_ifile':
        #elif zreq['reqType'] == 'mk_ifile':
        #elif zreq['reqType'] == 'r_ifile':
        #elif zreq['reqType'] == 'w_ifile':
        #elif zreq['reqType'] == 'append_ifile':

        #elif zreq['reqType'] == 'ls':
        #elif zreq['reqType'] == 'rm_dir':
        #elif zreq['reqType'] == 'mk_dir':



                        
        elif zreq['reqType'] == 'ls.html':
            #tmp_path = '/'.join(path_tokens[1:])

            #title    = 'home' if not path_tokens else path_tokens[-1]

            #print('427:', path_tokens, title, tmp_path)
            tpp      = '' 
            path = zreq['zpath']
            ls = os.listdir('../'+path)

            return PlainTextResponse(content='<br>'.join(ls), media_type="text/html")
            

        





    except Exception as e:
        # Catch any exceptions during the file read process
        raise HTTPException(status_code=500, detail=f"An error occurred while reading the file: {e}")        



    



