from decimal import Decimal
import datetime
from dateutil.relativedelta import relativedelta

async def orders_by_employee(cursor, options):
	errors = {}

	if options["date"]["from"] == '' or options["date"]["to"] == '':
		errors["date"] = "Please enter order date range."

	if "category" not in options:
		errors["category"] = "Please choose at least one product category."

	if len(errors) > 0:
		return {'status': 'error', 'errors': errors}

	query = """\
	SELECT
		CONCAT(E.FirstName, ' ', E.LastName) AS name,
		O.OrderID,
		O.OrderDate,
		C.CustomerName,
		inter.value
	FROM Employees AS E
	INNER JOIN Orders AS O ON E.EmployeeID = O.EmployeeID
	LEFT JOIN Customers AS C on O.CustomerID = C.CustomerID
	INNER JOIN (
		SELECT
			OD.OrderID,
			SUM(OD.Quantity * P.Price) AS value
		FROM OrderDetails AS OD
		LEFT JOIN Products AS P ON OD.ProductID = P.ProductID
		WHERE P.CategoryID IN ({})
		GROUP BY OD.OrderID
	) AS inter ON O.OrderID = inter.OrderID
	WHERE O.OrderDate BETWEEN %s AND %s;""".format(",".join(['%s'] * len(options["category"])))
	
	await cursor.execute(query, [*options["category"].keys(), options["date"]["from"], options["date"]["to"]])
	rows = await cursor.fetchall()

	results = []
	for row in rows:
		row = list(row)
		row[2] = row[2].date().strftime('%Y-%m-%d')
		results.append(row)

	return {'status': 'ok', 'rows': results, 'period': f'{options["date"]["from"]} - {options["date"]["to"]}'}

async def orders_by_location(cursor, options):
	errors = {}

	if "location" not in options:
		errors["location"] = "Please choose how to group locations."

	if options["date"]["from"] == '' or options["date"]["to"] == '':
		errors["date"] = "Please enter order date range."

	if "category" not in options:
		errors["category"] = "Please choose at least one product category."

	if len(errors) > 0:
		return {'status': 'error', 'errors': errors}

	location_query = {
		'city': "CONCAT(C.country, ' - ', C.city)",
		'country': "C.country"
	}
	query = """\
	SELECT
		{},
		C.CustomerName,
		O.OrderID,
		O.OrderDate,
		inter.value
	FROM Orders AS O
	LEFT JOIN Customers AS C ON O.CustomerID = C.CustomerID
	INNER JOIN (
		SELECT
			OD.OrderID,
			SUM(OD.quantity * P.Price) AS value
		FROM OrderDetails AS OD
		LEFT JOIN Products AS P ON OD.ProductID = P.ProductID
		WHERE P.CategoryID IN ({})
		GROUP BY OD.OrderID
	) AS inter ON O.OrderID = inter.OrderID
	WHERE O.OrderDate BETWEEN %s AND %s;\
	""".format(location_query[options["location"]], ",".join(['%s'] * len(options["category"])))
	
	await cursor.execute(query, [*options["category"].keys(), options["date"]["from"], options["date"]["to"]])
	rows = await cursor.fetchall()

	results = []
	for row in rows:
		row = list(row)
		row[3] = row[3].date().strftime('%Y-%m-%d')
		results.append(row)

	return {'status': 'ok', 'rows': results, 'period': f'{options["date"]["from"]} - {options["date"]["to"]}'}

async def orders_by_product(cursor, options):
	errors = {}

	if options["date"]["from"] == '' or options["date"]["to"] == '':
		errors["date"] = "Please enter order date range."

	if "category" not in options:
		errors["category"] = "Please choose at least one product category."

	if len(errors) > 0:
		return {'status': 'error', 'errors': errors}

	query = """\
	SELECT
		P.ProductName,
		C.CustomerName,
		O.OrderID,
		O.OrderDate,
		OD.Quantity,
		OD.Quantity * P.Price AS value
	FROM OrderDetails AS OD
	LEFT JOIN Orders AS O ON OD.OrderID = O.OrderID
	LEFT JOIN Products AS P ON OD.ProductID = P.ProductID
	LEFT JOIN Customers AS C ON O.CustomerID = C.CustomerID
	WHERE P.CategoryID IN ({})
	AND O.OrderDate BETWEEN %s AND %s;\
	""".format(",".join(['%s'] * len(options["category"])))

	await cursor.execute(query, [*options["category"].keys(), options["date"]["from"], options["date"]["to"]])
	rows = await cursor.fetchall()

	results = []
	for row in rows:
		row = list(row)
		row[3] = row[3].date().strftime('%Y-%m-%d')
		results.append(row)

	return {'status': 'ok', 'rows': results, 'period': f'{options["date"]["from"]} - {options["date"]["to"]}'}

