from typing import Optional,Any,TypeAlias,Self
from functools import cached_property
from typing import Optional,Any,Generator
from collections import defaultdict
from flask import session
import Utils
from Utils import Debug,IODate,m,i,s,f,getJSON
from db import Model,row
from decimal import Decimal
import math
import random
Rows:TypeAlias = Generator[dict,list,Any]

OVERHEAD = [
	{"title":"Run down building in a bad neighborhood","detail":"No heat but there's plenty of holes in the wall so build in air conditioning.  Your employees are not going to like this but it's cheap. - <strong>$5,000 / Quarter</strong>","value":"Low","effect":-20,"cost":5000},
	{"title":"An old house on the edge of town","detail":"Structurally sound building and it even has a kitchen.  It's awfully cramped but could be worse - <strong>$10,000 / Quarter</strong>","value":"Medium","effect":0,"cost":10000},
	{"title":"Lease a brand new building","detail":"Plenty of space and state of the art facilities.  Mass transit right to the front door and a built in gym.  Employees will be beating your door down to get a job here. - <strong>$20,000 / Quarter</strong>","value":"High","effect":20,"cost":20000}
]

DILEMMAS = {
	"Manager":{
		"description":"You need a new plant manager for your factory.  Choose from the following top candidates",
		"options":{
			"Expecting Mom - Age:27 - Very talented women with great credentials and job experience. She just found out she is expecting so she will need maternity leave in the near future":{
				"cost":0,
				"pr_effect":0
			},
			"The Pro - Age 57 - Top of his class at the Wharton Business School.  He has short stints at a ton of great companies but they all love him":{
				"cost":0,
				"pr_effect":0
			},
			"Home Grown - Age 26 - Started at the company straight out of high school.  Steady, on time, very dependable but with no management experience.":{
				"cost":0,
				"pr_effect":0,
			}
		}
	},
	"Ethics":{
		"description":"Do you feel your business reflects your ethical beliefs?  Would you have made different decisions if the employees were your family / friends?",
		"options":{
			"Yes, I would love to work at this company!":{
				"cost":0,
				"pr_effect":0
			},
			"Middle of the road.  We tried to do the right thing for our customers and shareholders":{
				"cost":0,
				"pr_effect":0
			},
			"No, we wanted to win so we were ruthless!":{
				"cost":0,
				"pr_effect":0
			}
		}
	},
	"Auction":{
		"description":"<strong>Marketing</strong><br>You need donations for a silent auction but don’t shop downtown or patronize any local stores.",
		"options":{
			'Write it in to your budget':{
				"cost":5000,
				"pr_effect":10,
			},
			'Give your employees a stipend to spend locally':{
				"cost":2500,
				"pr_effect":0,
			},
			'Continue to shop from larger brands saving money':{
				"cost":0,
				"pr_effect":-10
			}
		}
	},
	"Infrastructure":{
		"description":"Your company plans to build a new distribution center that promises jobs but would displace a low-income neighborhood.",
		"options":{
			'Do you proceed for growth':{
				"cost":5000,
				"pr_effect":0,
			},
			'Choose an alternative site at higher cost but less community disruption':{
				"cost":10000,
				"pr_effect":10,
			}
		},
	},
	"SocialMedia":{
		"description":"<strong>Marketing</strong><br>A popular but polarizing influencer offers to promote your brand for a significant fee. They have a large following but a history of public controversy.",
		"options":{
			'Gamble and accept the partnership for exposure':{
				"cost":5000,
				"pr_effect":20,
			},
			'Distance your brand to avoid potential backlash?':{
				"cost":0,
				"pr_effect":0,
			}
		}
	},
	"Conditions":{
		"description":"Your employees are complaining about substandard working conditions. How do you respond?",
		"options":{
			"Undergo massive updates of your facility":{
				"cost":10000,
				"pr_effect":20
			},
			"Put in the bare minimum repairs to keep employees from complaining":{
				"cost":3000,
				"pr_effect":0
			},
			"Do nothing":{
				"cost":0,
				"pr_effect":-20
			}
		}
	},
	"Production":{
		"description":"Your new line has dropped and you have been informed there was a manufacturing defect.",
		"options":{
			"You recall the defective phones":{
				"cost":10000,
				"pr_effect":20
			},
			"You offer free fixes on all your phones":{
				"cost":3000,
				"pr_effect":0
			},
			"Do nothing":{
				"cost":0,
				"pr_effect":-20
			}
		}
	},
	"Daycare":{
		"description":"<br>Human Resources</strong><br>Your employees are using too many sick days to take care of their children.  Production is suffering.",
		"options":{
			"Provide a comprehensive day care package":{
				"cost":10000,
				"pr_effect":20
			},
			"Provide a stipend for daycare":{
				"cost":3000,
				"pr_effect":0
			},
			"Do nothing":{
				"cost":0,
				"pr_effect":-20
			}
		}
	},
	"Deforestation":{
		"description":"Congratulations! You have built a brand-new facility for your Company. Unfortunately,you had to remove 300 trees to make space for your building. How do you respond?",
		"options":{
			"Plant 300 trees on a different section of the property":{
				"cost":8000,
				"pr_effect":20
			},
			"Plant a portion of the trees":{
				"cost":3000,
				"pr_effect":0
			},
			"Replant zero trees":{
				"cost":0,
				"pr_effect":-20
			},
		}
	},
	"Environmental":{
		"description":"The train carrying your shipment of batteries has derailed and caught fire.  You have a potential environmental disaster on your hands.",
		"options":{
			"Clean it up and make it better than it was":{
					"cost":50000,
					"pr_effect":25
			},
			"Hire crisis management firm to blame it on someone else":{
					"cost":10000,
					"pr_effect":10
			},
			"Pretend it didn't happen.  Looks normal to me, trees always glow in the dark":{
					"cost":0,
					"pr_effect":-25
			},
		}
	},
	"Pandemic":{
		"description":"A pandemic is spreading the globe.  People are reluctant to leave their houses and there are supply shortages of all your components",
		"options":{
			"Allow your employees to stay home with pay":{
					"cost":50000,
					"pr_effect":50
			},
			"Take reasonable precautions (Social Distancing, Hand Sanatizer,etc) and add extra shifts to reduce the number of people working at the same time.  Production will take a hit":{
					"cost":20000,
					"pr_effect":15
			},
			"It's just a cold.  We can tough it out":{
					"cost":0,
					"pr_effect":-15
			}
		}
	}
}

