from typing import Optional,Any,Generator,Self
from flask import session
import Utils
from Utils import Debug,IODate
from db import Model,row


def getUserID() -> int:
	try:
		Debug(title="Session User",data=session["User"])
		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 Mission_Model(Model):
	mission_id:int = 1
	user_id:int = 1
	table:str = ""
	_start:str = ""
	_end:str = ""
	q = {}

	def __init__(self,mission_id:int = 1):
		self.mission_id = mission_id
		self.q = self.q or {
			"Clients":f"join Clients on Clients.ID = {self.table}.Client_ID"
		}
		super().__init__()

	def range(self,start,end) ->Self:
		self.start(start)
		self.end(end)
		return self

	def start(self,date) -> Self:
		self._start = str(IODate(date))
		return self

	def end(self,date) -> Self:
		self._end = str(IODate(date))
		return self

	def querySuffix(self) -> str:
		return f"{self.table}.Mission_ID = {self.mission_id}"

	def get_client(self,client_id:int) -> list:
		self.qb = self.qb_init()
		self.qb.filter(query="Client_ID=%s",parms=[client_id])
		self.qb.order(field="Timestamp",direction="desc")
		return self.check_recent(self.select())

	def check_recent(self,data:list) -> list:
		data = list(data)
		for i in range(len(data)):
			if data[i].get("Timestamp") and IODate(data[i].get("Timestamp")).daysFromNow()<7:
				data[i]["Recent"] = True
			else:
				data[i]["Recent"] = False
		return data

	def by_day(self) -> dict:
		self.qb = self.qb_init()
		self.qb.fields(fields="count(*) as count,date(Timestamp) as dt")
		self.qb.group(group="date(Timestamp)")

		data = {}
		for row in self.select():
			dt = IODate(row.get("dt")).str()
			data.setdefault(dt,{})
			data[dt]["Served"] = row.get("count")

		return data

	def all(self,query:str="",parms:Optional[list]=None) -> list:
		self.qb = self.qb_init()
		self.qb.fields(f"{self.table}.*, Clients.Name as Name")
		self.qb.join(self.q.get("Clients",""))

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

		self.qb.order(field="Timestamp",direction="desc")
		return list(self.select())

class Users(Model):
	table = "Users"

	def auth(self,user:str,password:str) -> dict:
		self.qb = self.qb_init()
		self.qb.fields(fields="User as UserID,`Groups`,ID")
		self.qb.filter(query="User=%s and Password=%s",parms=[user,password])

		if udata := self.select_one():
			udata["Groups"] = Utils.getJSON(udata.get("Groups"))
			return udata
		else:
			return {}

	def get_name(self,user:str):
		self.qb_init().filter(query="User=%s",parms=[user])
		return Utils.first(self.select(),{})

