# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt


import json

import frappe
from frappe import _, scrub
from frappe.utils import flt


def execute(filters=None):
	return IssueSummary(filters).run()


class IssueSummary:
	def __init__(self, filters=None):
		self.filters = frappe._dict(filters or {})

	def run(self):
		self.get_columns()
		self.get_data()
		self.get_chart_data()
		self.get_report_summary()

		return self.columns, self.data, None, self.chart, self.report_summary

	def get_columns(self):
		self.columns = []

		if self.filters.based_on == "Customer":
			self.columns.append(
				{
					"label": _("Customer"),
					"options": "Customer",
					"fieldname": "customer",
					"fieldtype": "Link",
					"width": 200,
				}
			)

		elif self.filters.based_on == "Assigned To":
			self.columns.append(
				{
					"label": _("User"),
					"fieldname": "user",
					"fieldtype": "Link",
					"options": "User",
					"width": 200,
				}
			)

		elif self.filters.based_on == "Issue Type":
			self.columns.append(
				{
					"label": _("Issue Type"),
					"fieldname": "issue_type",
					"fieldtype": "Link",
					"options": "Issue Type",
					"width": 200,
				}
			)

		elif self.filters.based_on == "Issue Priority":
			self.columns.append(
				{
					"label": _("Issue Priority"),
					"fieldname": "priority",
					"fieldtype": "Link",
					"options": "Issue Priority",
					"width": 200,
				}
			)

		self.statuses = ["Open", "Replied", "On Hold", "Resolved", "Closed"]
		for status in self.statuses:
			self.columns.append(
				{"label": _(status), "fieldname": scrub(status), "fieldtype": "Int", "width": 80}
			)

		self.columns.append(
			{"label": _("Total Issues"), "fieldname": "total_issues", "fieldtype": "Int", "width": 100}
		)

		self.sla_status_map = {
			"SLA Failed": "failed",
			"SLA Fulfilled": "fulfilled",
			"First Response Due": "first_response_due",
			"Resolution Due": "resolution_due",
		}

		for label, fieldname in self.sla_status_map.items():
			self.columns.append({"label": _(label), "fieldname": fieldname, "fieldtype": "Int", "width": 100})

		self.metrics = [
			"Avg First Response Time",
			"Avg Response Time",
			"Avg Hold Time",
			"Avg Resolution Time",
			"Avg User Resolution Time",
		]

		for metric in self.metrics:
			self.columns.append(
				{"label": _(metric), "fieldname": scrub(metric), "fieldtype": "Duration", "width": 170}
			)

	def get_data(self):
		self.get_issues()
		self.get_rows()

	def get_issues(self):
		filters = self.get_common_filters()
		self.field_map = {
			"Customer": "customer",
			"Issue Type": "issue_type",
			"Issue Priority": "priority",
			"Assigned To": "_assign",
		}

		self.entries = frappe.db.get_all(
			"Issue",
			fields=[
				self.field_map.get(self.filters.based_on),
				"name",
				"opening_date",
				"status",
				"avg_response_time",
				"first_response_time",
				"total_hold_time",
				"user_resolution_time",
				"resolution_time",
				"agreement_status",
			],
			filters=filters,
		)

	def get_common_filters(self):
		filters = {}
		filters["opening_date"] = ("between", [self.filters.from_date, self.filters.to_date])

		if self.filters.get("assigned_to"):
			filters["_assign"] = ("like", "%" + self.filters.get("assigned_to") + "%")

		for entry in ["company", "status", "priority", "customer", "project"]:
			if self.filters.get(entry):
				filters[entry] = self.filters.get(entry)

		return filters

	def get_rows(self):
		self.data = []
		self.get_summary_data()

		for entity, data in self.issue_summary_data.items():
			if self.filters.based_on == "Customer":
				row = {"customer": entity}
			elif self.filters.based_on == "Assigned To":
				row = {"user": entity}
			elif self.filters.based_on == "Issue Type":
				row = {"issue_type": entity}
			elif self.filters.based_on == "Issue Priority":
				row = {"priority": entity}

			for status in self.statuses:
				count = flt(data.get(status, 0.0))
				row[scrub(status)] = count

			row["total_issues"] = data.get("total_issues", 0.0)

			for sla_status in self.sla_status_map.values():
				value = flt(data.get(sla_status), 0.0)
				row[sla_status] = value

			for metric in self.metrics:
				value = flt(data.get(scrub(metric)), 0.0)
				row[scrub(metric)] = value

			self.data.append(row)

	def get_summary_data(self):
		self.issue_summary_data = frappe._dict()

		for d in self.entries:
			status = d.status
			agreement_status = scrub(d.agreement_status)

			if self.filters.based_on == "Assigned To":
				if d._assign:
					for entry in json.loads(d._assign):
						self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault(status, 0.0)
						self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault(
							agreement_status, 0.0
						)
						self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault(
							"total_issues", 0.0
						)
						self.issue_summary_data[entry][status] += 1
						self.issue_summary_data[entry][agreement_status] += 1
						self.issue_summary_data[entry]["total_issues"] += 1

			else:
				field = self.field_map.get(self.filters.based_on)
				value = d.get(field)
				if not value:
					value = _("Not Specified")

				self.issue_summary_data.setdefault(value, frappe._dict()).setdefault(status, 0.0)
				self.issue_summary_data.setdefault(value, frappe._dict()).setdefault(agreement_status, 0.0)
				self.issue_summary_data.setdefault(value, frappe._dict()).setdefault("total_issues", 0.0)
				self.issue_summary_data[value][status] += 1
				self.issue_summary_data[value][agreement_status] += 1
				self.issue_summary_data[value]["total_issues"] += 1

		self.get_metrics_data()

	def get_metrics_data(self):
		issues = []

		metrics_list = [
			"avg_response_time",
			"avg_first_response_time",
			"avg_hold_time",
			"avg_resolution_time",
			"avg_user_resolution_time",
		]

		for entry in self.entries:
			issues.append(entry.name)

		field = self.field_map.get(self.filters.based_on)

		if issues:
			if self.filters.based_on == "Assigned To":
				assignment_map = frappe._dict()
				for d in self.entries:
					if d._assign:
						for entry in json.loads(d._assign):
							for metric in metrics_list:
								self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault(
									metric, 0.0
								)

							self.issue_summary_data[entry]["avg_response_time"] += (
								d.get("avg_response_time") or 0.0
							)
							self.issue_summary_data[entry]["avg_first_response_time"] += (
								d.get("first_response_time") or 0.0
							)
							self.issue_summary_data[entry]["avg_hold_time"] += d.get("total_hold_time") or 0.0
							self.issue_summary_data[entry]["avg_resolution_time"] += (
								d.get("resolution_time") or 0.0
							)
							self.issue_summary_data[entry]["avg_user_resolution_time"] += (
								d.get("user_resolution_time") or 0.0
							)

							if not assignment_map.get(entry):
								assignment_map[entry] = 0
							assignment_map[entry] += 1

				for entry in assignment_map:
					for metric in metrics_list:
						self.issue_summary_data[entry][metric] /= flt(assignment_map.get(entry))

			else:
				data = frappe.db.sql(
					f"""
					SELECT
						{field}, AVG(first_response_time) as avg_frt,
						AVG(avg_response_time) as avg_resp_time,
						AVG(total_hold_time) as avg_hold_time,
						AVG(resolution_time) as avg_resolution_time,
						AVG(user_resolution_time) as avg_user_resolution_time
					FROM `tabIssue`
					WHERE
						name IN %(issues)s
					GROUP BY {field}
				""",
					{"issues": issues},
					as_dict=1,
				)

				for entry in data:
					value = entry.get(field)
					if not value:
						value = _("Not Specified")

					for metric in metrics_list:
						self.issue_summary_data.setdefault(value, frappe._dict()).setdefault(metric, 0.0)

					self.issue_summary_data[value]["avg_response_time"] = entry.get("avg_resp_time") or 0.0
					self.issue_summary_data[value]["avg_first_response_time"] = entry.get("avg_frt") or 0.0
					self.issue_summary_data[value]["avg_hold_time"] = entry.get("avg_hold_time") or 0.0
					self.issue_summary_data[value]["avg_resolution_time"] = (
						entry.get("avg_resolution_time") or 0.0
					)
					self.issue_summary_data[value]["avg_user_resolution_time"] = (
						entry.get("avg_user_resolution_time") or 0.0
					)

	def get_chart_data(self):
		self.chart = []

		labels = []
		open_issues = []
		replied_issues = []
		on_hold_issues = []
		resolved_issues = []
		closed_issues = []

		entity = self.filters.based_on
		entity_field = self.field_map.get(entity)
		if entity == "Assigned To":
			entity_field = "user"

		for entry in self.data:
			labels.append(entry.get(entity_field))
			open_issues.append(entry.get("open"))
			replied_issues.append(entry.get("replied"))
			on_hold_issues.append(entry.get("on_hold"))
			resolved_issues.append(entry.get("resolved"))
			closed_issues.append(entry.get("closed"))

		self.chart = {
			"data": {
				"labels": labels[:30],
				"datasets": [
					{"name": "Open", "values": open_issues[:30]},
					{"name": "Replied", "values": replied_issues[:30]},
					{"name": "On Hold", "values": on_hold_issues[:30]},
					{"name": "Resolved", "values": resolved_issues[:30]},
					{"name": "Closed", "values": closed_issues[:30]},
				],
			},
			"type": "bar",
			"barOptions": {"stacked": True},
		}

	def get_report_summary(self):
		self.report_summary = []

		open_issues = 0
		replied = 0
		on_hold = 0
		resolved = 0
		closed = 0

		for entry in self.data:
			open_issues += entry.get("open")
			replied += entry.get("replied")
			on_hold += entry.get("on_hold")
			resolved += entry.get("resolved")
			closed += entry.get("closed")

		self.report_summary = [
			{
				"value": open_issues,
				"indicator": "Red",
				"label": _("Open"),
				"datatype": "Int",
			},
			{
				"value": replied,
				"indicator": "Grey",
				"label": _("Replied"),
				"datatype": "Int",
			},
			{
				"value": on_hold,
				"indicator": "Grey",
				"label": _("On Hold"),
				"datatype": "Int",
			},
			{
				"value": resolved,
				"indicator": "Green",
				"label": _("Resolved"),
				"datatype": "Int",
			},
			{
				"value": closed,
				"indicator": "Green",
				"label": _("Closed"),
				"datatype": "Int",
			},
		]