class Admin():
	def options(self):
		return {
			"Overhead":Overhead().options().get("options") or [],
			"Dilemas":Dilemmas().options() or []
		}

class Overhead():

	def cost(self,key:str) -> int:
		for item in OVERHEAD:
			if item.get("value","") == key:
				return i(item.get("cost",0))
		return 0

	def value(self,key:str) -> int:
		for item in OVERHEAD:
			if item.get("value","") == key:
				# Debug(title="Overhead Value",data=item.get("effect"))
				return i(item.get("effect",0))
		return 0

	def options(self):
		return {"options":OVERHEAD}

class Dilemmas():
	error:str
	def fromJSON(self,data:str) -> list:
		if len(data)>0:
			return getJSON(data)
		else:
			return[]
	def description(self,name:str):
		return DILEMMAS.get(name,{}).get("description")

	def cost(self,dilemma:str,action:str) -> Decimal:
		try:
			return m(DILEMMAS.get(dilemma,{}).get("options",{}).get(action,{}).get("cost"))
		except Exception as e:
			self.error = f"Trouble getting Dilemma cost {dilemma} -> {action} \n{str(e)}"
			Debug(title=f"Trouble getting Dilemma cost {dilemma} -> {action}")
			return m(0)

	def pr(self,dilemma:str,action:str) -> int:
		try:
			return i(DILEMMAS.get(dilemma,{}).get("options",{}).get(action,{}).get("pr_effect"))
		except Exception as e:
			self.error = f"Trouble getting Dilemma cost {dilemma} -> {action} \n{str(e)}"
			Debug(title=f"Trouble getting Dilemma cost {dilemma} -> {action}")
			return i(0)

	def mk_quarter(self,dilemmas:str) -> list:
		all = []
		for dilemma in getJSON(dilemmas) or []:
			data = {"description":DILEMMAS.get(dilemma,{}).get("description"),"options":{}}
			for option,val in DILEMMAS.get(dilemma,{}).get("options",{}).items():
				data["options"][option] = val
				try:
					del data["options"][option]["pr_effect"]
				except:
					pass
			all.append(data)
		return all

	def client_option(self,name:str):
		data = {}
		for desc,val in DILEMMAS.get(name,{}).get("options",{}).items():
			data[desc] = val
			try:
				del val["pr_effect"]
			except:
				pass

		return {"options":data,"description":DILEMMAS.get(name,{}).get("description")}

	def option(self,name:str):
		data = {}
		for desc,val in DILEMMAS.get(name,{}).get("options",{}).items():
			data[desc] = val
		return data

	def options(self):
		data = []
		for name,detail in DILEMMAS.items():
			data.append({
				"title":detail.get("description"),
				"value":name
			})
		return data

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 getUserID() -> int:
	try:
		return session["User"]["ID"]
	except Exception as e:
		Debug(title="Trouble getting session user",data=e)
		Debug(title="Session User",data=session["User"])
		return -1

class Global_Settings(Model):
	table = "Global"

	def summary(self,quality:str) -> dict:
		data = {}
		settings = Global_Settings()
		price = f(settings.get_value(f"{quality}_Median_Price"))
		bottom = price * .80
		data["price"] = f"${i(price * 0.80)} - ${i(price * 1.20)}"
		return {"price":data["price"]}

	def median_price(self,quality:str) -> Decimal:
		prices = self.median_prices()

		if item := prices.get(quality):
			return item.get("value")
		else:
			return Decimal(0.00)

	def median_prices(self):
		data = self.cache()
		return {
			"Low":data.get("Low_Median_Price"),
			"Medium":data.get("Medium_Median_Price"),
			"High":data.get("High_Median_Price")
		}

	def get(self,ID:int) -> dict:
		self.qb = self.qb_init()
		self.qb.filter(query="ID=%s",parms=[ID])
		return self.select_one()

	def get_value(self,key:str):
		self.qb = self.qb_init()
		self.qb.filter(query="`key`=%s",parms=[key])
		d = self.select_one() or {}
		return d.get("value","")

	def cache(self) -> dict:
		return super().cache(field="key")

