# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt


import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, flt, getdate, now_datetime, nowdate

import erpnext
from erpnext.setup.doctype.employee.test_employee import make_employee
from erpnext.stock.doctype.delivery_note.delivery_note import make_delivery_trip
from erpnext.stock.doctype.delivery_trip.delivery_trip import (
	get_contact_and_address,
	get_driver_email,
	notify_customers,
)
from erpnext.tests.utils import create_test_contact_and_address


class TestDeliveryTrip(FrappeTestCase):
	def setUp(self):
		super().setUp()
		driver = create_driver()
		create_vehicle()
		create_delivery_notification()
		create_test_contact_and_address()
		address = create_address(driver)

		self.delivery_trip = create_delivery_trip(driver, address)

	def tearDown(self):
		frappe.db.sql("delete from `tabDriver`")
		frappe.db.sql("delete from `tabVehicle`")
		frappe.db.sql("delete from `tabEmail Template`")
		frappe.db.sql("delete from `tabDelivery Trip`")
		return super().tearDown()

	def test_delivery_trip_notify_customers(self):
		notify_customers(delivery_trip=self.delivery_trip.name)
		self.delivery_trip.load_from_db()
		self.assertEqual(self.delivery_trip.email_notification_sent, 1)

	def test_unoptimized_route_list_without_locks(self):
		route_list = self.delivery_trip.form_route_list(optimize=False)

		# Return a single list of destinations, from home address and back
		self.assertEqual(len(route_list), 1)
		self.assertEqual(len(route_list[0]), 4)

	def test_unoptimized_route_list_with_locks(self):
		self.delivery_trip.delivery_stops[0].locked = 1
		self.delivery_trip.save()
		route_list = self.delivery_trip.form_route_list(optimize=False)

		# Return a single list of destinations, from home address and back,
		# since the stops don't need to optimized and simple time
		# estimation is enough
		self.assertEqual(len(route_list), 1)
		self.assertEqual(len(route_list[0]), 4)

	def test_optimized_route_list_without_locks(self):
		route_list = self.delivery_trip.form_route_list(optimize=True)

		# Return a single list of destinations, from home address and back,
		# since the route doesn't have any locks to be optimized against
		self.assertEqual(len(route_list), 1)
		self.assertEqual(len(route_list[0]), 4)

	def test_optimized_route_list_with_locks(self):
		self.delivery_trip.delivery_stops[0].locked = 1
		self.delivery_trip.save()
		route_list = self.delivery_trip.form_route_list(optimize=True)

		# Return multiple route lists, taking the home address as start and end
		self.assertEqual(len(route_list), 2)
		self.assertEqual(len(route_list[0]), 2)  # [home_address, locked_stop]
		self.assertEqual(len(route_list[1]), 3)  # [locked_stop, second_stop, home_address]

	def test_delivery_trip_status_draft(self):
		self.assertEqual(self.delivery_trip.status, "Draft")

	def test_unfit_driver_cannot_submit(self):
		for status, expiry_date in (("Left", None), ("Suspended", None), ("Active", add_days(nowdate(), -1))):
			with self.subTest(status=status, expiry_date=expiry_date):
				frappe.db.set_value(
					"Driver", self.delivery_trip.driver, {"status": status, "expiry_date": expiry_date}
				)
				with self.assertRaises(frappe.ValidationError):
					self.delivery_trip.submit()
				self.delivery_trip.reload()

	def test_license_valid_on_departure_date_can_submit(self):
		frappe.db.set_value(
			"Driver", self.delivery_trip.driver, "expiry_date", getdate(self.delivery_trip.departure_time)
		)
		self.delivery_trip.submit()
		self.assertEqual(self.delivery_trip.docstatus, 1)

	def test_driver_email_respects_employee_access(self):
		user = f"driver-{frappe.generate_hash(length=10)}@example.com"
		employee = make_employee(user, company="_Test Company")
		driver = self.delivery_trip.driver
		frappe.db.set_value("Driver", driver, "employee", employee)
		frappe.db.set_value(
			"Employee", employee, {"company_email": None, "prefered_email": "private@example.com"}
		)

		reader = f"delivery-{frappe.generate_hash(length=10)}@example.com"
		frappe.get_doc(
			{"doctype": "User", "email": reader, "first_name": "Delivery", "send_welcome_email": 0}
		).insert().add_roles("Delivery User")
		with self.set_user(reader):
			self.assertFalse(frappe.has_permission("Employee", "read", doc=employee))
			self.assertEqual(get_driver_email(driver), {"email": None})
		self.assertEqual(get_driver_email(driver), {"email": "private@example.com"})
		frappe.db.set_value("Employee", employee, "company_email", user)
		self.assertEqual(get_driver_email(driver), {"email": user})

	def test_delivery_trip_status_scheduled(self):
		self.delivery_trip.submit()
		self.assertEqual(self.delivery_trip.status, "Scheduled")

	def test_delivery_trip_status_cancelled(self):
		self.delivery_trip.submit()
		self.delivery_trip.cancel()
		self.assertEqual(self.delivery_trip.status, "Cancelled")

	def test_delivery_trip_status_in_transit(self):
		self.delivery_trip.submit()
		self.delivery_trip.delivery_stops[0].visited = 1
		self.delivery_trip.save()
		self.assertEqual(self.delivery_trip.status, "In Transit")

	def test_delivery_trip_status_completed(self):
		self.delivery_trip.submit()

		for stop in self.delivery_trip.delivery_stops:
			stop.visited = 1

		self.delivery_trip.save()
		self.assertEqual(self.delivery_trip.status, "Completed")

	def map_delivery_note_onto_trip(self, existing_stop):
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

		delivery_note = create_delivery_note()
		trip = frappe.new_doc("Delivery Trip")
		trip.append("delivery_stops", existing_stop)

		return delivery_note, make_delivery_trip(delivery_note.name, trip)

	def test_mapping_drops_placeholder_stop(self):
		delivery_note, trip = self.map_delivery_note_onto_trip({})

		self.assertEqual(len(trip.delivery_stops), 1)
		self.assertEqual(trip.delivery_stops[0].delivery_note, delivery_note.name)

	def test_mapping_keeps_partially_filled_stop(self):
		_, trip = self.map_delivery_note_onto_trip({"customer": "_Test Customer"})

		self.assertEqual(len(trip.delivery_stops), 2)
		self.assertIsNone(trip.delivery_stops[0].delivery_note)

	def test_stop_without_address_throws_mandatory_error(self):
		self.delivery_trip.append("delivery_stops", {"customer": "_Test Customer"})

		self.assertRaises(frappe.MandatoryError, self.delivery_trip.save)


