import sys
sys.path.append("/var/www/io_vulcan/venv/lib/python3.11/site-packages/")
sys.path.append("/var/www/io_vulcan/py/")
from typing import Optional,Any,TypeAlias
from flask import Flask,jsonify,Response,session,request,make_response,g,send_from_directory
from werkzeug.exceptions import HTTPException
from werkzeug.debug import DebuggedApplication
from flask.logging import default_handler
from flask_cors import CORS
from functools import wraps
import logging
import Utils
from Utils import toInt as i,toList as l,toStr as s,f,getUserID,getIP,getUserName
import time
from Utils import Debug
from flask.json.provider import _default as _json_default
# import app as app_cls
import datetime
import types
import json

class RequestFormatter(logging.Formatter):
	def format(self, record):
		if request:
			record.uri = request.base_url
			record.path = request.full_path
		else:
			record.uri = None
			record.path = None
		return super().format(record)

app = Flask(__name__)
CORS(app=app)
app.secret_key = "31337"

###################################
## Debug Mode
app.debug = True
app.config['debug'] = True
app.config['PROPAGATE_EXCEPTIONS'] = True

def getQuery():
	query = None
	return {"method":request.method}
	Debug(title="Data Request",data=request.method)
	try:
		if request.method!='GET' and request.json:
			query = json.loads(request.body.read())
		else:
			query = dict(request.params) if request.params else {}
	except Exception as e:
		if request.body:
			data = request.body.read()
			if data:
				Debug(title="Trouble",data=e)
				Debug(data=data,title="Trouble getting query data")
		query = dict(request.params) if request.params else {}
	try:
		for up_file in request.files:
			query[up_file] = request.files.get(up_file).file.read()
	except:
		Debug(data=request.files,title="Trouble getting upload files")

	return query if query else {}

def json_default(o):
	match type(o):
		# case app_cls.Geo:
		# 	if o is not None:
		# 		return str(o)
		# 	else:
		# 		return ""
		case types.GeneratorType:
			return list(o)
		case datetime.datetime:
			return o.isoformat()
		case Utils.IODate:
			return o.full()
		case _:
			return _json_default(o)

app.json.default = json_default

# import time

# START_TIME = time.perf_counter()

###################################
## Setup Logging to file

file_log = logging.FileHandler('api.log',encoding='utf8')
formatter = RequestFormatter('', datefmt="%d/%m/%Y %I:%M:%S %p")
file_log.setFormatter(formatter)
wlog = logging.getLogger('werkzeug')
wlog.setLevel(logging.INFO)

wlog.addHandler(file_log)
# app.logger.setLevel(logging.INFO)
app.logger.addHandler(file_log)
# app.logger.basicConfig(level=logging.DEBUG,stream=sys.stderr)

###################################
## Set app to application for wsgi

io_response:TypeAlias =tuple[dict,int]

try:
	application = DebuggedApplication(app, pin_logging=True,console_path="/console")
	#application = app
except Exception as e:
	Utils.Debug("App Trouble",data=e)

@app.before_request
def before_request():
	g.start_time = time.time()

@app.after_request
def after_request(response):
	# response.headers["Access-Control-Allow-Origin"] = "*"
	# response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
	# response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"

	if hasattr(g, 'start_time'):
		et = time.time() - g.start_time
		print_request(et)
		response.headers["io-time"] = et
	return response

# application = app
###################################
## Routes

def Output(data:Optional[Any]=None,status:int = 1,message:str = "",error:Optional[str|Exception]=None,code=200) -> tuple[Any,int]:
	if not data or isinstance(data,bool):
		data = {}
	if error:
		Utils.Debug(title="Error",data=error)
		if data:
			return(data,code)
		else:
			if code != 200:
				return(Utils.errorStr(error),code)
			else:
				return(Utils.errorStr(error),500)

	if status == -3:
		app.logger.error(f'{message} - {status} - {code}')
		return ("",code)

	return (data,200)


class bcolors:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKCYAN = '\033[96m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'

def print_request(et):
	dt = Utils.IODate()
	ms = et * 1000
	app.logger.warning(f"[IO] {dt.full()} | {bcolors.OKBLUE} {ms}ms {bcolors.ENDC}| {bcolors.OKGREEN} {request.path} {bcolors.ENDC} |")

def auth(perms:list):
	def do_auth(func):
		@wraps(func)
		def wrapper(*args,**kwargs):
			check=""
			parms = Utils.parms()
			if parms.get("u") and parms.get("p"):
				login()
			user_groups = session.get("User",{}).get("Groups") or []
			for check in perms:
				if check in user_groups:
					return func(*args,**kwargs)

			return Output(data=None,status=-3,message=f"Not authorized for {check} {parms}",code=401)
		return wrapper
	return do_auth