class Business_Item(dict):
	def cost_per_unit(self,manuf_coef:int=100) -> Decimal:
		from Utils import m
		prices = Global_Settings().median_prices()

		try:
			cost = f(prices.get(self.get("Quality",""),{}).get("value") or -1)
			cost *= f(manuf_coef/100)
			return m(cost/4)

		except Exception as e:
			Debug(title="Trouble getting Units Price",data=prices)
			return m(999)

	@property
	def total_value(self) -> Decimal:
		try:
			quality = self.get("Quality")
			capital = m(self.get("Capital"))
			loan = m(self.get("Loan"))
			median = f(Global_Settings().get_value(f"{quality}_Median_Price"))
			avg_val = m(median/2)
			unit_val = m(i(self.get("Units")) * avg_val)
			total = (capital) + unit_val
			# Debug(title="Unit Value",data=unit_val)
			return total
		except Exception as e:
			Debug(title="Trouble with total value",data=e)

		return Decimal(-1)

	def calc_capital(self,rev:Decimal,expenses:Decimal) -> Decimal:
		loan = m(self.get("Loan",0.00))
		self["Capital"] = m((rev+loan)-expenses)
		return self["Capital"]

class Business(Model):
	id:int = 1
	table = "Business"
	row_cls = "Business_item"

	def inventory_value(self,units:int,quality:str) -> Decimal:
		val = 0
		median = f(Global_Settings().get_value(f"{quality}_Median_Price"))
		avg_val = m(median/2)
		val = units * avg_val
		return m(val)

	def calc_stock_price(self,units:int,quality:str,capital:Decimal) -> Decimal:
		try:
			inv_value = self.inventory_value(units = units,quality=quality)
			return m((inv_value + capital) / 20240)
		except:
			return m(0)

	def reset(self) -> int:
		gdata = Global_Settings()
		loan = m(gdata.get_value("Initial_Capital") or 0)
		status = 1
		for bus in self.all():
			bus["Capital"] = loan
			bus["Loan"] = loan
			bus["PR_Score"] = 0
			bus["Units"] = 0
			bus["Stock_Price"] = 0
			self.update(bus)
			if self.update(bus) <=0:
				status = 0
		return status

	def get(self,ID:int) -> dict:
		self.qb = self.qb_init()
		self.qb.filter(query="ID=%s",parms=[ID])
		row = self.select_one()
		bus = {}
		if row:
			bus = Business_Item(row)
			bus["unit_cost"] = bus.cost_per_unit()
			bus["employee_list"] = {}
			# quarters = Quarters()
			# settings = Quarter_Settings()
			plans = Utils.group_by("Quarter_ID",Quarters().get_business(ID))
			prev = 0

			for setting in Quarter_Settings().active():
				qid = setting.get("ID","")
				bus["employee_list"][qid] = prev
				prev = plans.get(qid,{}).get("employees") or 0
		return bus

	def cache(self,field="ID",query:str="",parms:Optional[list]=None) -> dict:
		data = {}
		self.qb = self.qb_init()

		if query:
			self.qb.filter(query=query,parms=parms)

		for bus in self.select():
			data[bus.get("ID")] = bus
		return data

	def details(self) -> list:
		d = []
		for bus in self.all():
			bus["Quarter_Plan"] = Quarters().get_business(Business_ID=bus.get("ID"))
			d.append(bus)
		return d

	def detail(self,ID:int) -> dict:
		bus = self.get(ID=ID)
		bus["Quarter_Plan"] = Quarters().get_business(Business_ID=ID)
		return bus

