import sys
sys.path.append("venv/lib/python3.11/site-packages/")
from typing import Optional,Any,Generator
from flask import Flask,jsonify,Response,session,request,make_response
from werkzeug.exceptions import HTTPException
from werkzeug.debug import DebuggedApplication
from flask.logging import default_handler
import logging
import Utils
from Utils import toInt as i,toList as l,toStr as s
from Utils import Debug,IODate
from flask.json.provider import DefaultJSONProvider
from datetime import datetime, timedelta,date

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)

class IOJSONProvider(DefaultJSONProvider):
	def __init__(self, app):
		super().__init__(app)

	def default(self, obj):
		try:
			if isinstance(obj, (datetime,date,IODate)):
				return str(obj)
			elif isinstance(obj, timedelta):
				if obj.seconds > 0:
					if obj.days:
						return "{} days {} seconds".format(obj.days,obj.seconds)
					else:
						return "{} seconds".format(obj.seconds)
				else:
					return int(obj.days)
		except Exception as e:
			return super().default(obj)

		return super().default(obj)

app = Flask(__name__)
app.secret_key = Utils.config_value("flask","secret_key")
###################################
## Debug Mode
app.debug = True
app.config['debug'] = True
app.config['PROPAGATE_EXCEPTIONS'] = True
app.json = IOJSONProvider(app)
###################################
## 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.DEBUG)
wlog.addHandler(file_log)
app.logger.setLevel(logging.DEBUG)
app.logger.addHandler(file_log)
logging.basicConfig(level=logging.DEBUG,stream=sys.stderr)

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

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

def Output(data:Optional[Any],status:int = 1,message:str = "",error:Optional[str]=None,code=200) -> dict:
	if error:
		message = str(error)
		status = 0

	if data and isinstance(data,Generator):
		data = list(data)

	return ({
		"data":data,
		"status":status,
		"message":str(message)
	})

def auth(perms:list):
	def do_auth(func):
		def wrapper(*args,**kwargs):
			Debug(title="Checking Perms",data=[perms])
			for check in perms:
				if not check in check_data:
					return Output(data={},status=-3,message=f"Not authorized for {check}")
			return func(*args,**kwargs)
		return wrapper
	return do_auth

@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": 'Dickerson', "last_name": 'Macdonald' },
				{ "age": 40, "first_name": 'Dickerson', "last_name": 'Macdonald' },
				{ "age": 40, "first_name": 'Dickerson', "last_name": 'Macdonald' },
				{ "age": 40, "first_name": 'Dickerson', "last_name": 'Macdonald' },
				{ "age": 40, "first_name": 'Dickerson', "last_name": 'Macdonald' }
			]
	}
	return data

@app.route("/refresh")
def refresh() -> dict:
	from pathlib import Path
	status = Path('Service.py').touch()
	o = Output(data={"response":status},status=1,message="Success")
	return o

@app.route("/")
def io() -> dict:
	o = Output(data={"Name":"Brent"},status=1,message="Success")
	return o

@app.route("/status")
def status() -> dict:
    return Output(data={"Status":1},status=1,message="Success")

@app.route("/login",methods=["POST"])
def login():
	from app import Users
	import urllib.parse
	parms = Utils.parms()
	Debug(title="Parms",data=parms)
	user  = Users().auth(user=parms.get("u",""),password=parms.get("p","")) or {}
	if user:
		data = {"User":user}
		session["User"] = user
		resp = make_response(Output(data=data,status=1,message="Login Successful"))
		resp.set_cookie("app",urllib.parse.quote(Utils.toJSON(data)))
		return resp
	else:
		return Output(data={},status=0,error="User or Password was invalid")	

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

@app.route("/dashboard/")
def dashboard() -> dict:
	from app import Dashboard
	return Output(Dashboard().summary())

###################################
## Clients
@app.route("/clients/")
def clients() -> dict:
	from app import Clients
	return Output(list(Clients().all()))

@app.route("/clients/search/<name>")
def clients_search(name) -> dict:
	from app import Clients
	return Output(data=list(Clients().all(name=name)))

@app.route("/clients/<id>")
def clients_get(id:int) -> dict:
	from app import Clients
	return Output(Clients().get(id))

@app.route("/clients/select/")
def clients_select() -> dict:
	from app import Clients
	parms = Utils.parms()
	return Output(data={"options":Clients().names(),"selected": parms.get("EventID")})

