import typer
from typing_extensions import Annotated
from typing import Optional
from typing import Tuple
import os
import json
import plugin_ix as px
from enum import Enum
import pandas as pd
from rich.table import Table
from rich import print
import cms


FAILED = '[ [red] \u2717 [/red] ]'
PASSED = '[ [green] \u2713 [/green] ]'

"""
Handling cli interface for plugins, performing add list and remove
"""

cli = typer.Typer()
def to_Table(df: pd.DataFrame):
    """Displays a Pandas DataFrame as a rich table."""
    table = Table(show_header=True, header_style="bold magenta")
    
    for col in df.columns:
        table.add_column(col)
    
    for _, row in df.iterrows():
        table.add_row(*row.astype(str).tolist())
    
    # console.print(table)
    return table


# def cms.get_manifest (manifest):
#     if not manifest.endswith('json') and os.path.isdir(manifest):
#         manifest = manifest if not manifest.endswith(os.sep) else os.sep.join(manifest.split(os.sep)[:-1])
#         path =  os.sep.join([manifest,'qcms-manifest.json'])
#     else:
#         path =  manifest
#     return path

# def get_config(path,key=None):
    
#     f = open(path)
#     _config = json.loads(f.read())
#     f.close()
#     return _config[key] if key and key in _config else _config

def format(file,_content):
    return [{'file':file.replace('.py',''),'uri':f'api/{file.replace(".py","")}/{_name}','endpoint':_name} for _name in _content]

def get (manifest:Annotated[str,typer.Argument(help="project folder or manifest file")],):
    """
    List the plugins in the from a project folder (from the manifest)    
    """
    path    = cms.engine.config.get_manifest(manifest=manifest) #
    _config = cms.engine.config.get(path) #get_config(path)
    
    
    _root  = _config['layout']['root']
    #
    # remove the manifest file to pull the plugins folder
    folder  = os.sep.join([path.replace('qcms-manifest.json','')[:-1],f'{_root}/_plugins'])
    loader = px.Loader ()
    
    _api = []
    for _name in os.listdir(folder):
        
        _file =   os.sep.join([folder,_name])
        try:
            loader.load(file=_file)
            _api+= format(_name,loader.names())
            # _ondisk[_name] = loader.names()
            
                
        except Exception as e:
            #
            # If the file has an error, it will be skipped
            print (e)
            break
    #
    # at this point we know what we have on disk, we should be able to make a report/dashboard of sorts
        pass
        
    # On disk
    # files = os.listdir(folder)
    #
    _online = []
    _plugins = _config['plugins']
    for _name in _plugins :
        _online += format(_name, _plugins[_name])
    return _api,_online

class StatusFilter(str,Enum):
    online="online"
    offline="offline"
    

@cli.command(name="status")
def status(manifest:Annotated[str,typer.Argument(help="project folder or manifest file")],
           filter : StatusFilter = None
           ):
    """
    This function will provide the status of all available plugins
    """
    _avail,_depl = get(manifest)
    _avail = pd.DataFrame(_avail)# available on disk (not deployed)
    _depl = pd.DataFrame(_depl) # deployed
    files = _avail.file.unique().tolist()+ _depl.file.unique().tolist()
    _df = pd.DataFrame()
    for _name in files :
        _xe = _avail[_avail.file == _name].endpoint.tolist()  
        _ye = _depl[_depl.file == _name].endpoint.tolist()
        _online = list(set(_xe) & set(_ye))
        _offline = list(set(_xe) - set(_ye))
        _data = pd.DataFrame()
        
        
        _data['api']    = _offline + _online
        _data['file']       = _name
        _data['status'] = (['[red]offline[/red]']* len(_offline)) + (['[green]online[/green]']* len(_online))
        # _data['deployed'] = _online
        _data = _data[['file','api','status']]
        _df = pd.concat([_df, _data])
        # _df.append({'file':_name,'deployed':_online,'offline':_offline})
    _df['uri'] = _df.apply(lambda row: f'api/{row.file}/{row.api}',axis=1)
    _df = _df[['file','uri','api','status']]
    if filter :
        print (f"status in ('{filter.value}')")
        _df = _df[_df.status.str.contains(filter.value, case=False, regex=True)]
        # _df = _df.query(f" '{filter.value}' in status")
    print (to_Table(_df))
    pass
           
@cli.command(name="register")
def add (manifest:Annotated[str,typer.Argument(help="project folder or manifest file")],
         pointer:Annotated[str,typer.Argument(help="file/function or file.function with no file extension. e.g: demo/info")]
         ):
    """
    This function will add a plugin function to the site's configuration file making it available as an API
    """
    #
    # Let's make sure we are adding to the configuration something that actually exists
    #
    _file,_name = pointer.split('/')
    path    = cms.engine.config.get_manifest(manifest=manifest) #
    _config = cms.engine.config.get(path) #get_config(path)    
    _root  = _config['layout']['root']

    _folder  = os.sep.join([path.replace('qcms-manifest.json','')[:-1],f'{_root}/_plugins'])
    if f'{_file}.py' in os.listdir(_folder) :
        loader = px.Loader ()
        loader.load(file=f'{_folder}{os.sep}{_file}.py')
        if loader.has(_name) :
            #
            # adding to plugins
            _plugins = _config['plugins']
            if _file not in _plugins :
                _plugins[_file] = []
            if _name not in _plugins[_file] :
                _plugins[_file].append (_name)
                _config['plugins'] = _plugins
                cms.engine.config.write(config=_config,path=path)
                _msg = f"{PASSED} [bold]{pointer}[/bold] successfully added to {manifest}\nAPI endpoint [bold]api/{_file}/{_name}[/bold]"
            else:
                _msg = f"{FAILED} [bold]{pointer}[/bold] [red]already exists[/red] in  configuration"
        else:
            #
            # failure at this point, asking for a function in a file that doesn't exist
            _msg = f"[bold]{pointer}[/bold] [red]{_name} missing[/red] in {_folder}{os.sep}{_file}.py"
            pass
    else:
        #
        # throw/raise an exception 
        _msg = f"{FAILED} [bold]{pointer}[/bold] [red]NOT found[/red] in {_folder}"

    print (_msg)
    pass
@cli.command(name="unregister")
def remove(manifest:Annotated[str,typer.Argument(help="project folder or manifest file")],
         pointer:Annotated[str,typer.Argument(help="file/function or file.function with no file extension. e.g: demo/info")]
         ):
    """
    This function will remove an api from the configuration of a project and won't be available via http/https
    """
    _file,_name = pointer.split('/')
    path    = cms.engine.config.get_manifest(manifest=manifest) #
    _config = cms.engine.config.get(path) #get_config(path)    
    _root  = _config['layout']['root']
    _plugins = _config.get('plugins',{})
    if _file in _plugins :
        _plugins[_file] = [_fname for _fname in _plugins[_file] if _fname != _name]
        _config['plugins'] = _plugins
        _msg = f"{PASSED} [bold]{pointer}[/bold] was [green]successfully[/green] removed from {path}"
    else:
        _msg = f"{FAILED} [bold]{pointer}[/bold] was [red]NOT found[/red] in {path}"
    print (_msg)
    cms.engine.config.write(_config,path)
    #
    #
# def post(config,path):
#     f = open(path,'w')
#     f.write(json.dumps(config,indent=4))
#     f.close()