class Quarter_Settings(Model):
	id:int = 1
	table = "Quarter_Settings"

	def get(self,ID:int) -> dict:
		self.qb = self.qb_init()
		self.qb.filter(query="ID=%s",parms=[ID])
		return self.select_one()

	def get_name(self,name:str) -> dict:
		self.qb = self.qb_init()
		self.qb.filter(query="Name=%s",parms=[name]).limit("1")
		return self.select_one() or {}

	def deactivate(self) -> int:
		self.qb = self.qb_init()
		self.qb.update(data={"Active":0})
		return self.execute()

	def bus_summary(self,bus_id:int) -> dict:
		self.qb = self.qb_init()
		data = {}

		for row in self.all():
			name = row.get("Name")
			id = row.get("ID")
			Debug(title="bus summary",data=row)
			bus_data = Business().get_id(ID=bus_id)
			data[id] = row
			data[id]["Dilemmas"] = Dilemmas().mk_quarter(row.get("Dilemmas"))
			# data[id]["Dilema_Details"] = Dilemmas().client_option(row.get("Ethical_Dilema"))
			data[id]["Overhead_Settings"] = Overhead().options()
			data[id]["Production_Cost"] = self.calc_production_cost(bus_id=bus_id,quarter_name=name)
			data[id]["Efficiency"] = 40
			data[id]["Median"] = Global_Settings().summary(bus_data.get("Quality",""))
		return data

	##############################################################
	#--->  Warning this will only show data for the logged in user
	##############################################################

	def summary(self) -> dict:
		self.qb = self.qb_init()
		data = {}

		for row in self.all():
			name = row.get("Name")
			id = row.get("ID")
			bus_id = getBusID()
			bus_data = Business().get_id(ID=bus_id)
			data[id] = row
			# data[id]["Dilema_Details"] = Dilemmas().client_option(row.get("Ethical_Dilema"))
			data[id]["Overhead_Settings"] = Overhead().options()
			data[id]["Production_Cost"] = self.calc_production_cost(bus_id=bus_id,quarter_name=name)
			data[id]["Efficiency"] = 40
			data[id]["Median"] = Global_Settings().summary(bus_data.get("Quality",""))
		return data

	def calc_production_cost(self,bus_id:int,quarter_name:str="") -> Decimal:
		bus = Business().get(bus_id)
		cost = 0.00
		coef = 100
		try:
			if quarter_name:
				q = Quarter_Settings().get_name(name=quarter_name)
				coef = i(q.get("Manufacturing_Cost"))
			bus = Business_Item(Business().get_id(bus_id))
			return bus.cost_per_unit(manuf_coef=coef)

			quality = bus.get("Quality") or ""
			Debug(title="Getting Price for {quality}")
			prices = Global_Settings().median_prices()
			cost = float(prices.get(quality,{}).get("value") or 0)
			cost = m(cost*.25)
		except Exception as e:
			Debug(title="Exception",data=e)
			Debug(title="Trouble with Production Calc",data=[cost,prices])
			cost = 0.00
		return m(cost)

	def options(self) -> list:
		qdata = []
		for q in self.all(query="Active=1"):
			qdata.append({"title":q.get("Name",""),"value":q.get("ID")})
		return qdata

	def cache(self) -> dict:
		data = {}
		self.qb = self.qb_init()
		for q in self.select():
			data[q.get("ID")] = q
		return data

	def find(self,query:str,parms:Optional[list]) -> dict:
		self.qb = self.qb_init()
		self.qb.filter(query=query,parms=parms)
		return self.select_one()

	def active(self) -> list:
		return self.all(query="Active=1")

	def all(self,query:str='',parms:Optional[list]=None) -> list:
		self.qb = self.qb_init()
		if query:
			self.qb.filter(query=query,parms=parms)
		self.qb.order(field="Name")
		return list(self.select())

class Quarters(Model):
	id:int = 1
	table = "Quarters"

	def reset(self) -> int:
		self.qb = self.qb_init()
		self.qb.debug = True
		self.qb.delete(data={})
		return self.execute()

	def get_quarter_id(self,Quarter_ID:int) -> dict:
		self.qb = self.qb_init()
		self.qb.filter(query="Quarter_ID=%s",parms=[Quarter_ID])
		return self.select_one()

	def check_exists(self,Business_ID:int,Quarter_ID:int) -> bool:
		self.qb = self.qb_init()
		self.qb.filter(query="Business_ID=%s and Quarter_ID=%s",parms=[Business_ID,Quarter_ID])
		return len(list(self.select()))>0

	def get_business(self,Business_ID:int) -> list:
		self.qb = self.qb_init()
		self.qb.fields(fields="Quarters.*,Quarter_Settings.Name as Quarter_Name")
		self.qb.filter(query="Business_ID=%s",parms=[Business_ID])
		self.qb.join(join="join Quarter_Settings on Quarter_Settings.ID=Quarters.Quarter_ID")
		self.qb.order(field="Name")
		return self.parse_rows(self.select())

	def all(self,query:str="",parms:Optional[list] = None,show_calc:bool = False) -> list:
		self.qb = self.qb_init()
		self.qb.fields("Quarters.*,(select Name from Business where ID=Quarters.Business_ID) as Business_Name,(select Name from Quarter_Settings where ID=Quarters.Quarter_ID) as Quarter_Name")
		self.qb.order(field="Quarters.Business_ID,Quarters.Quarter_ID")
		if query:
			self.qb.filter(query=query,parms=parms)

		return self.parse_rows(self.select(),show_calc)

	def parse_rows(self,rows:Rows,show_calc:bool=False) -> list:
		d = []
		for r in rows:
			calc = Utils.getJSON(r.get("calc"))
			cdata = {}
			# Utils.Debug(title="Calc",data=calc)
			qsettings = Quarter_Settings().get_id(i(r.get("Quarter_ID")))
			if calc:
				cdata = {
					"stock_price":calc.get("stock_price"),
					"pr_score":calc.get("pr_score"),
					"quarter_name":qsettings.get("Name"),
					"capital_start":calc.get("capital_start"),
					"capital_end":calc.get("capital_end"),
					"revenue":calc.get("revenue"),
					"expense_list":calc.get("expense_list"),
					"expenses":calc.get("expenses"),
					"units_on_hand_start":calc.get("units_on_hand_start"),
					"inventory_value_start":m(calc.get("unit_cost")) * i(calc.get("units_on_hand_start")),
					"units_on_hand":calc.get("units_on_hand"),
					"unit_cost":calc.get("unit_cost",0),
					"inventory_value":m(calc.get("unit_cost")) * i(calc.get("units_on_hand")),
					"units_produced":calc.get("units_produced"),
					"units_sold":calc.get("units_sold"),
					"profit":calc.get("profit"),
				}

			if not show_calc and r.get("calc") and len(r.get("calc",""))>0:
				r[".editable"] = False
			else:
				r[".editable"] = True

			r["calc"] = cdata
			d.append(r)
		return d

