# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import datetime
import json

import frappe
from frappe.utils import flt

from erpnext.support.doctype.issue_priority.test_issue_priority import make_priorities
from erpnext.support.doctype.service_level_agreement.service_level_agreement import (
	get_service_level_agreement_fields,
)
from erpnext.tests.utils import ERPNextTestSuite


class TestServiceLevelAgreement(ERPNextTestSuite):
	def test_service_level_agreement(self):
		# Default Service Level Agreement
		create_default_service_level_agreement = create_service_level_agreement(
			default_service_level_agreement=1,
			holiday_list="__Test Holiday List",
			entity_type=None,
			entity=None,
			response_time=14400,
			resolution_time=21600,
		)

		get_default_service_level_agreement = get_service_level_agreement(default_service_level_agreement=1)

		self.assertEqual(
			create_default_service_level_agreement.name, get_default_service_level_agreement.name
		)
		self.assertEqual(
			create_default_service_level_agreement.entity_type,
			get_default_service_level_agreement.entity_type,
		)
		self.assertEqual(
			create_default_service_level_agreement.entity, get_default_service_level_agreement.entity
		)
		self.assertEqual(
			create_default_service_level_agreement.default_service_level_agreement,
			get_default_service_level_agreement.default_service_level_agreement,
		)

		# Service Level Agreement for Customer
		customer = create_customer()
		create_customer_service_level_agreement = create_service_level_agreement(
			default_service_level_agreement=0,
			holiday_list="__Test Holiday List",
			entity_type="Customer",
			entity=customer,
			response_time=7200,
			resolution_time=10800,
		)
		get_customer_service_level_agreement = get_service_level_agreement(
			entity_type="Customer", entity=customer
		)

		self.assertEqual(
			create_customer_service_level_agreement.name, get_customer_service_level_agreement.name
		)
		self.assertEqual(
			create_customer_service_level_agreement.entity_type,
			get_customer_service_level_agreement.entity_type,
		)
		self.assertEqual(
			create_customer_service_level_agreement.entity, get_customer_service_level_agreement.entity
		)
		self.assertEqual(
			create_customer_service_level_agreement.default_service_level_agreement,
			get_customer_service_level_agreement.default_service_level_agreement,
		)

		# Service Level Agreement for Customer Group
		customer_group = create_customer_group()
		create_customer_group_service_level_agreement = create_service_level_agreement(
			default_service_level_agreement=0,
			holiday_list="__Test Holiday List",
			entity_type="Customer Group",
			entity=customer_group,
			response_time=7200,
			resolution_time=10800,
		)
		get_customer_group_service_level_agreement = get_service_level_agreement(
			entity_type="Customer Group", entity=customer_group
		)

		self.assertEqual(
			create_customer_group_service_level_agreement.name,
			get_customer_group_service_level_agreement.name,
		)
		self.assertEqual(
			create_customer_group_service_level_agreement.entity_type,
			get_customer_group_service_level_agreement.entity_type,
		)
		self.assertEqual(
			create_customer_group_service_level_agreement.entity,
			get_customer_group_service_level_agreement.entity,
		)
		self.assertEqual(
			create_customer_group_service_level_agreement.default_service_level_agreement,
			get_customer_group_service_level_agreement.default_service_level_agreement,
		)

		# Service Level Agreement for Territory
		territory = create_territory()
		create_territory_service_level_agreement = create_service_level_agreement(
			default_service_level_agreement=0,
			holiday_list="__Test Holiday List",
			entity_type="Territory",
			entity=territory,
			response_time=7200,
			resolution_time=10800,
		)
		get_territory_service_level_agreement = get_service_level_agreement(
			entity_type="Territory", entity=territory
		)

		self.assertEqual(
			create_territory_service_level_agreement.name, get_territory_service_level_agreement.name
		)
		self.assertEqual(
			create_territory_service_level_agreement.entity_type,
			get_territory_service_level_agreement.entity_type,
		)
		self.assertEqual(
			create_territory_service_level_agreement.entity, get_territory_service_level_agreement.entity
		)
		self.assertEqual(
			create_territory_service_level_agreement.default_service_level_agreement,
			get_territory_service_level_agreement.default_service_level_agreement,
		)

	def test_custom_field_creation_for_sla_on_standard_dt(self):
		# Default Service Level Agreement
		doctype = "Lead"
		lead_sla = create_service_level_agreement(
			default_service_level_agreement=1,
			holiday_list="__Test Holiday List",
			entity_type=None,
			entity=None,
			response_time=14400,
			resolution_time=21600,
			doctype=doctype,
			sla_fulfilled_on=[{"status": "Converted"}],
		)

		# check default SLA for lead
		default_sla = get_service_level_agreement(default_service_level_agreement=1, doctype=doctype)
		self.assertEqual(lead_sla.name, default_sla.name)

		# check SLA custom fields created for leads
		sla_fields = get_service_level_agreement_fields(doctype)

		for field in sla_fields:
			filters = {"dt": doctype, "fieldname": field.get("fieldname")}
			self.assertTrue(frappe.db.exists("Custom Field", filters))
			self.assertEqual(
				get_link_filters("Custom Field", filters),
				json.loads(field["link_filters"]) if field.get("link_filters") else None,
			)

	def test_docfield_creation_for_sla_on_custom_dt(self):
		doctype = create_custom_doctype()
		sla = create_service_level_agreement(
			default_service_level_agreement=1,
			holiday_list="__Test Holiday List",
			entity_type=None,
			entity=None,
			response_time=14400,
			resolution_time=21600,
			doctype=doctype.name,
		)

		# check default SLA for custom dt
		default_sla = get_service_level_agreement(default_service_level_agreement=1, doctype=doctype.name)
		self.assertEqual(sla.name, default_sla.name)

		# check SLA docfields created
		sla_fields = get_service_level_agreement_fields(doctype.name)

		for field in sla_fields:
			filters = {"fieldname": field.get("fieldname"), "parent": doctype.name}
			self.assertTrue(frappe.db.exists("DocField", filters))
			self.assertEqual(
				get_link_filters("DocField", filters),
				json.loads(field["link_filters"]) if field.get("link_filters") else None,
			)

	def test_reset_field_properties_does_not_clobber_other_doctypes_field(self):
		"""Two doctypes each get their own "service_level_agreement" custom field
		(same fieldname, different owning doctype). Updating the field on one of
		them must not clobber the other's, even though both share the fieldname
		(regression test for the fix in reset_field_properties, see PR #56954)."""
		doctype_a = create_custom_doctype("Test SLA Dt A")
		doctype_b = create_custom_doctype("Test SLA Dt B")

		for doctype in (doctype_a.name, doctype_b.name):
			create_service_level_agreement(
				default_service_level_agreement=1,
				holiday_list="__Test Holiday List",
				entity_type=None,
				entity=None,
				response_time=14400,
				resolution_time=21600,
				doctype=doctype,
			)

		def get_sla_field_link_filters(doctype):
			return get_link_filters("DocField", {"parent": doctype, "fieldname": "service_level_agreement"})

		self.assertEqual(
			get_sla_field_link_filters(doctype_a.name),
			[["Service Level Agreement", "document_type", "=", doctype_a.name]],
		)

		# The field on doctype_b already exists, so creating another, entity-specific
		# SLA for doctype_b takes the "update existing field" branch (reset_field_properties)
		# instead of creating a new field.
		customer = create_customer()
		create_service_level_agreement(
			default_service_level_agreement=0,
			holiday_list="__Test Holiday List",
			entity_type="Customer",
			entity=customer,
			response_time=7200,
			resolution_time=10800,
			doctype=doctype_b.name,
		)

		self.assertEqual(
			get_sla_field_link_filters(doctype_a.name),
			[["Service Level Agreement", "document_type", "=", doctype_a.name]],
		)
		self.assertEqual(
			get_sla_field_link_filters(doctype_b.name),
			[["Service Level Agreement", "document_type", "=", doctype_b.name]],
		)

	def test_sla_application(self):
		# Default Service Level Agreement
		doctype = "Lead"
		lead_sla = create_service_level_agreement(
			default_service_level_agreement=1,
			holiday_list="__Test Holiday List",
			entity_type=None,
			entity=None,
			response_time=14400,
			resolution_time=21600,
			doctype=doctype,
			sla_fulfilled_on=[{"status": "Converted"}],
		)
		# make lead with default SLA
		creation = datetime.datetime(2019, 3, 4, 12, 0)
		lead = make_lead(creation=creation, index=1, company="_Test Support SLA")

		self.assertEqual(lead.service_level_agreement, lead_sla.name)
		self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0))
		self.assertEqual(lead.sla_resolution_by, datetime.datetime(2019, 3, 4, 18, 0))

		frappe.flags.current_time = datetime.datetime(2019, 3, 4, 15, 0)
		lead.reload()
		lead.status = "Converted"
		lead.save()

		self.assertEqual(lead.agreement_status, "Fulfilled")

	def test_hold_time(self):
		doctype = "Lead"
		create_service_level_agreement(
			default_service_level_agreement=1,
			holiday_list="__Test Holiday List",
			entity_type=None,
			entity=None,
			response_time=14400,
			resolution_time=21600,
			doctype=doctype,
			sla_fulfilled_on=[{"status": "Converted"}],
			pause_sla_on=[{"status": "Replied"}],
		)

		creation = datetime.datetime(2020, 3, 4, 4, 0)
		lead = make_lead(creation, index=2, company="_Test Support SLA")

		frappe.flags.current_time = datetime.datetime(2020, 3, 4, 4, 15)
		lead.reload()
		lead.status = "Replied"
		lead.save()

		lead.reload()
		self.assertEqual(lead.on_hold_since, frappe.flags.current_time)

		frappe.flags.current_time = datetime.datetime(2020, 3, 4, 5, 5)
		lead.reload()
		lead.status = "Converted"
		lead.save()

		lead.reload()
		self.assertEqual(flt(lead.total_hold_time, 2), 3000)
		self.assertEqual(lead.sla_resolution_by, datetime.datetime(2020, 3, 4, 16, 50))

	def test_failed_sla_for_response_only(self):
		doctype = "Lead"
		create_service_level_agreement(
			default_service_level_agreement=1,
			holiday_list="__Test Holiday List",
			entity_type=None,
			entity=None,
			response_time=14400,
			doctype=doctype,
			sla_fulfilled_on=[{"status": "Replied"}],
			pause_sla_on=[],
			apply_sla_for_resolution=0,
		)

		creation = datetime.datetime(2019, 3, 4, 12, 0)
		lead = make_lead(creation=creation, index=1, company="_Test Support SLA")
		self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0))

		# failed with response time only
		frappe.flags.current_time = datetime.datetime(2019, 3, 4, 16, 5)
		lead.reload()
		lead.status = "Replied"
		lead.save()

		lead.reload()
		self.assertEqual(lead.agreement_status, "Failed")

	def test_fulfilled_sla_for_response_only(self):
		doctype = "Lead"
		lead_sla = create_service_level_agreement(
			default_service_level_agreement=1,
			holiday_list="__Test Holiday List",
			entity_type=None,
			entity=None,
			response_time=14400,
			doctype=doctype,
			sla_fulfilled_on=[{"status": "Replied"}],
			apply_sla_for_resolution=0,
		)

		# fulfilled with response time only
		creation = datetime.datetime(2019, 3, 4, 12, 0)
		lead = make_lead(creation=creation, index=2, company="_Test Support SLA")

		self.assertEqual(lead.service_level_agreement, lead_sla.name)
		self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0))

		frappe.flags.current_time = datetime.datetime(2019, 3, 4, 15, 30)
		lead.reload()
		lead.status = "Replied"
		lead.save()

		lead.reload()
		self.assertEqual(lead.agreement_status, "Fulfilled")

	def test_service_level_agreement_filters(self):
		doctype = "Lead"
		lead_sla = create_service_level_agreement(
			default_service_level_agreement=0,
			doctype=doctype,
			holiday_list="__Test Holiday List",
			entity_type=None,
			entity=None,
			condition='doc.utm_source == "Test Source"',
			response_time=14400,
			sla_fulfilled_on=[{"status": "Replied"}],
			apply_sla_for_resolution=0,
		)
		creation = datetime.datetime(2019, 3, 4, 12, 0)
		lead = make_lead(creation=creation, index=4, company="_Test Support SLA")
		applied_sla = frappe.db.get_value("Lead", lead.name, "service_level_agreement")
		self.assertFalse(applied_sla)

		source = frappe.new_doc(doctype="UTM Source")
		source.name = "Test Source"
		source.flags.name_set = True
		source.insert(ignore_if_duplicate=True)
		lead.utm_source = "Test Source"
		lead.save()
		applied_sla = frappe.db.get_value("Lead", lead.name, "service_level_agreement")
		self.assertEqual(applied_sla, lead_sla.name)

		# check if SLA is removed if condition fails
		lead.reload()
		lead.utm_source = None
		lead.save()
		applied_sla = frappe.db.get_value("Lead", lead.name, "service_level_agreement")
		self.assertFalse(applied_sla)


