from __future__ import unicode_literals
import frappe
from frappe import _
import json
from frappe.utils import cstr, cint, nowdate, getdate, flt, get_request_session, get_datetime
from erpnext.erpnext_integrations.utils import validate_webhooks_request
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note, make_sales_invoice
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import sync_item_from_shopify
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer
from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log, dump_request_data
from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import get_shopify_url, get_header

@frappe.whitelist(allow_guest=True)
@validate_webhooks_request("Shopify Settings", 'X-Shopify-Hmac-Sha256', secret_key='shared_secret')
def store_request_data(order=None, event=None):
	if frappe.request:
		order = json.loads(frappe.request.data)
		event = frappe.request.headers.get('X-Shopify-Topic')

	dump_request_data(order, event)

def sync_sales_order(order, request_id=None, old_order_sync=False):
	frappe.set_user('Administrator')
	shopify_settings = frappe.get_doc("Shopify Settings")
	frappe.flags.request_id = request_id

	if not frappe.db.get_value("Sales Order", filters={"shopify_order_id": cstr(order['id'])}):
		try:
			validate_customer(order, shopify_settings)
			validate_item(order, shopify_settings)
			create_order(order, shopify_settings, old_order_sync=old_order_sync)
		except Exception as e:
			make_shopify_log(status="Error", exception=e)

		else:
			make_shopify_log(status="Success")

def prepare_sales_invoice(order, request_id=None):
	frappe.set_user('Administrator')
	shopify_settings = frappe.get_doc("Shopify Settings")
	frappe.flags.request_id = request_id

	try:
		sales_order = get_sales_order(cstr(order['id']))
		if sales_order:
			create_sales_invoice(order, shopify_settings, sales_order)
			make_shopify_log(status="Success")
	except Exception as e:
		make_shopify_log(status="Error", exception=e, rollback=True)

def prepare_delivery_note(order, request_id=None):
	frappe.set_user('Administrator')
	shopify_settings = frappe.get_doc("Shopify Settings")
	frappe.flags.request_id = request_id

	try:
		sales_order = get_sales_order(cstr(order['id']))
		if sales_order:
			create_delivery_note(order, shopify_settings, sales_order)
		make_shopify_log(status="Success")
	except Exception as e:
		make_shopify_log(status="Error", exception=e, rollback=True)

def get_sales_order(shopify_order_id):
	sales_order = frappe.db.get_value("Sales Order", filters={"shopify_order_id": shopify_order_id})
	if sales_order:
		so = frappe.get_doc("Sales Order", sales_order)
		return so

def validate_customer(order, shopify_settings):
	customer_id = order.get("customer", {}).get("id")
	if customer_id:
		if not frappe.db.get_value("Customer", {"shopify_customer_id": customer_id}, "name"):
			create_customer(order.get("customer"), shopify_settings)

def validate_item(order, shopify_settings):
	for item in order.get("line_items"):
		if item.get("product_id") and not frappe.db.get_value("Item", {"shopify_product_id": item.get("product_id")}, "name"):
			sync_item_from_shopify(shopify_settings, item)

def create_order(order, shopify_settings, old_order_sync=False, company=None):
	so = create_sales_order(order, shopify_settings, company)
	if so:
		if order.get("financial_status") == "paid":
			create_sales_invoice(order, shopify_settings, so, old_order_sync=old_order_sync)

		if order.get("fulfillments") and not old_order_sync:
			create_delivery_note(order, shopify_settings, so)

def create_sales_order(shopify_order, shopify_settings, company=None):
	product_not_exists = []
	customer = frappe.db.get_value("Customer", {"shopify_customer_id": shopify_order.get("customer", {}).get("id")}, "name")
	so = frappe.db.get_value("Sales Order", {"shopify_order_id": shopify_order.get("id")}, "name")

	if not so:
		items = get_order_items(shopify_order.get("line_items"), shopify_settings, getdate(shopify_order.get('created_at')))

		if not items:
			message = 'Following items exists in the shopify order but relevant records were not found in the shopify Product master'
			message += "\n" + ", ".join(product_not_exists)

			make_shopify_log(status="Error", exception=message, rollback=True)

			return ''

		so = frappe.get_doc({
			"doctype": "Sales Order",
			"naming_series": shopify_settings.sales_order_series or "SO-Shopify-",
			"shopify_order_id": shopify_order.get("id"),
			"shopify_order_number": shopify_order.get("name"),
			"customer": customer or shopify_settings.default_customer,
			"transaction_date": getdate(shopify_order.get("created_at")) or nowdate(),
			"delivery_date": getdate(shopify_order.get("created_at")) or nowdate(),
			"company": shopify_settings.company,
			"selling_price_list": shopify_settings.price_list,
			"ignore_pricing_rule": 1,
			"items": items,
			"taxes": get_order_taxes(shopify_order, shopify_settings),
			"apply_discount_on": "Grand Total",
			"discount_amount": get_discounted_amount(shopify_order),
		})

		if company:
			so.update({
				"company": company,
				"status": "Draft"
			})
		so.flags.ignore_mandatory = True
		so.save(ignore_permissions=True)
		so.submit()

	else:
		so = frappe.get_doc("Sales Order", so)

	frappe.db.commit()
	return so