class Reports_PL():
	def generate(self) -> dict:
		return {}

class Client_Dashboard(Model):
	def summary(self,bus_id:int) -> dict:
		q_settings = Quarter_Settings()
		bus_data = Business().get(ID=bus_id)
		data = {
			"Business":bus_data,
			"Plan":Quarters().get_business(bus_id),
			"State":"Dashboard",
			"Quarter_select":q_settings.options(),
			"Quarter_Settings":q_settings.bus_summary(bus_id),
			"Schedule":Schedule().current(),
			"Reports":{
				"PL":Reports_PL().generate()
			}
		}
		if (not data.get("Business")):
			data["State"] = "bus_init"

		return data

class Users(Model):
	table = "Users"
	row_cls = "User"
	def auth(self,user:str,password:str) -> dict:
		self.qb = self.qb_init()
		self.qb.filter(query="User=%s and Password=%s",parms=[user,password])
		r = self.select_one() or {}
		return r

	def check_business(self,UserID:int) -> bool:
		self.qb = self.qb_init()
		self.qb.filter(query="ID=%s",parms=[UserID])
		self.qb.debug = True
		r = self.select_one() or {}
		return i(r.get("Business_ID"))>0

	def mk_row(self,data):
		if isinstance(data.get("Groups"),str):
			data["Groups"] = Utils.getJSON(data.get("Groups",""))
		return row(data)

	def all(self,query:str="",parms:Optional[list]=None) -> list:
		self.qb = self.qb_init()
		self.qb.fields(fields="Users.*,(select Name from Business where Business.ID = Users.Business_ID) as Business_Name")
		if query:
			self.qb.filter(query=query,parms=parms)
		return list(self.select())


class Eval_Data():
	names = {
		"last":[
		  "Anderson",
		  "Thompson",
		  "Martinez",
		  "Williams",
		  "Brown",
		  "Miller",
		  "Davis",
		  "Garcia",
		  "Rodriguez",
		  "Wilson"
		],
		"male":[
			  "James",
			  "Michael",
			  "David",
			  "John",
			  "Robert",
			  "Daniel",
			  "Matthew",
			  "Christopher",
			  "Andrew",
			  "Joseph"
		],
		"female":[
		  "Emily",
		  "Sophia",
		  "Olivia",
		  "Ava",
		  "Isabella",
		  "Mia",
		  "Charlotte",
		  "Amelia",
		  "Harper",
		  "Evelyn"
		]
	}
	profiles = {"m":5,"f":5,"body":5}
	depts = ["Management","Labor","Marketing","Production"]
	gender = "female"

	def __init__(self):
		self.gender = "male" if random.randrange(0,1) else "female"

	@property
	def department(self) -> str:
		return self.depts[random.randrange(0,len(self.depts))]

	@property
	def age(self) -> int:
		return random.randrange(20,67)

	@property
	def name(self) -> str:
		return self.names[self.gender][random.randrange(0,len(self.names[self.gender]))] + ' ' + self.names["last"][random.randrange(0,len(self.names["last"]))]

	@property
	def profile(self) -> dict:
		c = 5
		return {
			"head":f"{self.gender[0]}{random.randrange(0,c)}",
			"body":f"b{random.randrange(0,c)}",
		}

class Employee_Evaluation(row):

	table = "Employee_Evaluations"
	def __init__(self,data):
		if isinstance(data.get("Profile"),str):
			data["Profile"] = getJSON(data.get("Profile"))
		super().__init__(data)

	@classmethod
	def gen(cls,bus_id:int,quarter_id:int) -> Self:

		ev = Eval_Data()
		data = {
			"Business_ID":bus_id,
			"Quarter_ID":quarter_id,
			"Name":ev.name,
			"Age":ev.age,
			"Department":ev.department,
			"Profile":ev.profile,
			"Performance":"Over the previous quarter, the employee demonstrated a strong work ethic and consistently met expectations when focused on assigned tasks. While they were frequently late to the office due to ongoing personal obligations, once present they showed determination and a willingness to put in extra effort to complete their work. There were occasional lapses in concentration, particularly during periods when personal matters required attention, which at times slowed progress or required follow-up. Despite these challenges, the employee remained reliable in delivering core responsibilities and was receptive to feedback. With improved time management and fewer external distractions, their overall performance has the potential to increase significantly in the coming quarter."
		}
		return cls(data)


class Employee_Evaluations(Model):
	table = "Employee_Evaluations"
	row_cls = "Employee_Evaluation"

	def get(self,ID:int) -> list[row]:
		self.qb = self.qb_init()
		self.qb.filter(query="ID=%s",parms=[ID])
		return list(self.select())

	def get_quarter(self,bus_id:int,quarter_id:int) -> list[row]:
		self.qb = self.qb_init()
		self.qb.filter(query="Business_ID=%s and Quarter_ID=%s",parms=[bus_id,quarter_id])
		return list(self.select())

	def mk_row(self,data:dict) -> row:
		return Employee_Evaluation(data)

	def gen(self,bus_id:int,quarter_id:int) -> dict:
		data = {"bus_id":bus_id,"quarter_id":quarter_id}
		return data

