import sys
#sys.path.append("/var/www/mission/venv/lib/python3.11/site-packages/")

from typing import Optional,Any,TypeAlias
from app import Employee_Evaluations
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
from functools import wraps
import logging
import Utils
from Utils import toInt as i,toList as l,toStr as s,f
from Utils import Debug

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__)
app.secret_key = "31337"
###################################
## Debug Mode
app.debug = True
app.config['debug'] = True
app.config['PROPAGATE_EXCEPTIONS'] = True

###################################
## 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

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)
# application = app
###################################
## Routes

def getBusID() -> int:
	try:
		return session["User"]["Business_ID"] or -1
	except Exception as e:
		Debug(title="Trouble getting session user",data=e)
		Debug(title="Session Business",data=session["User"]["Business_ID"])
		return -1

def Output(data:Optional[Any]=None,status:int = 1,message:str = "",error:Optional[str]=None,code=200) -> tuple[dict,int]:
	if error:
		message = str(error)
		status = 0
	u = session.get("User") or {}
	return ({
		"data":data,
		"status":status,
		"user":{
			"User":u.get("User"),
			"Business_ID":u.get("Business_ID"),
			"Name":u.get("Name")
		},
		"message":str(message)
	},code)

def auth(perms:list):
	def do_auth(func):
		@wraps(func)
		def wrapper(*args,**kwargs):
			parms = Utils.parms()
			check = None
			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}",code=401)

		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("/")
def io() -> io_response:
	return Output(data={"Name":"Brent"},status=1,message="Success")