@app.route("/clients/add/",methods=["POST"])
def clients_add() -> dict:
	from app import Clients
	parms = Utils.parms()
	parms["Created"] = Utils.IODate().full()
	parms["Trespass"] = parms.get("Trespass") or {}
	try:
		assert len(parms.get("Name",""))>0, "No name"
		if "Image" in parms:
			f = parms.get("Image")
			del parms["Image"]
			if f:
				fname = f.filename
				if fname:
					f.save(f"/var/www/mission/site/content/{fname}")
					parms["Photo"] = f"/content/{fname}"
		Clients().add(data=parms)	
	except Exception as e:
		return Output(data=str(e),status=0)
	return Output(data={},status=1)

@app.route("/clients/update/",methods=["POST"])
def adm_events_update() -> dict:
	from app import Clients
	parms = Utils.parms()
	try:
		f = parms.get("Image")
		if f:
			assert f is not None, "No File"
			fname = f.filename
			f.save(f"/var/www/mission/site/content/{fname}")
			del parms["Image"]
			parms["Photo"] = f"/content/{fname}"
		Clients().update(data=parms)
	except Exception as e:
		Utils.Debug(title="Trouble Updating",data=e)
	return Output(data={},status=1)

@app.route("/clients/delete/",methods=["POST"])
def adm_events_delete_row() -> dict:
	from app import Clients
	event_id = i(Utils.parms().get("ID"))
	try:
		assert event_id >0, "Invalid ID"
		return Output(Clients().delete(event_id))
	except Exception as e:
		return Output(data={},status=0,error=str(e))

@app.route("/clients/delete/<event_id>")
def adm_events_delete(event_id:int) -> dict:
	from app import Clients
	return Output(Clients().delete(int(event_id)))

@app.route("/clients/note/<op>/",methods=["POST"])
def notes_crud(op:str) -> dict:
	from app import Notes,Clients
	data = {}
	try:
		status = 1
		notes = Notes()
		parms = Utils.parms()
		assert parms.get("Client_ID") is not None, "Missing Client ID"
		if not parms.get("Trespass"):
			parms["Trespass"] = None

		if op == 'add':
			status = notes.add(data=parms)
		elif op =='update':
			assert i(parms.get("ID"))>0, "Invalid ID"
			notes.update(data=parms)
			status = len(notes.error)<=0
		elif op == 'delete':
			assert i(parms.get("ID"))>0, "Invalid ID"
			status = notes.delete({"ID":parms.get("ID")})

		data = Clients().get(client_id = i(parms.get("Client_ID")))
		return Output(data=data,status=status>0,error=notes.error)
	except Exception as e:
		return Output(data=data,status=0,error=str(e))

@app.route("/meals/")
def meals_all() -> dict:
	from app import Meals
	return Output(list(Meals().all()))

@app.route("/meals/<op>/",methods=["POST"])
def meals_crud(op:str) -> dict:
	from app import Meals,Clients
	data = {}
	try:
		status = 1
		meals = Meals()
		parms = Utils.parms()
		assert parms.get("Client_ID") is not None, "Missing Client ID"

		if op == 'add':
			status = meals.add(data=parms)
		elif op =='update':
			assert i(parms.get("ID"))>0, "Invalid ID"
			meals.update(data=parms)
			status = len(meals.error)<=0
		elif op == 'delete':
			assert i(parms.get("ID"))>0, "Invalid ID"
			status = meals.delete({"ID":parms.get("ID")})

		data = Clients().get(client_id = i(parms.get("Client_ID")))
		return Output(data=data,status=status>0,error=meals.error)
	except Exception as e:
		return Output(data=data,status=0,error=str(e))

@app.route("/showers/")
def showers_all() -> dict:
	from app import Showers
	return Output(Showers().all())

@app.route("/showers/<op>/",methods=["POST"])
def showers_crud(op:str) -> dict:
	from app import Showers,Clients
	data = {}
	try:
		status = 1
		showers = Showers()
		parms = Utils.parms()
		assert parms.get("Client_ID") is not None, "Missing Client ID"
		if op == 'add':
			status = showers.add(data=parms)
		elif op =='update':
			assert i(parms.get("ID"))>0, "Missing ID"
			showers.update(data=parms)
			status = len(showers.error)<=0
		elif op == 'delete':
			assert i(parms.get("ID"))>0, "Invalid ID"
			status = showers.delete({"ID":parms.get("ID")})

		data = Clients().get(client_id = i(parms.get("Client_ID")))
		return Output(data=data,status=status>0,error=showers.error)
	except Exception as e:
		return Output(data=data,status=0,error=str(e))

@app.route("/food/")
def food_all() -> dict:
	from app import Food
	return Output(list(Food().all()))

