from typing import Optional,Any,TypeAlias
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

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),
			"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 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
}