class Clients(Mission_Model):
	table = "Clients"

	def add(self,data:dict) -> int:
		data["Created"] = IODate().full()
		data["Mission_ID"] = self.mission_id
		return super().add(data=data)

	def get(self,client_id:int) -> dict:
		self.qb = self.qb_init(fields="*,ID as Client_ID")
		self.qb.filter(query="ID = %s",parms=[client_id])
		data = dict(self.select_one()) or {}
		data["Meals"] = list(Meals().get_client(client_id))
		data["Food"] = list(Food().get_client(client_id))
		data["Showers"] = list(Showers().get_client(client_id))
		data["Clothing"] = list(Clothing().get_client(client_id))
		data["Notes"] = list(Notes().get_client(client_id))
		data["Referrals"] = list(Referrals().get_client(client_id))
		data["Surplus"] = list(Surplus().get_client(client_id))
		data["Trespass"] = None

		for note in data.get("Notes") or []:
			Utils.Debug(title="Note",data=note)
			if note.get("Trespass"):
				data["Trespass"] = note
				break

		Utils.Debug(title="Get Client",data=data)
		return data

	def search(self,query:str) -> list:
		self.qb = self.qb_init()
		self.qb.filter(query="Name like %%s%",parms=[query])
		return self.select() or []

	def all(self,name:str="") -> list:
		self.qb = self.qb_init()
		if name:
			name = f'%{name}%'
			self.qb.filter(query="Name like %s",parms=[name])

		self.qb.order(field="Name")
		return list(self.select())

	def delete(self,id:int) -> bool:
		return bool(super().delete({"ID":id}))

	def summary(self) -> dict:
		if self._start:
			start = IODate(self._start)
			t_days = int(IODate(self._start).daysFromNow(True))
		else:
			t_days = 30
			start = IODate(-t_days)

		pdays = (t_days*2)+1
		end = IODate()
		pstart = IODate(-pdays)
		pend = IODate(-(t_days+1))
		guests = {}
		meals = {}
		showers = {}
		food = {}
		clothing = {}
		compare = {}
		days = []
		ages = []
		names = ["Showers","Food","Clothing","Meals"]
		sdata = {"Guests":{},"Meals":{},"Clothing":{},"Showers":{}}
		summary = {"current":{},"previous":{}}
		for name in names:
			sdata.setdefault(name,{})
			for row in globals()[name]().all(query="date(Timestamp)>=%s and date(Timestamp)<=%s",parms=[str(pstart),str(end)]):
				dt = IODate(row.get("Timestamp"))
				t_id = dt.ym

				age = abs((end - dt).days)
				ages.append(age)
				if age <= 30:
					summary["current"].setdefault(name,0)
					summary["current"][name]+=1
				elif age >= 31 and age <= 61:
					summary["previous"].setdefault(name,0)
					summary["previous"][name]+=1

				sdata["Guests"].setdefault(t_id,[])
				sdata["Guests"][t_id].append(row.get("Client_ID"))

				sdata[name].setdefault(t_id,0)
				sdata[name][t_id]+=1

				dt_id = dt.str()
				compare.setdefault(dt_id,{})
				compare[dt_id].setdefault(name,0)
				compare[dt_id][name]+=1

				days.append(dt_id)

		days = sorted(list(set(days)))

		for d in days:
			for name in names:
				compare[d].setdefault(name,0)

		for m in guests:
			guests[m] = len(list(set(guests[m])))

		data = {
			"range":{"start":start,"end":end,"pstart":pstart,"pend":pend,"tdays":t_days},
			"days":days,
			"guests":guests,
			"clothing":clothing,
			"meals":meals,
			"showers":showers,
			"food":food,
			"compare":compare,
			"ages":ages
		}
		data["current"] = summary.get("current")
		data["previous"] = summary.get("previous")
		data["delta"] = {}
		for name,qty in summary.get("current",{}).items():
			try:
				data["delta"][name] = round(Utils.delta(start=summary.get("previous",{}).get(name),end=summary.get("current",{}).get(name)) * 100)
			except:
				pass
		data["all_data"] = sdata
		return data

class Meals(Mission_Model):
	table = "Meals"

	def add(self,data:dict) -> int:
		data["Timestamp"] = IODate().full()
		data["Mission_ID"] = self.mission_id
		return super().add(data=data)

	def get_client(self,client_id:int) -> list:
		self.qb = self.qb_init()
		self.qb.filter(query="Client_ID=%s",parms=[client_id])
		self.qb.order(field="Timestamp",direction="desc")
		return self.check_recent(self.select())

	def log(self,days:int=30) -> list:
		start = IODate(Utils.neg(days)).str()
		self.qb = self.qb_init()
		self.qb.join("join Clients on Meals.Client_ID=Clients.ID")
		self.qb.fields(fields="Clients.Name,Meals.*")
		self.qb.filter(query="date(Timestamp)>=%s",parms=[start])
		self.qb.order(field="Meals.Timestamp",direction="desc")
		data = []
		for row in self.select():
			data.append({
				"Name":row.get("Name"),
				"Timestamp":row.get("Timestamp"),
				"Service":"Meals",
				"Detail":f'Seconds: {bool(row.get("Seconds"))}'
			})
		return data

	def all(self,query:str="",parms:Optional[list]=None) -> list:
		self.qb = self.qb_init()
		self.qb.fields(f"{self.table}.*, Clients.Name as Name")
		Utils.Debug(title=f"table {self.table}",data=self.q.get("Clients"))
		self.qb.join(self.q.get("Clients"))
		if query:
			self.qb.filter(query=query,parms=parms)

		self.qb.order(field="Timestamp",direction="desc")
		return self.select()

