"""Instantly find and access all your Nextcloud data files"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../notebooks/10_exploring-your-remote-data.ipynb.

# %% auto 0
__all__ = ['RemoteData']

# %% ../notebooks/10_exploring-your-remote-data.ipynb 18
import nc_py_api 
from nc_py_api import Nextcloud 

import humanize
import polars as pl 
import itables
from itables.widget import ITable
import os 
from pathlib import Path 
import time 
import re 
from IPython.display import HTML, display

# %% ../notebooks/10_exploring-your-remote-data.ipynb 19
def _node_to_dataframe(fsnode): 
    '''Convert `fsnode` object to polars a single row polars dataframe.'''

    df = pl.DataFrame({'path': fsnode.user_path, 'size': fsnode.info.size, 'mimetype': fsnode.info.mimetype, 'modified': fsnode.info.last_modified, 
                   'isdir': fsnode.is_dir, 'ext': os.path.splitext(fsnode.user_path)[1]})

    return df 

class RemoteData(object): 
    
    # See: https://help.nextcloud.com/t/using-nc-py-api-i-cant-download-any-file-due-to-ssl-certificte-verify-failed/194019 
    nc_py_api.options.NPA_NC_CERT = False 
    
    # keep full dataframe 
    itables.options.maxBytes = 0
    itables.init_notebook_mode()

    def __init__(self, configuration, search_regex='', searchBuilder={}): 
        '''Recursively scan the contents of a remote webdav server as specified by `configuration`. 
        '''

        # parse configuration 
        m = re.match('(^https://[^/]+/)(.*)', configuration['url'])
        nextcloud_url, self.cache_dir = m.groups()
        nc_auth_user = configuration['user']
        nc_auth_pass = configuration['password'] 
        
        

        print(f'Please wait while scanning all file paths in remote folder...')
        
        # Instantiate Nextcloud client 
        self.nc = Nextcloud(nextcloud_url=nextcloud_url, nc_auth_user=nc_auth_user, nc_auth_pass=nc_auth_pass)

        # query webdav server to obtain file listing 
        fs_nodes_list = self.nc.files.listdir(self.cache_dir, depth=-1, exclude_self=False) 
        
        n_paths = len(fs_nodes_list)

        # initialize polars dataframe with first row to fix schema 
        self.df = _node_to_dataframe(fs_nodes_list[0])

        #sum the sizes to find the total storage space
        total_size_bytes = self.df['size'].sum()
        total_size = humanize.naturalsize(total_size_bytes, True)
        
        for fsnode in fs_nodes_list[1:]: 
            self.df.extend(_node_to_dataframe(fsnode))

        # create interactive table 
        self.itable = ITable(
                    self.df,
                    layout={"top1": "searchBuilder"},
                    select=True,
                    search={"regex": True, "caseInsensitive": True, "search": search_regex},  
                    searchBuilder=searchBuilder, 
                    scrollY="500px", scrollCollapse=True, paging=False, 
                ) 

        print(f"Ready building file table for '{self.cache_dir}'")
        print(f'Total number of files and directories: {n_paths}')
        print(f'Total size of the files: {total_size}')
    
    def download_selected(self, cache_dir=None): 
        '''Download selected files (blue rows) from `table` to default local cache directory. 
        
        A custom `cache_dir` can be specified. '''
        
        # create cache path 
        if cache_dir is None: 
            cache_path = Path.home().joinpath('.cache', 'fairdatanow')
        else: 
            cache_path = Path.home().joinpath('.cache', cache_dir)
    
        os.makedirs(cache_path, exist_ok=True)
    
        # obtain remote paths and remote timestamps 
        local_path_list = []
        remote_path_list = [self.itable.df['path'][n] for n in self.itable.selected_rows]
        remote_modified_list = [self.itable.df['modified'][n] for n in self.itable.selected_rows]
        remote_isdir_list = [self.itable.df['isdir'][n] for n in self.itable.selected_rows]
        
        n_files = len(remote_path_list)
       
        for i, [remote_path, remote_modified, remote_isdir] in enumerate(zip(remote_path_list, remote_modified_list, remote_isdir_list)): 
    
            # only download actual files 
            if not remote_isdir:   
                remote_directory = os.path.dirname(remote_path)
                local_directory = cache_path.joinpath(remote_directory) # I guess this will not yet work for Windows
                
                # create directory structure inside cache 
                os.makedirs(local_directory, exist_ok=True) 
            
                # get remote epoch time  
                remote_modified_epoch_time = remote_modified.timestamp()
            
                # construct corresponding local path 
                local_path = cache_path.joinpath(remote_path) 
                local_path_list.append(str(local_path))
                
            
                # check if local file exists and if modification times are similar 
                is_local = local_path.exists()  
            
                is_similar = False 
                local_modified_epoch_time = None 
                if is_local: 
                    local_modified_epoch_time = os.stat(local_path).st_mtime
                    if local_modified_epoch_time == remote_modified_epoch_time: 
                        is_similar = True 
                        
                # download from nextcloud 
                if not is_similar: 
                    print(f'[{i}/{n_files - 1}] Timestamps do no match: {remote_modified_epoch_time} vs {local_modified_epoch_time}', end='\r')
                    print(f'[{i}/{n_files - 1}] Downloading to: {local_path}                                                       ' , end='\r')
                      
                    # write to cache 
                    with open(local_path, 'bw') as fh: 
                        self.nc.files.download2stream(remote_path, fh) 
                        
                    # adjust last modified timestamp 
                    now = int(time.time())
                    os.utime(local_path, (now, remote_modified_epoch_time)) 
                    
        print(f"Ready with downloading {n_files} selected remote files to local cache: {local_path}                                                                      ")

        return local_path_list

    def download(self, remote_path, local_path, chunk_size='5Mb'):
        #TODO: refactor download to this function
        return

    def upload(self, remote_path, local_path, allow_edit=False, chunk_size='5Mb'):
        '''Uploads a local file to the remote nextcloud storage. Use allow_edit to be able to override an existing file.'''

        # check if user is overriding file information and if it's allowed
        if (self.check_exist(remote_path)) and (not allow_edit):
            print(f"There is already a file present at {remote_path}, allow overrides with allow_edit=True")
            return

        # get file size in bytes and make it human readable
        file_size_bytes = os.path.getsize(local_path)
        file_size = humanize.naturalsize(file_size_bytes, True)
        
        write = input(f"File size is {file_size} \nDo you want to continue the upload to {remote_path} [y/n]?.")

        if write != 'y':
            print("Upload aborted.")
            return
            
        print(f"Starting upload to {remote_path}.")
        
        with open(local_path, 'br') as fp:
            self.nc.files.upload_stream(remote_path, fp)

        # search for the newly created fsnode and add to the dataframe
        remote_dir, remote_file = os.path.split(remote_path)
        fsnode = self.nc.files.find(['eq', 'name', remote_file], remote_dir)[0]
        
        self.df.extend(_node_to_dataframe(fsnode))
        self._reload_itable()
        
        print(f"Upload finished.")
        
        
    
    def delete(self, remote_path):
        '''Delete a file in the remote Nextcloud storage.'''

        #TODO: Check if remote_path is an empty dir.
        
        write = input(f"Are you sure you want to delete {remote_path} [y/n]?")
        if write != 'y':
            print("Deletion aborted.")
            return
            
        # search for the newly created fsnode and delete from the dataframe
        remote_dir, remote_file = os.path.split(remote_path)
        fsnode = self.nc.files.find(['eq', 'name', remote_file], remote_dir)[0]
        
        self.nc.files.delete(remote_path)
    
        self.df = self.df.filter(pl.col("path") != fsnode.user_path)
        self._reload_itable()

        print("File removed.")

    def check_exist(self, remote_path):
        '''Checks the dataframe to see if the given path is already present.'''

        # checks the path column and looks for any similarities with the remote_path
        result = self.df['path'].is_in([remote_path]).any()
        
        return result

    def _reload_itable(self):
        '''Reloads the dataframe stored in the itable object'''
        self.itable.df = self.df





