# Copyright (c) 2015, JINGROW and Contributors
# License: MIT. See LICENSE

import csv
import os
import re

import jingrow
import jingrow.permissions
from jingrow import _
from jingrow.core.pagetype.access_log.access_log import make_access_log
from jingrow.model.utils import is_virtual_pagetype
from jingrow.utils import cint, cstr, format_datetime, format_duration, formatdate, parse_json
from jingrow.utils.csvutils import UnicodeWriter

reflags = {"I": re.I, "L": re.L, "M": re.M, "U": re.U, "S": re.S, "X": re.X, "D": re.DEBUG}


def get_data_keys():
	return jingrow._dict(
		{
			"data_separator": _("Start entering data below this line"),
			"main_table": _("Table") + ":",
			"parent_table": _("Parent Table") + ":",
			"columns": _("Column Name") + ":",
			"pagetype": _("PageType") + ":",
		}
	)


@jingrow.whitelist()
def export_data(
	pagetype=None,
	parent_pagetype=None,
	all_pagetypes=True,
	with_data=False,
	select_columns=None,
	file_type="CSV",
	template=False,
	filters=None,
	export_without_column_meta=False,
):
	_pagetype = pagetype
	if isinstance(_pagetype, list):
		_pagetype = _pagetype[0]
	make_access_log(
		pagetype=_pagetype,
		file_type=file_type,
		columns=select_columns,
		filters=filters,
		method=parent_pagetype,
	)

	template_bool = template
	if isinstance(template, str):
		template_bool = template.lower() == "true"

	export_without_column_meta_bool = export_without_column_meta
	if isinstance(export_without_column_meta, str):
		export_without_column_meta_bool = export_without_column_meta.lower() == "true"

	exporter = DataExporter(
		pagetype=pagetype,
		parent_pagetype=parent_pagetype,
		all_pagetypes=all_pagetypes,
		with_data=with_data,
		select_columns=select_columns,
		file_type=file_type,
		template=template_bool,
		filters=filters,
		export_without_column_meta=export_without_column_meta_bool,
	)
	exporter.build_response()