# @app.route('/js/<path:filename>')
# def serve_js(filename):
#     return send_from_directory('../static/js', filename)
#
# @app.route('/css/<path:filename>')
# def serve_css(filename):
#     return send_from_directory('../static/css', filename)
#
#
# @app.route('/fa/<path:filename>')
# def serve_fa(filename):
#     return send_from_directory('../static/fa', filename)

@app.route("/json")
def test_json() -> dict:
	data = {
		"columns":[
			{"title":"Age","field":"age"},
			{"title":"First Name","field":"first_name"},
			{"title":"Last Name","field":"last_name"}
		],
		"rows":[
			{ "age": 40, "first_name": 'Bob', "last_name": 'Jones' }
		]
	}
	return data

def service(obj,method,parms) -> tuple[str,str]:
	err = ""
	output = ""
	try:
		data = getQuery()
		model = getattr(__import__(obj), method)(parms)
		output = getattr(model, method)(data)
		if not isinstance(output,str):
			output = Utils.toJSON(output)
	except Exception as e:
		err = str(e)
	return (output,err)


@app.route("/io/qr/<path:data>")
def qrcoder(data:str):
	from QR import qr
	dec_data = Utils.url_decode(data)
	output,err = qr().svg(dec_data)
	return Response(output, mimetype='image/svg+xml')


# @app.route("/io/<obj>/<method>/<data>")
# def director(obj:str,method:str,data:str):
# 	# output,err = qr().svg(data)
# 	output,err = service(obj,method,Utils.url_decode(s(data)))
# 	return Output(data=output,error=err)


@app.route("/demo")
def demo():
	return send_from_directory('../static','demo.html')

@app.route("/qr")
def qr():
	return send_from_directory('../static','qr.html')


@app.route("/")
def default():
	return send_from_directory('../static','index.html')


@app.route("/<path:uri>")
def io(uri:str):
	return send_from_directory('../static',uri)
	# return Output(data={"data":"Words"},status=1,message="Success")

@app.route("/admin/login",methods=["POST"])
def admlogin():
	from app import Users
	parms = Utils.parms()
	user = ""
	try:
		assert len(parms.get("u","")) > 0 and len(parms.get("p",""))>0, "Invalid user/pass"
		user = Users().auth(user=parms.get("u",""),password=parms.get("p",""))
		assert user, "No user"
		session["User"] = user
		return Output(data={},status=1,message="Login Successful")
	except Exception as e:
		Debug(title="Auth Fail",data=[parms])
		session["User"] = {}
		return Output(data="Login Failed",status=0,code=401,error=f"User or Password is invalid {Utils.toJSON(user)}")

@app.route("/login",methods=["POST"])
def login():
	from app import Users
	parms = Utils.parms()
	user = Users().get_userid(user_id=parms.get("auth",""))
	# Debug("Auth Check Result",data=user);
	if user:
		user["Groups"] = ["User"]
		session["User"] = user
		return Output(data={},status=1,message="Login Successful")
	else:
		Debug(title="Auth Fail",data=[parms])
		session["User"] = {}
		return Output(data="Login Failed",status=0,code=401,error=f"User or Password is invalid {Utils.toJSON(user)}")

@app.route("/logout",methods=["GET"])
def logout():
	data = {"User":{}}
	session["User"] = {}
	resp = make_response(Output(data={"User":session.get("User")},status=1,message="Logout Successful"))
	return resp

@app.errorhandler(Exception)
def server_error(err) -> io_response:
	app.logger.exception(err)
	print("Server Error")
	Utils.Debug(title="Trouble",data=err)
	return Output(data={"error":str(err)},status=0,message=f"exception {err}")

@app.errorhandler(404)
def page_not_found(error) -> io_response:
	return Output(data={"error":"404"},status=0,error=f"This page does not exist {request.path}",code=404)

@app.errorhandler(500)
def server_barf(error) -> io_response:
	Utils.Debug(title="Server Crash",data=error)
	return Output(data={"error":"Server Fail"},status=0,error=str(error),code=200)

@app.errorhandler(HTTPException)
def handle_exception(e) -> Response:
	import json
	response = e.get_response()
	response.data = json.dumps({
		"code": e.code,
		"name": e.name,
		"status":0,
		"description": e.description,
	})
	response.content_type = "application/json"
	Utils.Debug(title="HTTP ERROR",data=response.data)

	return response