def create_sales_invoice(shopify_order, shopify_settings, so, old_order_sync=False):
	if not frappe.db.get_value("Sales Invoice", {"shopify_order_id": shopify_order.get("id")}, "name")\
		and so.docstatus==1 and not so.per_billed and cint(shopify_settings.sync_sales_invoice):

		if old_order_sync:
			posting_date = getdate(shopify_order.get('created_at'))
		else:
			posting_date = nowdate()

		si = make_sales_invoice(so.name, ignore_permissions=True)
		si.shopify_order_id = shopify_order.get("id")
		si.shopify_order_number = shopify_order.get("name")
		si.set_posting_time = 1
		si.posting_date = posting_date
		si.due_date = posting_date
		si.naming_series = shopify_settings.sales_invoice_series or "SI-Shopify-"
		si.flags.ignore_mandatory = True
		set_cost_center(si.items, shopify_settings.cost_center)
		si.insert(ignore_mandatory=True)
		si.submit()
		make_payament_entry_against_sales_invoice(si, shopify_settings, posting_date)
		frappe.db.commit()

def set_cost_center(items, cost_center):
	for item in items:
		item.cost_center = cost_center

def make_payament_entry_against_sales_invoice(doc, shopify_settings, posting_date=None):
	from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
	payment_entry = get_payment_entry(doc.doctype, doc.name, bank_account=shopify_settings.cash_bank_account)
	payment_entry.flags.ignore_mandatory = True
	payment_entry.reference_no = doc.name
	payment_entry.posting_date = posting_date or nowdate()
	payment_entry.reference_date = posting_date or nowdate()
	payment_entry.insert(ignore_permissions=True)
	payment_entry.submit()

def create_delivery_note(shopify_order, shopify_settings, so):
	if not cint(shopify_settings.sync_delivery_note):
		return

	for fulfillment in shopify_order.get("fulfillments"):
		if not frappe.db.get_value("Delivery Note", {"shopify_fulfillment_id": fulfillment.get("id")}, "name")\
			and so.docstatus==1:

			dn = make_delivery_note(so.name)
			dn.shopify_order_id = fulfillment.get("order_id")
			dn.shopify_order_number = shopify_order.get("name")
			dn.set_posting_time = 1
			dn.posting_date = getdate(fulfillment.get("created_at"))
			dn.shopify_fulfillment_id = fulfillment.get("id")
			dn.naming_series = shopify_settings.delivery_note_series or "DN-Shopify-"
			dn.items = get_fulfillment_items(dn.items, fulfillment.get("line_items"), shopify_settings)
			dn.flags.ignore_mandatory = True
			dn.save()
			dn.submit()
			frappe.db.commit()

def get_fulfillment_items(dn_items, fulfillment_items, shopify_settings):
	return [dn_item.update({"qty": item.get("quantity")}) for item in fulfillment_items for dn_item in dn_items\
			if get_item_code(item) == dn_item.item_code]

def get_discounted_amount(order):
	discounted_amount = 0.0
	for discount in order.get("discount_codes"):
		discounted_amount += flt(discount.get("amount"))
	return discounted_amount

