# Copyright (c) 2018, SolutionERP LLC and contributors
# For license information, please see license.txt

import json

import xhiveframework
from xhiveframework import _
from xhiveframework.desk.doctype.tag.tag import add_tag
from xhiveframework.model.document import Document
from xhiveframework.utils import add_months, formatdate, getdate, today
from plaid.errors import ItemError

from xhiveerp.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
from xhiveerp.xhiveerp_integrations.doctype.plaid_settings.plaid_connector import PlaidConnector


class PlaidSettings(Document):
	@staticmethod
	@xhiveframework.whitelist()
	def get_link_token():
		plaid = PlaidConnector()
		return plaid.get_link_token()


@xhiveframework.whitelist()
def get_plaid_configuration():
	if xhiveframework.db.get_single_value("Plaid Settings", "enabled"):
		plaid_settings = xhiveframework.get_single("Plaid Settings")
		return {
			"plaid_env": plaid_settings.plaid_env,
			"link_token": plaid_settings.get_link_token(),
			"client_name": xhiveframework.local.site,
		}

	return "disabled"


@xhiveframework.whitelist()
def add_institution(token, response):
	response = json.loads(response)

	plaid = PlaidConnector()
	access_token = plaid.get_access_token(token)
	bank = None

	if not xhiveframework.db.exists("Bank", response["institution"]["name"]):
		try:
			bank = xhiveframework.get_doc(
				{
					"doctype": "Bank",
					"bank_name": response["institution"]["name"],
					"plaid_access_token": access_token,
				}
			)
			bank.insert()
		except Exception:
			xhiveframework.log_error("Plaid Link Error")
	else:
		bank = xhiveframework.get_doc("Bank", response["institution"]["name"])
		bank.plaid_access_token = access_token
		bank.save()

	return bank


@xhiveframework.whitelist()
def add_bank_accounts(response, bank, company):
	try:
		response = json.loads(response)
	except TypeError:
		pass

	if isinstance(bank, str):
		bank = json.loads(bank)
	result = []

	default_gl_account = get_default_bank_cash_account(company, "Bank")
	if not default_gl_account:
		xhiveframework.throw(_("Please setup a default bank account for company {0}").format(company))

	for account in response["accounts"]:
		acc_type = xhiveframework.db.get_value("Bank Account Type", account["type"])
		if not acc_type:
			add_account_type(account["type"])

		acc_subtype = xhiveframework.db.get_value("Bank Account Subtype", account["subtype"])
		if not acc_subtype:
			add_account_subtype(account["subtype"])

		bank_account_name = "{} - {}".format(account["name"], bank["bank_name"])
		existing_bank_account = xhiveframework.db.exists("Bank Account", bank_account_name)

		if not existing_bank_account:
			try:
				new_account = xhiveframework.get_doc(
					{
						"doctype": "Bank Account",
						"bank": bank["bank_name"],
						"account": default_gl_account.account,
						"account_name": account["name"],
						"account_type": account.get("type", ""),
						"account_subtype": account.get("subtype", ""),
						"mask": account.get("mask", ""),
						"integration_id": account["id"],
						"is_company_account": 1,
						"company": company,
					}
				)
				new_account.insert()

				result.append(new_account.name)
			except xhiveframework.UniqueValidationError:
				xhiveframework.msgprint(
					_("Bank account {0} already exists and could not be created again").format(account["name"])
				)
			except Exception:
				xhiveframework.log_error("Plaid Link Error")
				xhiveframework.throw(
					_("There was an error creating Bank Account while linking with Plaid."),
					title=_("Plaid Link Failed"),
				)

		else:
			try:
				existing_account = xhiveframework.get_doc("Bank Account", existing_bank_account)
				existing_account.update(
					{
						"bank": bank["bank_name"],
						"account_name": account["name"],
						"account_type": account.get("type", ""),
						"account_subtype": account.get("subtype", ""),
						"mask": account.get("mask", ""),
						"integration_id": account["id"],
					}
				)
				existing_account.save()
				result.append(existing_bank_account)
			except Exception:
				xhiveframework.log_error("Plaid Link Error")
				xhiveframework.throw(
					_("There was an error updating Bank Account {} while linking with Plaid.").format(
						existing_bank_account
					),
					title=_("Plaid Link Failed"),
				)

	return result


def add_account_type(account_type):
	try:
		xhiveframework.get_doc({"doctype": "Bank Account Type", "account_type": account_type}).insert()
	except Exception:
		xhiveframework.throw(xhiveframework.get_traceback())


def add_account_subtype(account_subtype):
	try:
		xhiveframework.get_doc({"doctype": "Bank Account Subtype", "account_subtype": account_subtype}).insert()
	except Exception:
		xhiveframework.throw(xhiveframework.get_traceback())


