# Copyright (c) 2022, JINGROW
# For license information, please see license.txt

from datetime import date
from itertools import groupby
from typing import List

import jingrow
from jingrow.model.page import Page

from jcloud.jcloud.pagetype.invoice_item.invoice_item import InvoiceItem
from jcloud.jcloud.pagetype.payout_order_item.payout_order_item import PayoutOrderItem
from jcloud.utils import log_error


class PayoutOrder(Page):
	# begin: auto-generated types
	# This code is auto-generated. Do not modify anything in this block.

	from typing import TYPE_CHECKING

	if TYPE_CHECKING:
		from jingrow.types import DF
		from jcloud.jcloud.pagetype.payout_order_item.payout_order_item import PayoutOrderItem

		amended_from: DF.Link | None
		currency_cny: DF.Data | None
		currency_usd: DF.Data | None
		due_date: DF.Date | None
		jingrow_purchase_order: DF.Data | None
		ignore_commission: DF.Check
		items: DF.Table[PayoutOrderItem]
		mode_of_payment: DF.Literal["Cash", "Credits", "Internal"]
		net_total_cny: DF.Currency
		net_total_usd: DF.Currency
		notes: DF.SmallText | None
		period_end: DF.Date | None
		period_start: DF.Date | None
		recipient_currency: DF.Data | None
		status: DF.Literal["Draft", "Paid", "Commissioned"]
		team: DF.Link
		total_amount: DF.Currency
		type: DF.Literal["Marketplace", "SaaS"]
	# end: auto-generated types

	dashboard_fields = [
		"period_end",
		"team",
		"mode_of_payment",
		"net_total_cny",
		"net_total_usd",
		"status",
		"total_amount",
		"items",
	]

	@staticmethod
	def get_list_query(query):
		PayoutOrder = jingrow.qb.PageType("Payout Order")
		query = query.where((PayoutOrder.pagestatus != 2))
		return query

	def validate(self):
		self.validate_items()
		self.validate_net_totals()
		self.compute_total_amount()

	def validate_items(self):
		for row in self.items:
			invoice_name = row.invoice
			invoice = jingrow.db.get_value(
				"Invoice",
				invoice_name,
				[
					"status",
					"currency",
					"transaction_fee",
					"exchange_rate",
					"amount_paid",
				],
				as_dict=True,
			)

			if invoice.status != "Paid":
				jingrow.throw(f"Invoice {invoice_name} is not paid yet.")

			invoice_item = get_invoice_item_for_po_item(invoice_name, row)

			# check to avoid app revenue ledger item's calculation
			if not invoice_item:
				return

			row.tax = row.tax or 0.0
			row.total_amount = invoice_item.amount
			row.site = invoice_item.site
			row.currency = invoice.currency
			row.gateway_fee = 0.0

			# validate commissions and thresholds
			app_payment = (
				jingrow.get_cached_pg("Marketplace App Payment", row.document_name)
				if jingrow.db.exists("Marketplace App Payment", row.document_name)
				else jingrow.get_pg(
					{
						"pagetype": "Marketplace App Payment",
						"app": row.document_name,
						"team": self.team,
					}
				).insert(ignore_permissions=True)
			)

			row.commission = (
				app_payment.get_commission(row.total_amount) if not self.ignore_commission else 0.0
			)

			row.net_amount = row.total_amount - row.commission

			if row.currency == "CNY":
				app_payment.total_cny += row.net_amount if row.net_amount > 0 else row.commission
			else:
				app_payment.total_usd += row.net_amount if row.net_amount > 0 else row.commission

			app_payment.save(ignore_permissions=True)

	def validate_net_totals(self):
		self.net_total_usd = 0
		self.net_total_cny = 0

		for row in self.items:
			if row.currency == "CNY":
				self.net_total_cny += row.net_amount
			else:
				self.net_total_usd += row.net_amount

		if self.net_total_usd <= 0 and self.net_total_cny <= 0:
			self.status = "Commissioned"

	def compute_total_amount(self):
		exchange_rate = jingrow.db.get_single_value("Jcloud Settings", "usd_rate")
		if self.recipient_currency == "USD":
			cny_in_usd = 0
			if self.net_total_cny > 0:
				cny_in_usd = self.net_total_cny / exchange_rate
			self.total_amount = self.net_total_usd + cny_in_usd
		elif self.recipient_currency == "CNY":
			self.total_amount = self.net_total_cny + (self.net_total_usd * exchange_rate)

	def before_submit(self):
		if self.mode_of_payment == "Cash" and (not self.jingrow_purchase_order):
			jingrow.throw(
				"Jingrow Purchase Order is required before marking this cash payout as Paid"
			)
		self.status = "Paid"