class Schedule(Model):
	table = "Schedule"
	def current(self) -> list:
		self.qb = self.qb_init()
		self.qb.order("timestamp desc")
		data = []
		now = IODate().utc()
		for item in self.select():
			dt = IODate(item["timestamp"]).utc()
			td = now-dt
			item["now"] = now.full()
			item["future"] = (now-dt).total_seconds()>=0
			item["delta"] = f"{td.days} Days, {td.seconds // 3600} Hours"
			data.append(item)
		return data


class Roll(Model):

	@property
	def period(self) ->tuple[str,str]:
		now = IODate().utc()
		start = IODate().replace(minute=0,second=0)
		end = IODate().replace(minute=59,second=59)
		if now.hour>12:
			start.replace(hour=12)
			end.replace(hour=23)
			return (start,end)
		else:
			start.replace(hour=1)
			end.replace(hour=11)
			return (start,end)

	def get_period(self,bus_id:int) -> list:
		all = []
		(start,end) = self.period
		self.qb = self.qb_init()
		self.qb.join(join=f"left join Roll on Roll.Student_ID = Students.ID and Roll.timestamp>=%s and Roll.timestamp<=%s",parms=[start,end])
		self.qb.filter(query="Business_ID=%s",parms=[bus_id])
		return all

	def students(self,bus_id:int) -> list:
		all = []
		self.qb = self.qb_init()
		self.qb.filter(query="Business_ID=%s",parms=[bus_id])
		return all

class Reports():
	def outlook(self) -> dict:
		data = {}
		for quarter in Quarter_Settings().all():
			data[quarter.get("Name")] = quarter
		return data