@xhiveframework.whitelist()
def sync_transactions(bank, bank_account):
	"""Sync transactions based on the last integration date as the start date, after sync is completed
	add the transaction date of the oldest transaction as the last integration date."""
	last_transaction_date = xhiveframework.db.get_value("Bank Account", bank_account, "last_integration_date")
	if last_transaction_date:
		start_date = formatdate(last_transaction_date, "YYYY-MM-dd")
	else:
		start_date = formatdate(add_months(today(), -12), "YYYY-MM-dd")
	end_date = formatdate(today(), "YYYY-MM-dd")

	try:
		transactions = get_transactions(
			bank=bank, bank_account=bank_account, start_date=start_date, end_date=end_date
		)

		result = []
		if transactions:
			for transaction in reversed(transactions):
				result += new_bank_transaction(transaction)

		if result:
			last_transaction_date = xhiveframework.db.get_value("Bank Transaction", result.pop(), "date")

			xhiveframework.logger().info(
				f"Plaid added {len(result)} new Bank Transactions from '{bank_account}' between {start_date} and {end_date}"
			)

			xhiveframework.db.set_value(
				"Bank Account", bank_account, "last_integration_date", last_transaction_date
			)
	except Exception:
		xhiveframework.log_error(xhiveframework.get_traceback(), _("Plaid transactions sync error"))


def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
	access_token = None

	if bank_account:
		related_bank = xhiveframework.db.get_values(
			"Bank Account", bank_account, ["bank", "integration_id"], as_dict=True
		)
		access_token = xhiveframework.db.get_value("Bank", related_bank[0].bank, "plaid_access_token")
		account_id = related_bank[0].integration_id
	else:
		access_token = xhiveframework.db.get_value("Bank", bank, "plaid_access_token")
		account_id = None

	plaid = PlaidConnector(access_token)

	transactions = []
	try:
		transactions = plaid.get_transactions(
			start_date=start_date, end_date=end_date, account_id=account_id
		)
	except ItemError as e:
		if e.code == "ITEM_LOGIN_REQUIRED":
			msg = _("There was an error syncing transactions.") + " "
			msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " "
			xhiveframework.log_error(message=msg, title=_("Plaid Link Refresh Required"))

	return transactions


def new_bank_transaction(transaction):
	result = []

	bank_account = xhiveframework.db.get_value("Bank Account", dict(integration_id=transaction["account_id"]))

	amount = float(transaction["amount"])
	if amount >= 0.0:
		deposit = 0.0
		withdrawal = amount
	else:
		deposit = abs(amount)
		withdrawal = 0.0

	status = "Pending" if transaction["pending"] == "True" else "Settled"

	tags = []
	try:
		tags += transaction["category"]
		tags += [f'Plaid Cat. {transaction["category_id"]}']
	except KeyError:
		pass

	if not xhiveframework.db.exists("Bank Transaction", dict(transaction_id=transaction["transaction_id"])):
		try:
			new_transaction = xhiveframework.get_doc(
				{
					"doctype": "Bank Transaction",
					"date": getdate(transaction["date"]),
					"status": status,
					"bank_account": bank_account,
					"deposit": deposit,
					"withdrawal": withdrawal,
					"currency": transaction["iso_currency_code"],
					"transaction_id": transaction["transaction_id"],
					"transaction_type": (
						transaction["transaction_code"] or transaction["payment_meta"]["payment_method"]
					),
					"reference_number": (
						transaction["check_number"]
						or transaction["payment_meta"]["reference_number"]
						or transaction["name"]
					),
					"description": transaction["name"],
				}
			)
			new_transaction.insert()
			new_transaction.submit()

			for tag in tags:
				add_tag(tag, "Bank Transaction", new_transaction.name)

			result.append(new_transaction.name)

		except Exception:
			xhiveframework.throw(_("Bank transaction creation error"))

	return result


def automatic_synchronization():
	settings = xhiveframework.get_doc("Plaid Settings", "Plaid Settings")
	if settings.enabled == 1 and settings.automatic_sync == 1:
		enqueue_synchronization()


@xhiveframework.whitelist()
def enqueue_synchronization():
	plaid_accounts = xhiveframework.get_all(
		"Bank Account", filters={"integration_id": ["!=", ""]}, fields=["name", "bank"]
	)

	for plaid_account in plaid_accounts:
		xhiveframework.enqueue(
			"xhiveerp.xhiveerp_integrations.doctype.plaid_settings.plaid_settings.sync_transactions",
			bank=plaid_account.bank,
			bank_account=plaid_account.name,
		)


@xhiveframework.whitelist()
def get_link_token_for_update(access_token):
	plaid = PlaidConnector(access_token)
	return plaid.get_link_token(update_mode=True)


def get_company(bank_account_name):
	from xhiveframework.defaults import get_user_default

	company_names = xhiveframework.db.get_all("Company", pluck="name")
	if len(company_names) == 1:
		return company_names[0]
	if xhiveframework.db.exists("Bank Account", bank_account_name):
		return xhiveframework.db.get_value("Bank Account", bank_account_name, "company")
	company_default = get_user_default("Company")
	if company_default:
		return company_default
	xhiveframework.throw(_("Could not detect the Company for updating Bank Accounts"))


@xhiveframework.whitelist()
def update_bank_account_ids(response):
	data = json.loads(response)
	institution_name = data["institution"]["name"]
	bank = xhiveframework.get_doc("Bank", institution_name).as_dict()
	bank_account_name = f"{data['account']['name']} - {institution_name}"
	return add_bank_accounts(response, bank, get_company(bank_account_name))