def get_invoice_item_for_po_item(
	invoice_name: str, payout_order_item: PayoutOrderItem
) -> InvoiceItem | None:
	try:
		if payout_order_item.invoice_item:
			item = jingrow.get_pg("Invoice Item", payout_order_item.invoice_item)
			if (
				item.parent == invoice_name
				and item.document_name == payout_order_item.document_name
				and item.document_type == payout_order_item.document_type
				and item.plan == payout_order_item.plan
				and item.rate == payout_order_item.rate
			):
				return item

		return jingrow.get_pg(
			"Invoice Item",
			{
				"parent": invoice_name,
				"document_name": payout_order_item.document_name,
				"document_type": payout_order_item.document_type,
				"plan": payout_order_item.plan,
				"rate": payout_order_item.rate,
			},
		)
	except jingrow.DoesNotExistError:
		return None


def create_marketplace_payout_orders_monthly(period_start=None, period_end=None):
	period_start, period_end = (
		(period_start, period_end)
		if period_start and period_end
		else get_current_period_boundaries()
	)
	items = get_unaccounted_marketplace_invoice_items()

	# Group by teams
	for app_team, items in groupby(items, key=lambda x: x["app_team"]):
		try:
			item_names = [i.name for i in items]

			po_exists = jingrow.db.exists(
				"Payout Order", {"team": app_team, "period_end": period_end}
			)

			if not po_exists:
				create_payout_order_from_invoice_item_names(
					item_names, team=app_team, period_start=period_start, period_end=period_end
				)
			else:
				po = jingrow.get_pg("Payout Order", {"team": app_team, "period_end": period_end})
				add_invoice_items_to_po(po, item_names)

			jingrow.db.set_value(
				"Invoice Item",
				{"name": ("in", item_names)},
				"has_marketplace_payout_completed",
				True,
			)

			if not jingrow.flags.in_test:
				# Save this particular PO transaction
				jingrow.db.commit()
		except Exception:
			jingrow.db.rollback()
			log_error("Payout Order Creation Error", team=app_team, invoice_items=items)


def get_current_period_boundaries():
	today = jingrow.utils.today()
	period_start = jingrow.utils.data.get_first_day(today)
	period_end = jingrow.utils.data.get_last_day(today)

	return period_start, period_end


def add_invoice_items_to_po(po, invoice_item_names):
	for item_name in invoice_item_names:
		invoice_item = jingrow.get_pg("Invoice Item", item_name)
		po.append(
			"items",
			{
				"invoice_item": invoice_item.name,
				"invoice": invoice_item.parent,
				"document_type": invoice_item.document_type,
				"document_name": invoice_item.document_name,
				"rate": invoice_item.rate,
				"plan": invoice_item.plan,
				"quantity": invoice_item.quantity,
				"site": invoice_item.site,
			},
		)
	po.save()


def get_unaccounted_marketplace_invoice_items():
	# Get all marketplace app invoice items
	invoice = jingrow.qb.PageType("Invoice")
	invoice_item = jingrow.qb.PageType("Invoice Item")
	marketplace_app = jingrow.qb.PageType("Marketplace App")

	items = (
		jingrow.qb.from_(invoice_item)
		.left_join(invoice)
		.on(invoice_item.parent == invoice.name)
		.left_join(marketplace_app)
		.on(marketplace_app.name == invoice_item.document_name)
		.where(invoice.status == "Paid")
		.where(invoice_item.document_type == "Marketplace App")
		.where(invoice_item.has_marketplace_payout_completed == 0)
		.select(
			invoice_item.name, invoice_item.document_name, marketplace_app.team.as_("app_team")
		)
		.distinct()
		.run(as_dict=True)
	)

	return items


@jingrow.whitelist()
def create_payout_order_from_invoice_items(
	invoice_items: List[InvoiceItem],
	team: str,
	period_start: date,
	period_end: date,
	mode_of_payment: str = "Cash",
	notes: str = "",
	type: str = "Marketplace",
	save: bool = True,
) -> PayoutOrder:
	po = jingrow.get_pg(
		{
			"pagetype": "Payout Order",
			"team": team,
			"mode_of_payment": mode_of_payment,
			"notes": notes,
			"type": type,
			"period_start": period_start,
			"period_end": period_end,
		}
	)

	for invoice_item in invoice_items:
		po.append(
			"items",
			{
				"invoice_item": invoice_item.name,
				"invoice": invoice_item.parent,
				"document_type": invoice_item.document_type,
				"document_name": invoice_item.document_name,
				"rate": invoice_item.rate,
				"plan": invoice_item.plan,
				"quantity": invoice_item.quantity,
				"site": invoice_item.site,
			},
		)

	if save:
		po.insert()

	return po


def create_payout_order_from_invoice_item_names(item_names, *args, **kwargs):
	invoice_items = (jingrow.get_pg("Invoice Item", i) for i in item_names)
	return create_payout_order_from_invoice_items(invoice_items, *args, **kwargs)


def create_marketplace_payout_orders():
	# ONLY RUN ON LAST DAY OF THE MONTH
	today = jingrow.utils.today()
	period_end = jingrow.utils.data.get_last_day(today).strftime("%Y-%m-%d")

	if today != period_end:
		return

	create_marketplace_payout_orders_monthly()