class Base_Simulator():
	quarter_settings:dict
	_global_settings:dict
	quarter:dict
	bus:dict
	_log:dict

	def __init__(self,quarter:dict,bus:Business_Item):
		self.quarter_settings = Quarter_Settings().cache().get(quarter.get("Quarter_ID")) or {}
		self._global_settings = Global_Settings().cache()
		self.quarter = quarter
		self.bus = Business_Item(bus)
		self.quality = self.bus.get("Quality")
		self._log = defaultdict(list)
		self.capital = m(bus.get("Capital"))
		self.labor_rate = m(self.quarter_settings.get("Labor_Cost",0.00))
		self.wages = m(self.labor_rate * i(self.quarter.get("employees")))
		self.employees = i(self.quarter.get("employees"))
		self.employee_potential = m(self.globals("Employee_Sales"))
		self.employee_sales = self.employees * self.employee_potential 

		self.rd_spend = m(self.quarter.get("rd") or 0.00)
		self.rd_median = m(self.globals("RD_Median"))
		self.rd_coef = f(self.globals("RD_ROI"))
		self.price = m(self.quarter.get("price"))

		# self.other = self.calc_other(self.quarter.get("ethical_dilema",""))
		self.other,self.pr_score = self.calc_other()
		self.units_on_hand = i(self.bus.get("Units")) or 0
		self.units_produced = i(self.quarter.get("units"))
		self.units_total = i(self.units_produced + self.units_on_hand)
		self.units_sold = 0

		self.prod_cost = self.calc_prod_cost(self.units_produced)
		self.revenue = m(0.00)

		self.marketing = m(self.quarter.get("marketing"))
		self.marketing_roi = f(self.globals("Marketing_ROI"))
		self.marketing_eff = (self.marketing_roi)

		self.marketing_sales = self.calc_eff(value=self.marketing,median=m(self.globals("Marketing_Median")),coef=self.marketing_eff)
		try:
			self.marketing_result = round(self.marketing_sales / self.marketing) * 100
		except:
			self.marketing_result = 0
		self.marketing_units = round(self.marketing_sales / self.price)
		self.plant_size = self.quarter_settings.get("plant_size","")
		self.calc_revenue()
		# self.units_left = self.units_total - self.units_sold
		# self.capital = m((self.revenue - self.expenses) + m(self.bus.get("Capital")))
		# self.inventory_value = Business().inventory_value(self.units_left,self.bus.get("Quality",""))
		# self.stock_price = Business().calc_stock_price(capital=self.capital,inv_value=self.inventory_value)

	@property
	def manuf_cost(self) -> Decimal:
		return m(self.unit_cost * self.units_produced)

	@property
	def unit_cost(self) -> Decimal:
		bus = Business_Item(self.bus)
		return Decimal(bus.cost_per_unit() or 0.00)

	@property
	def dilemma_cost(self) -> Decimal:
		cost,_ = self.calc_other()
		return(cost or Decimal(0))

	@property
	def fixed_cost(self) -> Decimal:
		tier =  self.quarter.get("overhead") or ""
		try:
			cost = Overhead().cost(tier)
			return m(cost)
		except Exception as e:
			Debug(title="Troubling finding fixed cost",data=tier)
			return m(0)

	@property
	def plant_cost(self) -> Decimal:
		if self.quarter_settings.get("plant_size") == "Small":
			return m(1_000)
		elif self.quarter_settings.get("plant_size") == "Mediam":
			return m(2_500)
		elif self.quarter_settings.get("plant_size") == "Large":
			return m(5_000)
		return m(0.00)

	@property
	def expense_list(self) -> dict:
		return {
			"Labor Cost":self.wages,
			"Production Cost":self.prod_cost,
			"Marketing":self.marketing,
			"Fixed":self.fixed_cost,
			"Other":self.dilemma_cost
		}

	@property
	def expenses(self) -> Decimal:
		total = sum(self.expense_list.values())
		return m(total)

	@property
	def rd_eff(self) -> float:
		spend = self.rd_spend
		median = self.rd_median
		coef = self.rd_coef
		result = self.calc_eff(value=spend,median=median,coef=coef)
		return round(f(result / median),2)


	def calc_other(self) -> tuple[Decimal,int]:
		dilemmas = getJSON(self.quarter_settings.get("Dilemmas") or "")
		actions =  getJSON(self.quarter.get("dilemma") or "")
		cost = m(0)
		pr = 0
		i=0
		for dilemma in dilemmas:
			# Debug(title="Dilemma",data=dilemma)
			# Debug(title="Action",data=actions[i])
			try:
				cost += Dilemmas().cost(dilemma=dilemma,action=actions[i])
				pr += Dilemmas().pr(dilemma=dilemma,action=actions[i])
				i+=1
			except Exception as e:
				pass
		# Debug(title="Troubling finding fixed cost",data=[dilemma,actions[i]])
		return cost,pr

	def calc_other_old(self) -> tuple[Decimal,int]:
		dilemmas = getJSON(self.quarter.get("dilemma"))
		resp = getJSON(self.quarter.get("dilemma"))
		i=0
		total = 0
		pr = 0
		for problem in self.quarter_settings.get("Dilemmas") or []:
			detail = DILEMMAS.get(problem,{}).get("options",{}).get(resp[i])
			Debug(title="Found detail",data=detail)
			if detail:
				total += detail.get("cost")
				pr += detail.get("pr_effect")

		return Decimal(total),pr

	def calc_prod_cost(self,units:int) -> Decimal:
		return m(units * self.unit_cost)

	def calc_revenue(self) -> Decimal:
		Debug(title="Calc Units Total",data=self.units_total)
		potential = i(round(self.marketing_sales / self.price))

		if potential > self.units_total:
			self.units_sold = self.units_total
		else:
			self.units_sold = potential

		self.revenue = self.price * self.units_sold
		return self.revenue 

	def log(self,key:str,val:Any):
		self._log[key].append(val)

	def calc_price_eff(self):
		max_eff = 50
		settings = Global_Settings()
		median = settings.median_price(self.bus.get("Quality",""))
		delta = Utils.change(median,self.price)

		eff = ((delta*100) / max_eff)

		if eff < -1:
			eff = -max_eff 

		eff *=-1
		eff /=1.5

		if eff <-1:
			eff = -1

		Debug(title="Price Eff",data={
			"median":median,
			"price":self.price,
			"delta":delta,
			"eff":eff
		})
		return eff

	def calc_eff(self,value:Decimal,median:Decimal,coef:float) -> Decimal:
		price_eff = self.calc_price_eff()
		c_size = f((median / 20))
		r = f(value)
		eff = 85
		inc = 5
		total = 0.00
		while r > 0.00:
			if r < c_size:
				c_size = r

			eff_rate = (eff/100) + coef
			cval = abs(c_size * eff_rate)
			total += cval
			r-=c_size

			if r < 0:
				r = 0

			eff+=inc
			if eff>100:
				eff = 100
				inc = -1

			if eff < 20:
				eff = 20


		strength = i(self.quarter_settings.get("Economic_Strength")) / 100
		ototal = total
		total *= strength
		eff_total = total + (total*price_eff)
		Debug(title="Price Efficiency",data={
			"ototal":ototal,
			"total":total,
			"strength":strength,
			"eff_total":eff_total,
			"price_eff":price_eff
		})
		return m(eff_total)

	def globals(self,key,default='') -> Any:
		return self._global_settings.get(key,default).get("value")

class Q_Sim(Base_Simulator):
	def __init__(self,quarter:dict,bus:dict):
		super().__init__(quarter=quarter,bus=bus)


	def calc(self):
		capital = (self.revenue - self.expenses) + self.capital
		units_on_hand = self.units_total - self.units_sold
		quality = self.bus.get("Quality","")

		stock_price = Business().calc_stock_price(units = units_on_hand,quality=quality,capital=capital)
		return {
			"Quality":quality,
			"pr_score":self.pr_score,
			"labor":{
				"employees":self.employees,
				"labor_cost":self.wages,
				"sales":self.employee_sales
			},
			"marketing":{
				"spend":self.marketing,
				"eff":self.marketing_result,
				"median":m(self.globals("Marketing_Median")),
				"ceof":f(self.globals("Marketing_ROI")),
				"sales":self.marketing_sales,
			},
			"wages":self.wages,
			"price":self.price,
			"expenses":self.expenses,
			"expense_list":self.expense_list,
			"revenue":self.revenue,
			"profit":self.revenue - self.expenses,
			"capital":(self.revenue - self.expenses) + self.capital,
			"capital_start":self.capital,
			"capital_end":capital,
			"manuf_cost":self.manuf_cost,
			"unit_cost":self.unit_cost,
			"units_produced":self.units_produced,
			"units_sold":self.units_sold,
			"units_on_hand_start":self.units_on_hand,
			"units_on_hand":units_on_hand,
			"stock_price":stock_price
		}