def get_order_items(order_items, shopify_settings, delivery_date):
	items = []
	all_product_exists = True
	product_not_exists = []

	for shopify_item in order_items:
		if not shopify_item.get('product_exists'):
			all_product_exists = False
			product_not_exists.append({'title':shopify_item.get('title'),
				'shopify_order_id': shopify_item.get('id')})
			continue

		if all_product_exists:
			item_code = get_item_code(shopify_item)
			items.append({
				"item_code": item_code,
				"item_name": shopify_item.get("name"),
				"rate": shopify_item.get("price"),
				"delivery_date": delivery_date,
				"qty": shopify_item.get("quantity"),
				"stock_uom": shopify_item.get("uom") or _("Nos"),
				"warehouse": shopify_settings.warehouse
			})
		else:
			items = []

	return items

def get_item_code(shopify_item):
	item_code = frappe.db.get_value("Item", {"shopify_variant_id": shopify_item.get("variant_id")}, "item_code")
	if not item_code:
		item_code = frappe.db.get_value("Item", {"shopify_product_id": shopify_item.get("product_id")}, "item_code")
	if not item_code:
		item_code = frappe.db.get_value("Item", {"item_name": shopify_item.get("title")}, "item_code")

	return item_code

def get_order_taxes(shopify_order, shopify_settings):
	taxes = []
	for tax in shopify_order.get("tax_lines"):
		taxes.append({
			"charge_type": _("On Net Total"),
			"account_head": get_tax_account_head(tax),
			"description": "{0} - {1}%".format(tax.get("title"), tax.get("rate") * 100.0),
			"rate": tax.get("rate") * 100.00,
			"included_in_print_rate": 1 if shopify_order.get("taxes_included") else 0,
			"cost_center": shopify_settings.cost_center
		})

	taxes = update_taxes_with_shipping_lines(taxes, shopify_order.get("shipping_lines"), shopify_settings)

	return taxes

def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings):
	"""Shipping lines represents the shipping details,
		each such shipping detail consists of a list of tax_lines"""
	for shipping_charge in shipping_lines:
		if shipping_charge.get("price"):
			taxes.append({
				"charge_type": _("Actual"),
				"account_head": get_tax_account_head(shipping_charge),
				"description": shipping_charge["title"],
				"tax_amount": shipping_charge["price"],
				"cost_center": shopify_settings.cost_center
			})

		for tax in shipping_charge.get("tax_lines"):
			taxes.append({
				"charge_type": _("Actual"),
				"account_head": get_tax_account_head(tax),
				"description": tax["title"],
				"tax_amount": tax["price"],
				"cost_center": shopify_settings.cost_center
			})

	return taxes

def get_tax_account_head(tax):
	tax_title = tax.get("title").encode("utf-8")

	tax_account =  frappe.db.get_value("Shopify Tax Account", \
		{"parent": "Shopify Settings", "shopify_tax": tax_title}, "tax_account")

	if not tax_account:
		frappe.throw(_("Tax Account not specified for Shopify Tax {0}").format(tax.get("title")))

	return tax_account

@frappe.whitelist(allow_guest=True)
def sync_old_orders():
	frappe.set_user('Administrator')
	shopify_settings = frappe.get_doc('Shopify Settings')

	if not shopify_settings.sync_missing_orders:
		return

	url = get_url(shopify_settings)
	session = get_request_session()

	try:
		res = session.get(url, headers=get_header(shopify_settings))
		res.raise_for_status()
		orders = res.json()["orders"]

		for order in orders:
			if is_sync_complete(shopify_settings, order):
				stop_sync(shopify_settings)
				return

			sync_sales_order(order=order, old_order_sync=True)
			last_order_id = order.get('id')

		if last_order_id:
			shopify_settings.load_from_db()
			shopify_settings.last_order_id = last_order_id
			shopify_settings.save()
			frappe.db.commit()

	except Exception as e:
		raise e

def stop_sync(shopify_settings):
	shopify_settings.sync_missing_orders = 0
	shopify_settings.last_order_id = ''
	shopify_settings.save()
	frappe.db.commit()

def get_url(shopify_settings):
	last_order_id = shopify_settings.last_order_id

	if not last_order_id:
		if shopify_settings.sync_based_on == 'Date':
			url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&created_at_min={0}&since_id=0".format(
				get_datetime(shopify_settings.from_date)), shopify_settings)
		else:
			url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(
				shopify_settings.from_order_id), shopify_settings)
	else:
		url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings)

	return url

def is_sync_complete(shopify_settings, order):
	if shopify_settings.sync_based_on == 'Date':
		return getdate(shopify_settings.to_date) < getdate(order.get('created_at'))
	else:
		return cstr(order.get('id')) == cstr(shopify_settings.to_order_id)