
from flask import Flask,render_template,make_response,request, redirect, Response, session
import flask
#import transport
#from transport import providers
import cms
from cms import Plugin
import sys
import os
import json
import copy
import io
import base64
from jinja2 import Environment, BaseLoader
import typer
from typing_extensions import Annotated
from typing import Optional

import numpy as np
import pandas as pd
import uuid
import datetime
import requests
import io
from cms import disk, cloud, engine
import cms.sites

_app = Flask(__name__)
cli = typer.Typer()

# def _getHandler (app_id,resource=None) :
#     global _qcms
#     _id = _getId(app_id,resource)
    
#     return _qcms._apps[_id]
# def _getId(app_id,app_x):
#     if app_x not in [None,''] :
#         _uri =  '/'.join([app_id,app_x])
#         return _uri[:-1] if _uri.endswith('/') else _uri
#     return app_id
# def _setHandler (app_id,resource) :
#     session['app_id'] = _getId(app_id,resource)

@_app.route("/favicon.ico",defaults={'app':None})
@_app.route("/<app>/favicon.ico")
def favicon (app):
    global _qcms
    _site = _qcms.get (request)
    # return _read(app,None,_site.get('system.icon'))
    
    return redirect(_site.get('system.icon')) #_site.read(request,uri=_site.get('system.icon'))
@_app.route("/<_id>/robots.txt")
@_app.route("/robots.txt",defaults={'_id':None})
def robots_txt(_id):
    """
    This function will generate a robots expression for a variety of crawlers, the paths will be provided by
    menu options
    """
    global _qcms
    _site = _qcms.get (request)
    _routes = _site.get('system.routes')
    _context = _site.get('system.context')
    _info  = [f'''
    User-agent: *
    Allow: /{_context}
    ''']

    if _routes :
        for _key in _routes:
            _uri = '/'.join(["",_context,_key])
            _info.append(f'''
    User-agent: *
    Allow: {_uri}
                        ''')
    
    # return '\n'.join(_info),200,{'Content-Type':'plain/text'}
    return Response('\n'.join(_info), mimetype='text/plain')  
@_app.before_request
def format():
    global _qcms 
    isroute = _qcms.inspect.isroute(request)
    isfile  = _qcms.inspect.isfile(request)
    isapi   = _qcms.inspect.isapi(request)
    _map = ['/reload','/version','/dialog','/login','/logout']
    isendpoint = request.path.split('/')[1] in _map or request.path in _map
    # print (request.path.split('/'),' ** ',request.path)
    
    if not isendpoint and not isfile and  not isapi  and not request.path.endswith('/'):
        
        return redirect(request.path+'/')

@_app.route("/login",methods=['GET'],strict_slashes = False)
def login ():
    global _qcms
    # _secureMethod = request.headers.get('method','pam')
    
    
    #
    # are we using a default html document ?
    #
    _site = _qcms.get(None)
    
    # _page = f'login/{_site.secure.method()}.html'
    _uri = _site.get('layout.login')
    # _args= _site.get('system.source.secure')
    
    _args = {}
    _args['login'] = {'model':_site.secure.method()}
    _args['system'] = _site.get('system')
    _args['layout'] = _site.get('layout')
    #
    # add addition information associated with authentication
    #
    if 'source' in _args['system']:
        del _args['system']['source']
    
    if not _uri :
        _uri = f'login/{_site.secure.method()}.html'
        _html = render_template(_uri,**_args)
    else:
        #
        # let's place the html content here ...
        _html = _site.html(_uri,'login')
    #
    # we need to see what to _do about the session cookies
    #
    _args['html']  = _html
    return render_template('login/login.html',**_args)

@_app.route("/login",methods=['POST'],strict_slashes = False)
def authenticate () :
    #
    # we will forward this to the appropriate agent
    #
    global _qcms
    _site = _qcms.get()
    _page = session.get('redirect','') 
    
    _uri = f"{_site.get('system.context')}/{_page}".replace("//","/")
    if _uri.endswith('/') :
        _uri = _uri[:-1]
    
    if _site.secure.allow(request=request,uri=_uri) :
        return redirect(_uri)
    else:
        _good,_args = _site.secure.authenticate(request=request)
        _site.log(action='authenticate',input={'method':_site.secure.method(),'out':_good})
        if not _good :
            uri = request.path
        
        
        if _good :
            
            _key = list(_good.keys())[0]
            response = make_response(redirect(_uri))
            response.set_cookie(_key,_good[_key],**_args)
            response.headers['Location'] = _uri
            return response
        else:

            #
            # error 403 must be returned
            #        
            # response = make_response(redirect(_uri))
            # response.status = 403
            # uri = request.path
            pass
        return redirect(request.path),403
    
@_app.route("/logout",methods=["POST",'GET'],strict_slashes = False)
def logout():
    global _qcms
    _site   = _qcms.get()
    _id     = _site.secure._authContext #-- cookie key
    resp    = make_response(redirect("/"))
    resp.delete_cookie(_id)
    return resp