@app.route("/food/<op>/",methods=["POST"])
def food_crud(op:str) -> dict:
	from app import Food,Clients
	data = {}
	try:
		status = 1
		food = Food()
		parms = Utils.parms()
		assert parms.get("Client_ID") is not None, "Missing Client ID"

		if op == 'add':
			status = food.add(data=parms)
		elif op =='update':
			assert i(parms.get("ID"))>0, "Invalid ID"
			food.update(data=parms)
			status = len(food.error)<=0
		elif op == 'delete':
			assert i(parms.get("ID"))>0, "Invalid ID"
			status = food.delete({"ID":parms.get("ID")})

		data = Clients().get(client_id = i(parms.get("Client_ID")))
		return Output(data=data,status=status>0,error=food.error)
	except Exception as e:
		return Output(data=data,status=0,error=str(e))

@app.route("/clothing/")
def clothing_all() -> dict:
	from app import Clothing
	return Output(list(Clothing().all()))

@app.route("/clothing/<op>/",methods=["POST"])
def clothing_crud(op:str) -> dict:
	from app import Clothing,Clients
	data = {}
	try:
		status = 1
		clothing = Clothing()
		parms = Utils.parms()
		assert parms.get("Client_ID") is not None, "Missing Client ID"

		if op == 'add':
			status = clothing.add(data=parms)
		elif op =='update':
			assert i(parms.get("ID"))>0, "Invalid ID"
			clothing.update(data=parms)
			status = len(clothing.error)<=0
		elif op == 'delete':
			assert i(parms.get("ID"))>0, "Invalid ID"
			status = clothing.delete({"ID":parms.get("ID")})

		data = Clients().get(client_id = i(parms.get("Client_ID")))
		return Output(data=data,status=status>0,error=clothing.error)
	except Exception as e:
		return Output(data=data,status=0,error=str(e))

@app.route("/referral/")
def referral_all() -> dict:
	from app import Referrals
	return Output(list(Referrals().all()))

@app.route("/referral/<op>/",methods=["POST"])
def referral_crud(op:str) -> dict:
	from app import Referrals,Clients
	data = {}
	try:
		status = 1
		referral = Referrals()
		parms = Utils.parms()
		assert parms.get("Client_ID") is not None, "Missing Client ID"

		if op == 'add':
			status = referral.add(data=parms)
		elif op =='update':
			assert i(parms.get("ID"))>0, "Invalid ID"
			referral.update(data=parms)
			status = len(referral.error)<=0
		elif op == 'delete':
			assert i(parms.get("ID"))>0, "Invalid ID"
			status = referral.delete({"ID":parms.get("ID")})

		data = Clients().get(client_id = i(parms.get("Client_ID")))
		return Output(data=data,status=status>0,error=referral.error)
	except Exception as e:
		return Output(data=data,status=0,error=str(e))

@app.route("/surplus/")
def surplus_all() -> dict:
	from app import Surplus
	return Output(list(Surplus().all()))

@app.route("/surplus/<op>/",methods=["POST"])
def surplus_crud(op:str) -> dict:
	from app import Surplus,Clients
	data = {}
	try:
		status = 1
		surplus = Surplus()
		parms = Utils.parms()
		assert parms.get("Client_ID") is not None, "Missing Client ID"

		if op == 'add':
			status = surplus.add(data=parms)
		elif op =='update':
			assert i(parms.get("ID"))>0, "Invalid ID"
			surplus.update(data=parms)
			status = len(surplus.error)<=0
		elif op == 'delete':
			assert i(parms.get("ID"))>0, "Invalid ID"
			status = surplus.delete({"ID":parms.get("ID")})

		data = Clients().get(client_id = i(parms.get("Client_ID")))
		return Output(data=data,status=status>0,error=surplus.error)
	except Exception as e:
		return Output(data=data,status=0,error=str(e))

@app.route("/tables/",methods=["GET"])
def tables_all() -> dict:
	try:
		from app import Clothing,Food,Showers,Meals,Clients,Referrals,Surplus
		data = {}
		data["Clothing"] = list(Clothing().all())
		data["Meals"] = list(Meals().all())
		data["Food"] = list(Food().all())
		data["Showers"] = list(Showers().all())
		data["Clients"] = list(Clients().all())
		data["Surplus"] = list(Surplus().all())
		data["Referrals"] = list(Referrals().all())
		return Output(data=data,status=1)
	except Exception as e:
		Utils.Debug(title="Trouble Creating Table",data=e)
		return Output(data=e,status=0)

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

@app.errorhandler(404)
def page_not_found(error) -> tuple:
	return 'This page does not exist', 404

@app.errorhandler(500)
def server_barf(error) -> tuple:
	Utils.Debug(title="Server Crash",data=error)
	return Output(data={},status=0,error=str(error)),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