def get_link_filters(field_doctype, filters):
	value = frappe.db.get_value(field_doctype, filters, "link_filters")
	return json.loads(value) if value else None


def get_service_level_agreement(
	default_service_level_agreement=None, entity_type=None, entity=None, doctype="Issue"
):
	if default_service_level_agreement:
		filters = {
			"default_service_level_agreement": default_service_level_agreement,
			"document_type": doctype,
		}
	else:
		filters = {"entity_type": entity_type, "entity": entity}

	service_level_agreement = frappe.get_doc("Service Level Agreement", filters)
	return service_level_agreement


def create_service_level_agreement(
	default_service_level_agreement,
	holiday_list,
	response_time,
	entity_type,
	entity,
	resolution_time=0,
	doctype="Issue",
	condition="",
	sla_fulfilled_on=None,
	pause_sla_on=None,
	apply_sla_for_resolution=1,
	service_level=None,
	start_time="10:00:00",
	end_time="18:00:00",
):
	if pause_sla_on is None:
		pause_sla_on = []
	if sla_fulfilled_on is None:
		sla_fulfilled_on = []
	make_holiday_list()
	make_priorities()

	if not sla_fulfilled_on:
		sla_fulfilled_on = [{"status": "Resolved"}, {"status": "Closed"}]

	pause_sla_on = [{"status": "Replied"}] if doctype == "Issue" else pause_sla_on

	service_level_agreement = frappe._dict(
		{
			"doctype": "Service Level Agreement",
			"enabled": 1,
			"document_type": doctype,
			"service_level": service_level
			or "__Test {} SLA".format(entity_type if entity_type else "Default"),
			"default_service_level_agreement": default_service_level_agreement,
			"condition": condition,
			"default_priority": "Medium",
			"holiday_list": holiday_list,
			"entity_type": entity_type,
			"entity": entity,
			"start_date": frappe.utils.getdate(),
			"end_date": frappe.utils.add_to_date(frappe.utils.getdate(), days=100),
			"apply_sla_for_resolution": apply_sla_for_resolution,
			"priorities": [
				{
					"priority": "Low",
					"response_time": response_time,
					"resolution_time": resolution_time,
				},
				{
					"priority": "Medium",
					"response_time": response_time,
					"default_priority": 1,
					"resolution_time": resolution_time,
				},
				{
					"priority": "High",
					"response_time": response_time,
					"resolution_time": resolution_time,
				},
			],
			"sla_fulfilled_on": sla_fulfilled_on,
			"pause_sla_on": pause_sla_on,
			"support_and_resolution": [
				{
					"workday": "Monday",
					"start_time": start_time,
					"end_time": end_time,
				},
				{
					"workday": "Tuesday",
					"start_time": start_time,
					"end_time": end_time,
				},
				{
					"workday": "Wednesday",
					"start_time": start_time,
					"end_time": end_time,
				},
				{
					"workday": "Thursday",
					"start_time": start_time,
					"end_time": end_time,
				},
				{
					"workday": "Friday",
					"start_time": start_time,
					"end_time": end_time,
				},
			],
		}
	)

	filters = {
		"default_service_level_agreement": service_level_agreement.default_service_level_agreement,
		"service_level": service_level_agreement.service_level,
	}

	if not default_service_level_agreement:
		filters.update({"entity_type": entity_type, "entity": entity})

	sla = frappe.db.exists("Service Level Agreement", filters)
	if sla:
		frappe.delete_doc("Service Level Agreement", sla, force=1)

	return frappe.get_doc(service_level_agreement).insert(ignore_permissions=True, ignore_if_duplicate=True)