class DataExporter:
	def __init__(
		self,
		pagetype=None,
		parent_pagetype=None,
		all_pagetypes=True,
		with_data=False,
		select_columns=None,
		file_type="CSV",
		template=False,
		filters=None,
		export_without_column_meta=False,
	):
		self.pagetype = pagetype
		self.parent_pagetype = parent_pagetype
		self.all_pagetypes = all_pagetypes
		self.with_data = cint(with_data)
		self.select_columns = select_columns
		self.file_type = file_type
		self.template = template
		self.filters = filters
		self.export_without_column_meta = export_without_column_meta
		self.data_keys = get_data_keys()

		self.prepare_args()

	def prepare_args(self):
		if self.select_columns:
			self.select_columns = parse_json(self.select_columns)
		if self.filters:
			self.filters = parse_json(self.filters)

		self.pgs_to_export = {}
		if self.pagetype:
			if isinstance(self.pagetype, str):
				self.pagetype = [self.pagetype]

			if len(self.pagetype) > 1:
				self.pgs_to_export = self.pagetype[1]
			self.pagetype = self.pagetype[0]

		if not self.parent_pagetype:
			self.parent_pagetype = self.pagetype

		self.column_start_end = {}

		if self.all_pagetypes:
			self.child_pagetypes = [
				dict(pagetype=df.options, parentfield=df.fieldname)
				for df in jingrow.get_meta(self.pagetype).get_table_fields()
			]

	def build_response(self):
		self.writer = UnicodeWriter()
		self.name_field = "parent" if self.parent_pagetype != self.pagetype else "name"

		if self.template:
			self.add_main_header()

		# No need of empty row at the start
		if not self.export_without_column_meta:
			self.writer.writerow([""])

		self.tablerow = [self.data_keys.pagetype]
		self.labelrow = [_("Column Labels:")]
		self.fieldrow = [self.data_keys.columns]
		self.mandatoryrow = [_("Mandatory:")]
		self.typerow = [_("Type:")]
		self.inforow = [_("Info:")]
		self.columns = []

		self.build_field_columns(self.pagetype)

		if self.all_pagetypes:
			for d in self.child_pagetypes:
				self.append_empty_field_column()
				if (
					self.select_columns and self.select_columns.get(d["pagetype"], None)
				) or not self.select_columns:
					# if atleast one column is selected for this pagetype
					self.build_field_columns(d["pagetype"], d["parentfield"])

		self.add_field_headings()
		self.add_data()
		if self.with_data and not self.data:
			jingrow.respond_as_web_page(
				_("No Data"), _("There is no data to be exported"), indicator_color="orange"
			)

		if self.file_type == "Excel":
			self.build_response_as_excel()
		else:
			# write out response as a type csv
			jingrow.response["result"] = cstr(self.writer.getvalue())
			jingrow.response["type"] = "csv"
			jingrow.response["pagetype"] = self.pagetype

	def add_main_header(self):
		self.writer.writerow([_("Data Import Template")])
		self.writer.writerow([self.data_keys.main_table, self.pagetype])

		if self.parent_pagetype != self.pagetype:
			self.writer.writerow([self.data_keys.parent_table, self.parent_pagetype])
		else:
			self.writer.writerow([""])

		self.writer.writerow([""])
		self.writer.writerow([_("Notes:")])
		self.writer.writerow([_("Please do not change the template headings.")])
		self.writer.writerow([_("First data column must be blank.")])
		self.writer.writerow([_('If you are uploading new records, leave the "name" (ID) column blank.')])
		self.writer.writerow(
			[_('If you are uploading new records, "Naming Series" becomes mandatory, if present.')]
		)
		self.writer.writerow(
			[
				_(
					"Only mandatory fields are necessary for new records. You can delete non-mandatory columns if you wish."
				)
			]
		)
		self.writer.writerow([_("For updating, you can update only selective columns.")])
		self.writer.writerow(
			[_("You can only upload upto 5000 records in one go. (may be less in some cases)")]
		)
		if self.name_field == "parent":
			self.writer.writerow([_('"Parent" signifies the parent table in which this row must be added')])
			self.writer.writerow(
				[_('If you are updating, please select "Overwrite" else existing rows will not be deleted.')]
			)

	def build_field_columns(self, dt, parentfield=None):
		meta = jingrow.get_meta(dt)

		# build list of valid pagefields
		tablecolumns = []
		table_name = "tab" + dt

		for f in jingrow.db.get_table_columns_description(table_name):
			field = meta.get_field(f.name)
			if f.name in ["owner", "creation"]:
				std_field = next((x for x in jingrow.model.std_fields if x["fieldname"] == f.name), None)
				if std_field:
					field = jingrow._dict(
						{
							"fieldname": std_field.get("fieldname"),
							"label": std_field.get("label"),
							"fieldtype": std_field.get("fieldtype"),
							"options": std_field.get("options"),
							"idx": 0,
							"parent": dt,
						}
					)

			if field and (
				(self.select_columns and f.name in self.select_columns[dt]) or not self.select_columns
			):
				tablecolumns.append(field)

		tablecolumns.sort(key=lambda a: int(a.idx))

		_column_start_end = jingrow._dict(start=0)

		if dt == self.pagetype:
			if (meta.get("autoname") and meta.get("autoname").lower() == "prompt") or (self.with_data):
				self._append_name_column()

			# if importing only child table for new record, add parent field
			if meta.get("istable") and not self.with_data:
				self.append_field_column(
					jingrow._dict(
						{
							"fieldname": "parent",
							"parent": "",
							"label": "Parent",
							"fieldtype": "Data",
							"reqd": 1,
							"info": _(
								"Parent is the name of the document to which the data will get added to."
							),
						}
					),
					True,
				)

			_column_start_end = jingrow._dict(start=0)
		else:
			_column_start_end = jingrow._dict(start=len(self.columns))

			if self.with_data:
				self._append_name_column(dt)

		for pagefield in tablecolumns:
			self.append_field_column(pagefield, True)

		# all non mandatory fields
		for pagefield in tablecolumns:
			self.append_field_column(pagefield, False)

		# if there is one column, add a blank column (?)
		if len(self.columns) - _column_start_end.start == 1:
			self.append_empty_field_column()

		# append PageType name
		self.tablerow[_column_start_end.start + 1] = dt

		if parentfield:
			self.tablerow[_column_start_end.start + 2] = parentfield

		_column_start_end.end = len(self.columns) + 1

		self.column_start_end[(dt, parentfield)] = _column_start_end

	def append_field_column(self, pagefield, for_mandatory):
		if not pagefield:
			return
		if for_mandatory and not pagefield.reqd:
			return
		if not for_mandatory and pagefield.reqd:
			return
		if pagefield.fieldname in ("parenttype", "trash_reason"):
			return
		if pagefield.hidden:
			return
		if (
			self.select_columns
			and pagefield.fieldname not in self.select_columns.get(pagefield.parent, [])
			and pagefield.fieldname != "name"
		):
			return

		self.tablerow.append("")
		self.fieldrow.append(pagefield.fieldname)
		self.labelrow.append(_(pagefield.label, context=pagefield.parent))
		self.mandatoryrow.append(pagefield.reqd and "Yes" or "No")
		self.typerow.append(pagefield.fieldtype)
		self.inforow.append(self.getinforow(pagefield))
		self.columns.append(pagefield.fieldname)

	def append_empty_field_column(self):
		self.tablerow.append("~")
		self.fieldrow.append("~")
		self.labelrow.append("")
		self.mandatoryrow.append("")
		self.typerow.append("")
		self.inforow.append("")
		self.columns.append("")

	@staticmethod
	def getinforow(pagefield):
		"""make info comment for options, links etc."""
		if pagefield.fieldtype == "Select":
			if not pagefield.options:
				return ""
			else:
				return _("One of") + ": %s" % ", ".join(filter(None, pagefield.options.split("\n")))
		elif pagefield.fieldtype == "Link":
			return "Valid %s" % pagefield.options
		elif pagefield.fieldtype == "Int":
			return "Integer"
		elif pagefield.fieldtype == "Check":
			return "0 or 1"
		elif pagefield.fieldtype in ["Date", "Datetime"]:
			return cstr(jingrow.defaults.get_defaults().date_format)
		elif hasattr(pagefield, "info"):
			return pagefield.info
		else:
			return ""

	def add_field_headings(self):
		if not self.export_without_column_meta:
			self.writer.writerow(self.tablerow)

		# Just include Labels in the first row
		self.writer.writerow(self.labelrow)

		if not self.export_without_column_meta:
			self.writer.writerow(self.fieldrow)
			self.writer.writerow(self.mandatoryrow)
			self.writer.writerow(self.typerow)
			self.writer.writerow(self.inforow)

		if self.template:
			self.writer.writerow([self.data_keys.data_separator])

	def add_data(self):
		from jingrow.query_builder import PageType

		if self.template and not self.with_data:
			return

		jingrow.permissions.can_export(self.parent_pagetype, raise_exception=True)

		# sort nested set pagetypes by `lft asc`
		order_by = None
		table_columns = jingrow.db.get_table_columns(self.parent_pagetype)
		if "lft" in table_columns and "rgt" in table_columns:
			order_by = f"`tab{self.parent_pagetype}`.`lft` asc"
		# get permitted data only
		self.data = jingrow.get_list(
			self.pagetype, fields=["*"], filters=self.filters, limit_page_length=None, order_by=order_by
		)

		for pg in self.data:
			op = self.pgs_to_export.get("op")
			names = self.pgs_to_export.get("name")

			if names and op:
				if op == "=" and pg.name not in names:
					continue
				elif op == "!=" and pg.name in names:
					continue
			elif names:
				try:
					sflags = self.pgs_to_export.get("flags", "I,U").upper()
					flags = 0
					for a in re.split(r"\W+", sflags):
						flags = flags | reflags.get(a, 0)

					c = re.compile(names, flags)
					m = c.match(pg.name)
					if not m:
						continue
				except Exception:
					if pg.name not in names:
						continue
			# add main table
			rows = []

			self.add_data_row(rows, self.pagetype, None, pg, 0)

			if self.all_pagetypes:
				# add child tables
				for c in self.child_pagetypes:
					if is_virtual_pagetype(c["pagetype"]):
						continue
					child_pagetype_table = PageType(c["pagetype"])
					data_row = (
						jingrow.qb.from_(child_pagetype_table)
						.select("*")
						.where(child_pagetype_table.parent == pg.name)
						.where(child_pagetype_table.parentfield == c["parentfield"])
						.orderby(child_pagetype_table.idx)
					)
					for ci, child in enumerate(data_row.run(as_dict=True)):
						self.add_data_row(rows, c["pagetype"], c["parentfield"], child, ci)
			for row in rows:
				self.writer.writerow(row)

	def add_data_row(self, rows, dt, parentfield, pg, rowidx):
		d = pg.copy()
		meta = jingrow.get_meta(dt)
		if self.all_pagetypes:
			d.name = f'"{d.name}"'

		if len(rows) < rowidx + 1:
			rows.append([""] * (len(self.columns) + 1))
		row = rows[rowidx]

		_column_start_end = self.column_start_end.get((dt, parentfield))

		if _column_start_end:
			for i, c in enumerate(self.columns[_column_start_end.start : _column_start_end.end]):
				df = meta.get_field(c)
				fieldtype = df.fieldtype if df else "Data"
				value = d.get(c, "")
				if value:
					if fieldtype == "Date":
						value = formatdate(value)
					elif fieldtype == "Datetime":
						value = format_datetime(value)
					elif fieldtype == "Duration":
						value = format_duration(value, df.hide_days)

				row[_column_start_end.start + i + 1] = value

	def build_response_as_excel(self):
		from jingrow.desk.utils import provide_binary_file
		from jingrow.utils.xlsxutils import make_xlsx

		filename = jingrow.generate_hash(length=10)
		with open(filename, "wb") as f:
			f.write(cstr(self.writer.getvalue()).encode("utf-8"))
		f = open(filename)
		reader = csv.reader(f)
		xlsx_file = make_xlsx(reader, "Data Import Template" if self.template else "Data Export")

		f.close()
		os.remove(filename)

		provide_binary_file(self.pagetype, "xlsx", xlsx_file.getvalue())

	def _append_name_column(self, dt=None):
		self.append_field_column(
			jingrow._dict(
				{
					"fieldname": "name" if dt else self.name_field,
					"parent": dt or "",
					"label": "ID",
					"fieldtype": "Data",
					"reqd": 1,
				}
			),
			True,
		)