@_app.route('/dialog',defaults={'app':None},methods=['POST','GET'], strict_slashes = False)
@_app.route('/<path:app>/dialog', methods=['POST','GET'], strict_slashes = False)  
@_app.route('/<path:app>/dialog/', methods=['POST','GET'], strict_slashes = False)  
def _dialog(app):
    
    global _qcms
    # _site = _qcms.get(app)
    _site = _qcms.get(request)
    _uri = request.headers['uri']    
    _id = request.headers['dom']

    # _html = ''.join(["<div style='padding:1%'>",str( e.render(**_args)),'</div>'])
    # _args = _qcms.render(_uri,'html',app) #session.get('app_id','main'))
    _args = _site.render(id='html', request=request)
    _args['title'] = _id
    return render_template('dialog.html',**_args) #title=_id,html=_html)
@_app.route("/reload/<key>",strict_slashes = False)
def _reload(key) :
    global _qcms

    _site = _qcms.get(None)
    _systemKey = _site.get('system.source.key')
    if _systemKey == key and _systemKey:
        _site.reload()
        return "",200
    else:
        return "",403

@_app.route('/reload/',methods=['POST'],strict_slashes = False)
def reload():
    _key = request.headers['key'] if 'key' in request.headers else None
    return _reload(_key)

@_app.route("/",defaults={'file':None}, methods=['POST','GET','PUT'], strict_slashes = False)
@_app.route('/<path:file>',defaults={}, methods=['POST','GET','PUT'], strict_slashes = False)

# @_app.route('/<app>/files/<path:file>', defaults=[{'module':None}])
# @_app.route('/<app>/<module>/files/<path:file>')
def _read(file):
    global _qcms    
    #
    # apply security here (on the top level site only)
    # @TODO: upon failure, need to forward this to an error page ...
    #
    _site = _qcms.get(request)
    if _site.secure.allow(request=request) :
        return _qcms.delegate(request)
    else:
        session['redirect']  = request.path
        return redirect("/login")


# @_app.route("/api/<path:uri>", methods=['POST','GET','PUT'], defaults={'uri':None,'route':None})
# @_app.route("<path:route>/api/<path:uri>", methods=['POST','GET','PUT'])
# def _api (route,uri) :
# #     print ()
#     pass
# @_app.route("/api/<module>/<name>",defaults={'app':None,'key':None},methods=['GET','POST','DELETE','PUT'])
# @_app.route("/<app>/api/<module>/<name>",defaults={'key':None},methods=['GET','POST','DELETE','PUT'])
# @_app.route("/<app>/<key>/api/<module>/<name>",methods=['GET','POST','DELETE','PUT'])
# def _proxyCall(app,key,module,name):
#     global _qcms
    
#     _id = _getId(app,key)
    
#     _site = _qcms.get(request)
#     _uri = f'api/{module}/{name}'
#     _delegate = cms.delegate()
#     return _delegate(uri=_uri, handler=_site, request=request)

@_app.route('/version',strict_slashes = False)
def _version ():
    global _qcms
    _site = _qcms.get()
    global _config 
    return _site.get('system.version')
 

# @_app.route('/page',methods=['POST'],defaults={'app_id':None,'key':None})
# @_app.route('/<app_id>/page',methods=['POST'],defaults={'key':None})
# @_app.route('/<app_id>/<key>/page',methods=['POST'])
# def getPostedPage(app_id,key):
#     """
#     This function will return a page given a location parameter
#     """
#     global _qcms
#     _id = _getId(app_id,key)
#     _site = _qcms.get(_id)
#     _uri = request.headers['uri']
    
#     if 'dom' not in request.headers :
#         _id = _uri.split('/')[-1].split('.')[0]
#     else:
#         _id = request.headers['dom']

#     if 'read?uri=' in _uri or 'download?doc=' in _uri :
#         _uri = _uri.split('=')[1]
#     _args = _qcms.render(_uri,_id,_getId(app_id,key)) #session.get(app_id,'main'))
#     return _args[_id],200

@_app.route('/page',defaults={'app_id':None,'resource':None})
@_app.route('/<app_id>/page',defaults={'resource':None},methods=['GET'])
@_app.route('/<app_id>/<resource>/page',methods=['GET'])
def _cms_page (app_id,resource):
    global _qcms
    _uri = request.args['uri']
    _title = request.args['title'] if 'title' in request.args else ''
    _args = _qcms.render(_uri,_title,app_id)
    return _args[_title],200


@cli.command()
def start (
    path:Annotated[str,typer.Argument(help="path of the manifest file")]='qcms-manifest.json',
    port:int=typer.Option(default=None, help="port number to serve on (will override configuration)")

    ) :
    """
    This function is designed to start the application with its associated manifest (configuration) location
    :path   path to the  the manifest
    :shared run in shared mode i.e 
    """
    global _qcms

    if os.path.exists(path) and os.path.isfile(path):
        _args = {'path':path}
        _args['location'] = path
        _args['shared'] = True
        
        
        _qcms = cms.sites.QCMS(**_args)
        _args = _qcms.get().get('system.app')
        
        if port :
            _args['port'] = port
        _app.secret_key = str(uuid.uuid4())
        _app.config['PERMANENT_SESSION_LIFETIME'] = datetime.timedelta(hours=24)
        _app.run(**_args)
        _status = 'found'
    else:
        _status = 'not found'
    print(f'''
            manifest: {path}
            status  : {_status}
        ''')
@cli.command(name='help')
def _help() :
    pass
if __name__ == '__main__' :
    cli()