def create_customer():
	customer = frappe.get_doc(
		{
			"doctype": "Customer",
			"customer_name": "_Test Customer",
			"customer_group": "Commercial",
			"customer_type": "Individual",
			"territory": "Rest Of The World",
		}
	)
	if not frappe.db.exists("Customer", "_Test Customer"):
		customer.insert(ignore_permissions=True)
		return customer.name
	else:
		return frappe.db.exists("Customer", "_Test Customer")


def create_customer_group():
	customer_group = frappe.get_doc(
		{"doctype": "Customer Group", "customer_group_name": "_Test SLA Customer Group"}
	)

	if not frappe.db.exists("Customer Group", {"customer_group_name": "_Test SLA Customer Group"}):
		customer_group.insert()
		return customer_group.name
	else:
		return frappe.db.exists("Customer Group", {"customer_group_name": "_Test SLA Customer Group"})


def create_territory():
	territory = frappe.get_doc(
		{
			"doctype": "Territory",
			"territory_name": "_Test SLA Territory",
		}
	)

	if not frappe.db.exists("Territory", {"territory_name": "_Test SLA Territory"}):
		territory.insert()
		return territory.name
	else:
		return frappe.db.exists("Territory", {"territory_name": "_Test SLA Territory"})


def create_service_level_agreements_for_issues():
	create_service_level_agreement(
		default_service_level_agreement=1,
		holiday_list="__Test Holiday List",
		entity_type=None,
		entity=None,
		response_time=14400,
		resolution_time=21600,
	)

	create_customer()
	create_service_level_agreement(
		default_service_level_agreement=0,
		holiday_list="__Test Holiday List",
		entity_type="Customer",
		entity="_Test Customer",
		response_time=7200,
		resolution_time=10800,
	)

	create_customer_group()
	create_service_level_agreement(
		default_service_level_agreement=0,
		holiday_list="__Test Holiday List",
		entity_type="Customer Group",
		entity="_Test SLA Customer Group",
		response_time=7200,
		resolution_time=10800,
	)

	create_territory()
	create_service_level_agreement(
		default_service_level_agreement=0,
		holiday_list="__Test Holiday List",
		entity_type="Territory",
		entity="_Test SLA Territory",
		response_time=7200,
		resolution_time=10800,
	)

	create_service_level_agreement(
		default_service_level_agreement=0,
		holiday_list="__Test Holiday List",
		entity_type=None,
		entity=None,
		response_time=14400,
		resolution_time=21600,
		service_level="24-hour-SLA",
		start_time="00:00:00",
		end_time="23:59:59",
		condition="doc.issue_type == 'Critical'",
	)