class Sim(Model):
	def calc_stock(self,business:dict):
		try:
			bus = Business_Item(business)
			val = m(bus.total_value)
			stock = val / 20240

			if stock > 0:
				return stock
			else:
				return 0

		except Exception as e:
			Debug(title="Exception",data=e)
			Debug(title="Trouble with calc stock",data=business)
			return -1

	def pr_avg(self,bus:dict) -> int:
		pr = 0
		try:
			assert len(bus["PR_Scores"])>0, "No PR Scores"
			pr = i(sum(bus["PR_Scores"]))
			#pr /= int(len(bus["PR_Scores"]))
			return i(pr)
		except Exception as e:
			pass
			# Debug(title="Trouble with pr average",data=e)
		return 0

	def quarters(self,test:bool=False,bus_id:Optional[int]=None) -> dict:
		from Utils import m
		pbus_id = bus_id
		data = {}
		gsettings = Global_Settings().cache()
		business = Business()

		# if bus_id:
		# 	bus_cache = business.cache(query="Business.ID=%s",parms=[bus_id])
		# else:
		# 	bus_cache = business.cache()

		bus_cache = business.cache()
		quarters = Quarters()
		sims = []

		for cb_id in bus_cache:
			bus_cache[cb_id]["Capital"] = m(bus_cache[cb_id].get("Loan")) or gsettings.get("Initial_Capital",{}).get("value")
			bus_cache[cb_id]["Units"] = 0.00
			bus_cache[cb_id]["PR_Score"] = 0
			bus_cache[cb_id]["Stock_Price"] = 0
			bus_cache[cb_id]["PR_Scores"] = []

		# if bus_id:
		# 	q_all = Quarters().all(query="Business_ID=%s",parms=[bus_id])
		# else:
		# 	q_all = Quarters().all()

		q_all = Quarters().all()
		for quarter in q_all:
			bus_id = quarter.get("Business_ID")
			bus_data = bus_cache.get(bus_id) or {}
			calc = Q_Sim(quarter=quarter,bus=bus_data).calc()
			quarter["calc"] = calc
			if not test:
				quality = bus_cache[bus_id].get("Quality","")
				units = i(calc.get("units_on_hand"))
				capital = m(calc.get("capital"))
				quarter["calc"]["stock_price"] = Business().calc_stock_price(units=units,quality=quality,capital=capital)
				quarters.update(data={"ID":quarter.get("ID"),"calc":Utils.toJSON(calc)})

			qid = quarter.get("ID")
			bus_cache[bus_id].setdefault("quarters",[])
			bus_cache[bus_id]["quarters"].append(quarter)

			bus_cache[bus_id].setdefault("rev_total",m(0))
			bus_cache[bus_id].setdefault("expense_total",m(0))
			bus_cache[bus_id].setdefault("units_sold",m(0))
			bus_cache[bus_id].setdefault("units_made",m(0))

			bus_cache[bus_id]["rev_total"]+= m(quarter["calc"].get("revenue",0.00))
			bus_cache[bus_id]["expense_total"]+= m(quarter["calc"].get("expenses",0.00))
			bus_cache[bus_id]["units_sold"] += i(calc.get("units_sold"))
			bus_cache[bus_id]["units_made"] += i(calc.get("units_produced"))
			bus_cache[bus_id]["Units"] = calc.get("units_on_hand")
			bus_cache[bus_id]["Capital"] = calc.get("capital")

			bus_cache[bus_id]["PR_Score"] += calc.get("pr_score")
			bus_cache[bus_id]["PR_Scores"].append(calc.get("pr_score"))

			sims.append(calc)

			Debug(title="Updating Business",data=bus_cache.get(pbus_id))

		if not test:
			for bus_id,bus in bus_cache.items():
				stock = self.calc_stock(business=bus)
				business.update(data={"ID":bus_id,"Stock_Price":stock, "PR_Score":self.pr_avg(bus),"Units":(bus.get("Units")),"Capital":bus.get("Capital",0.00)})

		return {"sims":sims,"bus_cache":bus_cache}

def auth_failed(func):
	def message(*argc,**argv):
		user = argv.get("User")
		level = argv.get("level")
		response = {"status":0,"message":f"User {user} is not {level}"}
		Utils.Debug(title="Response",data=response)
		return response
	return message

def auth(level:str):
	Utils.Debug(title="Auth args",data={"Level":level})
	user = "Brenta"

	def grab_func(func):
		def wrapper(*argc,**argv):
			Utils.Debug(title="inside",data={"argc":argc,"argv":argv})
			func(*argc,**argv)
		return wrapper
	if user == "Brent":
		return grab_func
	else:
		return auth_failed

BW_Config = {
	"capital":10000
}