def create_address(driver):
	if not frappe.db.exists("Address", {"address_title": "_Test Address for Driver"}):
		address = frappe.get_doc(
			{
				"doctype": "Address",
				"address_title": "_Test Address for Driver",
				"address_type": "Office",
				"address_line1": "Station Road",
				"city": "_Test City",
				"state": "Test State",
				"country": "India",
				"links": [{"link_doctype": "Driver", "link_name": driver.name}],
			}
		).insert(ignore_permissions=True)

		frappe.db.set_value("Driver", driver.name, "address", address.name)

		return address

	return frappe.get_doc("Address", {"address_title": "_Test Address for Driver"})


def create_driver():
	if not frappe.db.exists("Driver", {"full_name": "Newton Scmander"}):
		driver = frappe.get_doc(
			{
				"doctype": "Driver",
				"full_name": "Newton Scmander",
				"status": "Active",
				"cell_number": "98343424242",
				"license_number": "B809",
			}
		).insert(ignore_permissions=True)

		return driver

	return frappe.get_doc("Driver", {"full_name": "Newton Scmander"})


def create_delivery_notification():
	if not frappe.db.exists("Email Template", "Delivery Notification"):
		dispatch_template = frappe.get_doc(
			{
				"doctype": "Email Template",
				"name": "Delivery Notification",
				"response": "Test Delivery Trip",
				"subject": "Test Subject",
				"owner": frappe.session.user,
			}
		)
		dispatch_template.insert()

	delivery_settings = frappe.get_single("Delivery Settings")
	delivery_settings.dispatch_template = "Delivery Notification"
	delivery_settings.save()


def create_vehicle():
	if not frappe.db.exists("Vehicle", "JB 007"):
		vehicle = frappe.get_doc(
			{
				"doctype": "Vehicle",
				"license_plate": "JB 007",
				"make": "Maruti",
				"model": "PCM",
				"last_odometer": 5000,
				"acquisition_date": nowdate(),
				"location": "Mumbai",
				"chassis_no": "1234ABCD",
				"uom": "Litre",
				"vehicle_value": flt(500000),
			}
		)
		vehicle.insert()


def create_delivery_trip(driver, address, contact=None):
	if not contact:
		contact = get_contact_and_address("_Test Customer")

	delivery_trip = frappe.get_doc(
		{
			"doctype": "Delivery Trip",
			"company": erpnext.get_default_company(),
			"departure_time": add_days(now_datetime(), 5),
			"driver": driver.name,
			"driver_address": address.name,
			"vehicle": "JB 007",
			"delivery_stops": [
				{
					"customer": "_Test Customer",
					"address": contact.shipping_address.parent,
					"contact": contact.contact_person.parent,
				},
				{
					"customer": "_Test Customer",
					"address": contact.shipping_address.parent,
					"contact": contact.contact_person.parent,
				},
			],
		}
	).insert(ignore_permissions=True)

	return delivery_trip