async def orders_by_supplier(cursor, options):
	errors = {}

	if options["date"]["from"] == '' or options["date"]["to"] == '':
		errors["date"] = "Please enter order date range."

	if "category" not in options:
		errors["category"] = "Please choose at least one product category."

	if len(errors) > 0:
		return {'status': 'error', 'errors': errors}

	query = """\
	SELECT
		S.suppliername,
		P.ProductName,
		C.CustomerName,
		O.OrderID,
		O.OrderDate,
		OD.Quantity,
		OD.Quantity * P.Price AS value
	FROM OrderDetails AS OD
	LEFT JOIN Orders AS O ON OD.OrderID = O.OrderID
	LEFT JOIN Products AS P ON OD.ProductID = P.ProductID
	LEFT JOIN Customers AS C ON O.CustomerID = C.CustomerID
	LEFT JOIN Suppliers AS S ON P.SupplierID = S.SupplierID
	WHERE P.CategoryID IN ({})
	AND O.OrderDate BETWEEN %s AND %s;\
	""".format(",".join(['%s'] * len(options["category"])))

	await cursor.execute(query, [*options["category"].keys(), options["date"]["from"], options["date"]["to"]])
	rows = await cursor.fetchall()

	results = []
	for row in rows:
		row = list(row)
		row[4] = row[4].date().strftime('%Y-%m-%d')
		results.append(row)

	return {'status': 'ok', 'rows': results, 'period': f'{options["date"]["from"]} - {options["date"]["to"]}'}

async def pivot_table(cursor, options):
	errors = {}
	valid_rows    = set(['month', 'year', 'employee', 'shipper', 'customer', 'supplier', 'category', 'product'])
	valid_columns = set(['month', 'year', 'employee', 'shipper', 'category'])
	valid_values  = set(['sales', 'qty'])

	if options["date"]["from"] == '' or options["date"]["to"] == '':
		errors["date"] = "Please enter order date range."

	if "category" not in options:
		errors["category"] = "Please choose at least one product category."

	if options["rows"] not in valid_rows:
		errors["rows"] = "Please choose the data to display as rows."

	if options["columns"] not in valid_columns:
		options["columns"] = "none"

	if options["rows"] == options["columns"]:
		errors["columns"] = "Columns and Rows should be different."

	if options["values"] not in valid_values:
		errors["values"] = "Value selection invalid."

	if len(errors) > 0:
		return {'status': 'error', 'errors': errors}

	# SELECT, GROUP BY, JOIN
	parts = {
		'none'    : ("", "", ""),
		'month'   : ("CONCAT(YEAR(O.OrderDate), '-', LPAD(MONTH(O.OrderDate), 2, '0')) AS month", "month", ""),
		'year'    : ("YEAR(O.OrderDate) AS year", "year", ""),
		'employee': ("CONCAT(E.FirstName, ' ', E.LastName) AS employeename",
					"employeename",
					"LEFT JOIN Employees AS E ON O.EmployeeID = E.EmployeeID"),
		'shipper' : ("SH.ShipperName",
					"SH.ShipperName",
					"LEFT JOIN Shippers AS SH ON O.ShipperID = SH.ShipperID"),
		'customer': ("C.CustomerName",
					"C.CustomerName",
					"LEFT JOIN Customers AS C ON O.CustomerID = C.CustomerID"),
		'supplier': ("S.SupplierName",
					"S.SupplierName",
					"LEFT JOIN Suppliers AS S ON P.SupplierID = S.SupplierID"),
		'category': ("CA.CategoryName",
					"CA.CategoryName",
					"LEFT JOIN Categories AS CA ON P.CategoryID = CA.CategoryID"),
		'product' : ("P.ProductName", "P.ProductName", "")
	}

	values = {
		'sales' : 'SUM(OD.Quantity * P.Price)',
		'qty'   : 'SUM(OD.Quantity)'
	}

	query = """
	SELECT
		{rows} {columns} {values}
	FROM OrderDetails AS OD
	LEFT JOIN Orders AS O ON OD.OrderID = O.OrderID
	LEFT JOIN Products AS P ON OD.ProductID = P.ProductID
	{joins}
	WHERE P.CategoryID IN ({categories})
	AND O.OrderDate BETWEEN %s AND %s
	GROUP BY {grouprows}{groupcols}
	""".format(
		rows=f"{parts[options['rows']][0]},",
		columns=f"{parts[options['columns']][0]}," if options["columns"] != 'none' else '',
		values=f"{values[options['values']]}",
		joins=f"{parts[options['rows']][2]} {parts[options['columns']][2]}",
		categories=",".join(['%s'] * len(options["category"])),
		grouprows=parts[options['rows']][1],
		groupcols=', '+parts[options['columns']][1] if options["columns"] != 'none' else ''
	)

	await cursor.execute(query, [*options["category"].keys(), options["date"]["from"], options["date"]["to"]])
	response = await cursor.fetchall()

	if options["columns"] == 'none':
		results = [ list(row) for row in response ]
		value_lookup = {'sales': 'Order Value', 'qty': 'Item Quantity'}
		rv = {'status': 'ok', 'rows': results, 'columns': [{'name': options['rows'].capitalize()}, {'name': value_lookup[options["values"]], 'format': '$'}]}
		return rv
	else:
		results = []
		intermediate = {}
		cols = set()
		for row in response:
			if row[0] is None:
				continue
			cols.add(row[1])
			if row[0] not in intermediate:
				intermediate[row[0]] = {'Total': 0}
			assert row[1] not in intermediate[row[0]]
			intermediate[row[0]][row[1]] = row[2]
			intermediate[row[0]]['Total'] += row[2]
		cols = list(sorted(cols)) + ['Total']
		for row in sorted(intermediate):
			values = [row] + [ intermediate[row].get(col, '') for col in cols ]
			results.append(values)
		headings = [{'name': options['rows'].capitalize(), 'frozen': True}] + [ {'name': col, 'format': '$'} for col in cols ]
		headings[-1]['frozen'] = True
		return {'status': 'ok', 'rows': results, 'columns': headings, 'period': f'{options["date"]["from"]} - {options["date"]["to"]}'}