class Showers(Mission_Model):
	table = "Showers"
	def add(self,data:dict) -> int:
		data["Timestamp"] = IODate().full()
		data["Mission_ID"] = self.mission_id
		return super().add(data=data)

	def get_client(self,client_id:int) -> list:
		self.qb = self.qb_init()
		self.qb.filter(query="Client_ID=%s",parms=[client_id])
		self.qb.order(field="Timestamp",direction="desc")
		return self.check_recent(self.select())

	def log(self,days:int=30) -> list:
		start = IODate(Utils.neg(days)).str()
		self.qb = self.qb_init()
		self.qb.join("join Clients on Showers.Client_ID=Clients.ID")
		self.qb.fields(fields="Clients.Name,Showers.*")
		self.qb.filter(query="date(Timestamp)>=%s",parms=[start])
		self.qb.order(field="Timestamp",direction="desc")

		data = []
		for row in self.select():
			data.append({
				"Name":row.get("Name"),
				"Timestamp":row.get("Timestamp"),
				"Service":"Showers",
				"Detail":f'Clothing: {bool(row.get("Clothing"))} Cleaned Up: {bool(row.get("Cleaned"))} Special: {row.get("Special")}'
			})
		return data

	def all(self,query:str="",parms:Optional[list]=None) -> list:
		self.qb = self.qb_init()
		self.qb.fields(f"{self.table}.*, Clients.Name as Name")
		self.qb.join(self.q.get("Clients"))
		if query:
			self.qb.filter(query=query,parms=parms)

		self.qb.order(field="Timestamp",direction="desc")

		return list(self.select())

class Food(Mission_Model):
	table = "Food"

	def add(self,data:dict) -> int:
		data["Timestamp"] = IODate().full()
		data["Mission_ID"] = self.mission_id
		return super().add(data=data)

	def get_client(self,client_id:int) -> list:
		self.qb = self.qb_init()
		self.qb.filter(query="Client_ID=%s",parms=[client_id])
		self.qb.order("Timestamp","desc")
		return self.check_recent(self.select())

	def log(self,days:int=30) -> list:
		start = IODate(Utils.neg(days)).str()
		self.qb = self.qb_init()
		self.qb.join("join Clients on Food.Client_ID=Clients.ID")
		self.qb.fields(fields="Clients.Name,Food.*")
		self.qb.filter(query="date(Timestamp)>=%s",parms=[start])
		self.qb.order(field="Timestamp",direction="desc")

		data = []
		for row in self.select():
			data.append({
				"Name":row.get("Name"),
				"Timestamp":row.get("Timestamp"),
				"Service":"Food",
				"Detail":f'{row.get("Note")}'
			})
		return data

	def all(self,query:str="",parms:Optional[list]=None) -> Generator:
		self.qb = self.qb_init()
		self.qb.fields(f"{self.table}.*, Clients.Name as Name")
		self.qb.join(join=str(self.q.get("Clients")))
		if query:
			self.qb.filter(query=query,parms=parms)

		self.qb.order(field="Timestamp",direction="desc")
		return self.select()

class Referrals(Mission_Model):
	table = "Referrals"

	def add(self,data:dict) -> int:
		data["Timestamp"] = IODate().full()
		data["Mission_ID"] = self.mission_id
		data["User_ID"] = self.user_id
		if not data.get("Return"):
			data["Return"] = None
		return super().add(data=data)