def make_holiday_list():
	holiday_list = frappe.db.exists("Holiday List", "__Test Holiday List")
	if not holiday_list:
		holiday_list = frappe.get_doc(
			{
				"doctype": "Holiday List",
				"holiday_list_name": "__Test Holiday List",
				"from_date": "2019-01-01",
				"to_date": "2019-12-31",
				"holidays": [
					{"description": "Test Holiday 1", "holiday_date": "2019-03-05"},
					{"description": "Test Holiday 2", "holiday_date": "2019-03-07"},
					{"description": "Test Holiday 3", "holiday_date": "2019-02-11"},
				],
			}
		).insert()


def create_custom_doctype(name="Test SLA on Custom Dt"):
	if not frappe.db.exists("DocType", name):
		doc = frappe.get_doc(
			{
				"doctype": "DocType",
				"module": "Support",
				"custom": 1,
				"fields": [
					{"label": "Date", "fieldname": "date", "fieldtype": "Date"},
					{"label": "Description", "fieldname": "desc", "fieldtype": "Long Text"},
					{
						"label": "Email ID",
						"fieldname": "email_id",
						"fieldtype": "Link",
						"options": "Customer",
					},
					{
						"label": "Status",
						"fieldname": "status",
						"fieldtype": "Select",
						"options": "Open\nReplied\nClosed",
					},
				],
				"permissions": [{"role": "System Manager", "read": 1, "write": 1}],
				"name": name,
			}
		)
		doc.insert()
		return doc
	else:
		return frappe.get_doc("DocType", name)


def make_lead(creation=None, index=0, company=None):
	return frappe.get_doc(
		{
			"doctype": "Lead",
			"email_id": f"test_lead1@example{index}.com",
			"lead_name": f"_Test Lead {index}",
			"status": "Open",
			"creation": creation,
			"service_level_agreement_creation": creation,
			"priority": "Medium",
			"company": company,
		}
	).insert(ignore_permissions=True)