@app.route("/login",methods=["POST"])
def login():
	from app import Users
	parms = Utils.parms()
	user  = Users().auth(user=parms.get("u",""),password=parms.get("p","")) or {}

	if user:
		session["User"] = user
		return Output(status=1,message="Login Successful")
	else:
		Debug(title="Auth Fail",data=[parms])
		return Output(data={},status=0,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.route("/client/dashboard/")
@auth(['client'])
def client_dashboard() -> io_response:
	from app import Client_Dashboard
	client_db = Client_Dashboard()
	try:
		bus_id = getBusID()
		return Output(data=client_db.summary(bus_id=bus_id),error=client_db.error)
	except Exception as e:
		return Output(error=str(e))

@app.route("/client/bus_setup/",methods=["POST"])
@auth(['client'])
def bus_setup() -> io_response:
	from app import Business,Users,Global_Settings
	bus = Business()
	users = Users()
	try:
		user_id = session.get("User").get("ID")
		assert users.check_business(UserID=user_id) is False, "A business is already assigned to this user"
		parms = Utils.parms()
		bus_row = parms
		if "Image" in parms:
			f = parms.get("Image")
			del parms["Image"]
			if f:
				fname = f.filename
				if fname:
					f.save(f"/var/www/bsim/site/content/{fname}")
					parms["Logo"] = f"/content/{fname}"

		initial = Global_Settings().get_value("Initial_Capital")
		Debug(title="Initial",data=initial)
		bus_row["Loan"] = initial
		bus_row["Capital"] = initial
		bus_row["Units"] = 0
		bus_id = bus.add(bus_row)
		if bus_id>0:
			users.update(data={"Business_ID":bus_id,"ID":user_id})
			session["User"] = users.get_id(user_id) or {}
		else:
			Output(data={},error="Couldn't create business")
		return Output(data={},error=bus.error or users.error)
	except Exception as e:
		return Output(error=str(e))

@app.route("/client/bus_update/",methods=["POST"])
@auth(['client'])
def bus_update() -> io_response:
	from app import Business,Users,Global_Settings
	bus = Business()
	users = Users()
	try:
		user_id = session.get("User").get("ID")
		bus_id = getBusID()
		parms = Utils.parms()
		bus_row = {}
		if "Image" in parms:
			f = parms.get("Image")
			del parms["Image"]
			if f:
				fname = f.filename
				if fname:
					f.save(f"/var/www/bsim/site/content/{fname}")
					bus_row["Logo"] = f"/content/{fname}"
					bus_row["ID"] = bus_id
					Debug(title="Business Row",data=bus_row)
					if bus_row.get("ID") and bus_row.get("Logo"):
						status = bus.update(bus_row)

			else:
				return Output(data={},error="Logo Not Found")
		return Output(data={},error=bus.error)
	except Exception as e:
		return Output(error=str(e))


@app.route("/client/plan/",methods=["GET"])
@auth(['client'])
def client_plan_all() -> io_response:
	from app import Quarters
	bus_id = getBusID()
	return Output(Quarters().get_business(Business_ID=bus_id))

@app.route("/client/plan/<ID>",methods=["GET"])
@auth(['client'])
def client_plan_get(ID:int) -> io_response:
	from app import Quarters
	try:
		q = Quarters()
		quarter = q.get_id(ID=ID)
		assert quarter, "Quater not found"
		return Output(data=quarter,error=q.error)
	except Exception as e:
		return Output(error=str(e))

@app.route("/client/plan/<op>/",methods=["POST"])
@auth(['client'])
def client_plan_crud(op:str) -> io_response:
	from app import Quarters,Overhead
	try:
		status = 1
		quarters = Quarters()
		parms = Utils.parms()

		overhead = parms.get("overhead","")
		o_effect = f(Overhead().value(overhead)) / 100
		units = i(parms.get("units"))
		employees = i(parms.get("employees"))
		eff = 40 + (40 * o_effect)
		max_units = employees * eff

		Debug(title="Check max units",data={
			"Max":max_units,
			"Units":units,
			"eff":eff
		})
		if units > max_units:
			return Output(data={},status=0,error="Not enough employees to make {units} units")

		parms["Business_ID"] = session.get("User").get("Business_ID")
		if op == 'add':
			b_id = parms["Business_ID"]
			q_id = i(parms.get("Quarter_ID"))
			check = quarters.check_exists(Business_ID=b_id,Quarter_ID=q_id)
			Debug(title="Checking For Quarter",data=[b_id,q_id,check])
			if not check:
				quarters.add(data=parms)
				status = len(quarters.error)<=0
			else:
				return Output(data={},status=0,error = "Plan already exists for that quarter")
		elif op =='update':
			assert i(parms.get("ID"))>0, "Invalid ID"
			quarters.update(data=parms)
			status = len(quarters.error)<=0
		elif op == 'delete':
			assert i(parms.get("ID"))>0, "Invalid ID"
			status = quarters.delete({"ID":parms.get("ID")})

		return Output(data={},status=status,error=quarters.error)
	except Exception as e:
		return Output(data={},status=0,error=str(e))


@app.route("/client/reports/forecast",methods=["GET"])
@auth(['client'])
def client_reports_outlook() -> io_response:
	from app import Reports
	return Output(Reports().outlook())

@app.route("/client/roll/",methods=["GET"])
@auth(['client'])
def client_roll() -> io_response:
	from app import Roll
	bus_id = getBusID()
	data = {}
	data["students"] = Roll().get_period(bus_id=bus_id)
	return Output(data)

@app.route("/admin/business/")
@auth(['admin'])
def business() -> io_response:
	from app import Business
	return Output(data=Business().all())

@app.route("/admin/business/detail/")
@auth(['admin'])
def business_details() -> io_response:
	from app import Business
	return Output(data=Business().details())

@app.route("/admin/business/<ID>")
@auth(['admin'])
def business_get(ID:int) -> io_response:
	from app import Business
	return Output(Business().get(ID=ID))

@app.route("/admin/business/detail/<ID>")
@auth(['admin'])
def business_detail(ID:int) -> io_response:
	from app import Business
	try:
		return Output(Business().detail(ID=ID))
	except Exception as e:
		return Output(error=str(e))

@app.route("/admin/business/add/",methods=["POST"])
@auth(['admin'])
def business_add() -> io_response:
	from app import Business
	try:
		data = Utils.parms()
		bus = Business()
		status = bus.add(data)
		return Output(data=status,status=status>0,error=bus.error)
	except Exception as e:
		return Output(data={},status=0,error=str(e))

@app.route("/admin/business/update/",methods=["POST"])
@auth(['admin'])
def business_update() -> io_response:
	from app import Business
	try:
		data = Utils.parms()
		assert i(data.get("ID"))>0, "Invalid ID"
		bus = Business()
		status = bus.update(data)
		return Output(data=status,status=len(bus.error)<=0,error=bus.error)
	except Exception as e:
		return Output(data={},status=0,error=str(e))

@app.route("/admin/business/delete/",methods=["POST"])
@auth(['admin'])
def business_delete() -> io_response:
	from app import Business
	try:
		data = Utils.parms()
		assert i(data.get("ID"))>0, "Invalid ID"
		bus = Business()
		status = bus.delete({"ID":data.get("ID")})
		return Output(data=status,status=status>0,error=bus.error)
	except Exception as e:
		return Output(data={},status=0,error=str(e))

@app.route("/client/quarter/select/")
@auth(['admin'])
def quarter_select() -> io_response:
	from app import Quarter_Settings	
	q_settings = Quarter_Settings()
	return Output(data=q_settings.options(),error=q_settings.error)

@app.route("/admin/options/")
@auth(['admin'])
def admin_options() -> io_response:
	from app import Admin
	return Output(data=Admin().options(),error=None)

@app.route("/admin/globals/")
@auth(['admin'])
def admin_globals() -> io_response:
	from app import Global_Settings
	global_settings = Global_Settings()
	return Output(data=global_settings.all(),error=global_settings.error)

@app.route("/admin/globals/<ID>",methods=["GET"])
@auth(['admin'])
def admin_globals_get(ID:int) -> io_response:
	from app import Global_Settings
	return Output(Global_Settings().get(ID=ID))

@app.route("/admin/globals/<op>/",methods=["POST"])
@auth(['admin'])
def admin_globals_crud(op:str) -> io_response:
	from app import Global_Settings
	try:
		status = 1
		global_settings = Global_Settings()
		parms = Utils.parms()
		if op == 'add':
			status = global_settings.add(data=parms)
		elif op =='update':
			assert i(parms.get("ID"))>0, "Invalid ID"
			global_settings.update(data=parms)
			status = len(global_settings.error)<=0
		elif op == 'delete':
			assert i(parms.get("ID"))>0, "Invalid ID"
			status = global_settings.delete({"ID":parms.get("ID")})
		return Output(data={},status=status,error=global_settings.error)
	except Exception as e:
		return Output(data={},status=0,error=str(e))

@app.route("/admin/quarter/settings/")
@auth(['admin'])
def quarter_settings() -> io_response:
	from app import Quarter_Settings
	quarter_settings = Quarter_Settings()
	return Output(data=quarter_settings.all(),error=quarter_settings.error)

@app.route("/admin/quarter/settings/<ID>",methods=["GET"])
@auth(['admin'])
def quarter_settings_get(ID:int) -> io_response:
	from app import Quarter_Settings
	return Output(Quarter_Settings().get(ID=ID))

@app.route("/admin/quarter/settings/<op>/",methods=["POST"])
@auth(['admin'])
def quarter_settings_crud(op:str) -> io_response:
	from app import Quarter_Settings
	try:
		status = 1
		quarter_settings = Quarter_Settings()
		parms = Utils.parms()
		parms["Active"] = 1 if parms.get("Active") == "on" else 0
		if op == 'add':
			status = quarter_settings.add(data=parms)
		elif op =='update':
			assert i(parms.get("ID"))>0, "Invalid ID"
			quarter_settings.update(data=parms)
			status = len(quarter_settings.error)<=0
		elif op == 'delete':
			assert i(parms.get("ID"))>0, "Invalid ID"
			status = quarter_settings.delete({"ID":parms.get("ID")})

		status = int(status>0)
		return Output(data={},status=status,error=quarter_settings.error)
	except Exception as e:
		return Output(data={},status=0,error=str(e))

@app.route("/admin/quarters/",methods=["GET"])
@auth(['admin'])
def quarters_all() -> io_response:
	from app import Quarters
	return Output(Quarters().all(show_calc=True))

@app.route("/admin/quarters/<ID>",methods=["GET"])
@auth(['admin'])
def quarter_get(ID:int) -> io_response:
	from app import Quarters
	try:
		q = Quarters()
		quarter = q.get_id(ID=ID)
		assert quarter, "Quater not found"
		return Output(data=quarter,error=q.error)
	except Exception as e:
		return Output(error=str(e))

@app.route("/admin/quarters/reset/",methods=["GET"])
@auth(['admin'])
def quarter_reset() -> io_response:
	from app import Quarters,Quarter_Settings,Business
	qs = Quarter_Settings()
	q = Quarters()
	b = Business()
	err = qs.error + q.error + b.error
	status = int(qs.deactivate() and q.reset() and b.reset())
	return Output(data={},status=int(status>0),error=err)

@app.route("/admin/quarters/<op>/",methods=["POST"])
@auth(['admin'])
def quarter_crud(op:str) -> io_response:
	from app import Quarters
	try:
		status = 1
		quarters = Quarters()
		parms = Utils.parms()
		if op == 'add':
			status = quarters.add(data=parms)
		elif op =='update':
			assert i(parms.get("ID"))>0, "Invalid ID"
			quarters.update(data=parms)
			status = len(quarters.error)<=0
		elif op == 'delete':
			assert i(parms.get("ID"))>0, "Invalid ID"
			status = quarters.delete({"ID":parms.get("ID")})
		status = int(status>0)
		return Output(data={},status=status,error=quarters.error)
	except Exception as e:
		return Output(data={},status=0,error=str(e))

@app.route("/admin/users/",methods=["GET"])
@auth(['admin'])
def users_all() -> io_response:
	from app import Users
	return Output(Users().all())

@app.route("/admin/users/<ID>",methods=["GET"])
@auth(['admin'])
def users_get(ID:int) -> io_response:
	from app import Users
	try:
		users = Users()
		user_data = users.get_id(ID=ID)
		assert user_data, "User not found"
		return Output(data=user_data,error=users.error)
	except Exception as e:
		return Output(error=str(e))

@app.route("/admin/users/<op>/",methods=["POST"])
@auth(["admin"])
def users_crud(op:str) -> io_response:
	import Utils
	from app import Users
	try:
		status = 1
		users = Users()
		parms = Utils.parms()
		if not parms.get("Business_ID"):
			parms["Business_ID"] = None

		if not parms.get("Groups"):
			parms["Groups"] = "client"
		parms["Groups"] = Utils.toJSON(Utils.split(parms.get("Groups",""),","))
		if op == 'add':
			users.add(data=parms)
			status = 1 if len(users.error)<=0 else 0
		elif op =='update':
			assert i(parms.get("ID"))>0, "Invalid ID"
			users.update(data=parms)
			status = 1 if len(users.error)<=0 else 0
		elif op == 'delete':
			assert i(parms.get("ID"))>0, "Invalid ID"
			status = users.delete({"ID":parms.get("ID")})
		return Output(data={},status=status,error=users.error)
	except Exception as e:
		return Output(data={},status=0,error=str(e))

@app.route("/admin/evaluations/",methods=["GET"])
@auth(['admin'])
def evaluations_all() -> io_response:
	from app import Employee_Evaluations
	return Output(Employee_Evaluations().all())

@app.route("/admin/evaluations/<ID>",methods=["GET"])
@auth(['admin'])
def evaluations_get(ID:int) -> io_response:
	from app import Employee_Evaluations
	try:
		users = Employee_Evaluations()
		user_data = users.get_id(ID=ID)
		assert user_data, "User not found"
		return Output(data=user_data,error=users.error)
	except Exception as e:
		return Output(error=str(e))

@app.route("/admin/evaluations/<op>/",methods=["POST"])
@auth(["admin"])
def evaluations_crud(op:str) -> io_response:
	import Utils
	from app import Employee_Evaluations
	try:
		status = 1
		evals = Employee_Evaluations()
		parms = Utils.parms()
		if not parms.get("Business_ID"):
			parms["Business_ID"] = None

		if op == 'add':
			evals.add(data=parms)
			status = 1 if len(evals.error)<=0 else 0
		elif op =='update':
			assert i(parms.get("ID"))>0, "Invalid ID"
			evals.update(data=parms)
			status = 1 if len(evals.error)<=0 else 0
		elif op == 'delete':
			assert i(parms.get("ID"))>0, "Invalid ID"
			status = evals.delete({"ID":parms.get("ID")})
		return Output(data={},status=status,error=evals.error)
	except Exception as e:
		return Output(data={},status=0,error=str(e))

@app.route("/admin/sim/",methods=["GET","POST"])
@auth(['admin'])
def sim_quarter() -> io_response:
	from app import Sim
	parms = Utils.parms()
	sim = Sim()
	test = True if parms.get("test") else False
	return Output(data=sim.quarters(test=test),error=sim.error)

@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={},status=0,message=f"exception {err}")

@app.errorhandler(404)
def page_not_found(error) -> io_response:
	return Output(data={},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={},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