class Surplus(Mission_Model):
	table = "Surplus"

	def add(self,data:dict) -> int:
		data["Timestamp"] = IODate().full()
		data["Mission_ID"] = self.mission_id
		data["User_ID"] = self.user_id
		return super().add(data=data)

	def log(self,days:int=30) -> list:
		start = IODate(Utils.neg(days)).str()
		self.qb = self.qb_init()
		self.qb.join("join Clients on Surplus.Client_ID=Clients.ID")
		self.qb.fields(fields="Clients.Name,Surplus.*")
		self.qb.filter(query="date(Timestamp)>=%s",parms=[start])
		self.qb.order(field="Timestamp",direction="desc")

		data = []
		for row in self.select():
			data.append({
				"Name":row.get("Name"),
				"Timestamp":row.get("Timestamp"),
				"Service":"Surplus",
				"Detail":row.get("Item")
			})
		return data

class Clothing(Mission_Model):
	table = "Clothing"

	def add(self,data:dict) -> int:
		data["Timestamp"] = IODate().full()
		data["Mission_ID"] = self.mission_id
		data["User_ID"] = self.user_id
		return super().add(data=data)


	def log(self,days:int=30) -> list:
		start = IODate(Utils.neg(days)).str()
		self.qb = self.qb_init()
		self.qb.join("join Clients on Clothing.Client_ID=Clients.ID")
		self.qb.fields(fields="Clients.Name,Clothing.*")
		self.qb.filter(query="date(Timestamp)>=%s",parms=[start])
		self.qb.order(field="Timestamp",direction="desc")
		data = []
		for row in self.select():
			data.append({
				"Name":row.get("Name"),
				"Timestamp":row.get("Timestamp"),
				"Service":"Clothing",
				"Detail":f'{row.get("In")} => {row.get("Out")}'
			})
		return data

	def all(self,query:str="",parms:Optional[list]=None) -> list:
		self.qb = self.qb_init()
		self.qb.fields(f"{self.table}.*, Clients.Name as Name")
		self.qb.join(self.q.get("Clients"))
		if query:
			self.qb.filter(query=query,parms=parms)

		self.qb.order(field="Timestamp",direction="desc")
		return self.select()

class Notes(Mission_Model):
	table = "Notes"
	q = {
		"User":"join Users on Users.ID = Notes.User_ID",
		"Client":"join Clients on Clients.ID = Notes.Client_ID"
	}

	def add(self,data:dict) -> int:
		data["Timestamp"] = IODate().full()
		data["Mission_ID"] = self.mission_id
		data["User_ID"] = getUserID()
		data["Trespass"] = data.get("Trespass") or None
		return super().add(data=data)

	def get_client(self,client_id:int) -> list:
		self.qb = self.qb_init()
		self.qb.fields(fields="Notes.*,Users.Name as User")
		self.qb.filter(query="Client_ID=%s",parms=[client_id])
		self.qb.order(field="Timestamp",direction="desc")
		self.qb.join(join=self.q.get("User",""))
		Debug(title="Query Cache",data=self.q)
		return self.check_recent(list(self.select()))

	def all(self) -> list:
		self.qb = self.qb_init()
		self.qb.fields("Notes.*,Users.Name as User")
		self.qb.join(join=self.q.get("User",""))
		self.qb.join(join=self.q.get("Client",""))
		self.qb.order("date(Timestamp) desc")
		return list(self.select())

class Options(Model):
	table = "Options"

	def mk_row(self,data) -> dict:
		data["options"] = Utils.getJSON(data.get("options")) if isinstance(data.get("options"),str) else data.get("options") or []
		return row(data)

	def cache(self) -> dict:
		self.qb = self.qb_init()
		return {row.get("name"):row.get("options") for row in self.select()}

class Dashboard(Model):
	def log(self,days:int=30) -> list:
		data = []
		data += list(Meals().log(days=days))
		data += list(Surplus().log(days=days))
		data += list(Clothing().log(days=days))
		data += list(Food().log(days=days))
		data += list(Showers().log(days=days))

		return data

	def summary(self) -> dict:
		data = {
			"Guest_Total":0,
			"Monthly":Clients().summary(),
			"Meals":Meals().by_day(),
			"Showers":Showers().by_day(),
			"Clothing":Clothing().by_day(),
			"Food":Food().by_day(),
			"Surplus":Surplus().by_day(),
			"Log":list(self.log()),
			"Options":Options().cache()
		}
		return data

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
