"""A report generating system for PyPPL"""
import sys
from time import time
from pathlib import Path
import toml
from cmdy import CmdyReturnCodeException
from diot import Diot
from pyppl.plugin import hookimpl
from pyppl.logger import logger
from pyppl.jobmgr import STATES
from pyppl.utils import fs, format_secs
from pyppl.exception import ProcessAttributeError
from .report import Report

__version__ = "0.5.0"

def report_template_converter(value):
	if value and value.startswith('file:'):
		scriptpath = Path(value[5:])
		if not scriptpath.is_absolute():
			from inspect import getframeinfo, stack

			# 0: .../pyppl_report/pyppl_report/__init__.py
			# 1: ...//PyPPL/pyppl/plugin.py
			# 2: ...//pyppl_report/pyppl_report/__init__.py
			# 3: .../pluggy/callers.py
			# 4: .../pluggy/manager.py
			# 5: .../pluggy/manager.py
			# 6: .../pluggy/hooks.py
			# 7: .../PyPPL/pyppl/proc.py
			# 8: .../PyPPL/pyppl/proc.py
			# 9: .../PyPPL/pyppl/pyppl.py
			# 10: /file/define/the/report
			# if it fails in the future, check if the callstacks changed from pluggy
			caller = getframeinfo(stack()[10][0])
			scriptdir = Path(caller.filename).parent.resolve()
			scriptpath = scriptdir / scriptpath
		if not scriptpath.is_file():
			raise ProcessAttributeError(
				'Report template file does not exist: %s' % scriptpath)
		return "file:%s" % scriptpath
	return value

@hookimpl
def setup(config):
	"""Setup the plugin"""
	config.config.report_template = ''
	config.config.report_envs = Diot(level = 2, pre = '', post = '')

@hookimpl
def logger_init(logger): # pylint: disable=redefined-outer-name
	logger.add_level('report')

@hookimpl
def proc_init(proc):
	proc.add_config('report_template', default = '', converter = report_template_converter)
	proc.add_config('report_envs', default = Diot(), converter = lambda envs: envs or Diot())

@hookimpl
def proc_postrun(proc, status):
	"""Generate report for the process"""
	# skip if process failed or cached
	report_file = proc.workdir.joinpath('proc.report.md')
	template    = proc.config.report_template
	if status != 'succeeded' or not template or (
		all(job.state == STATES.DONECACHED for job in proc.jobs) and \
		report_file.is_file()):
		return

	fs.remove(report_file)
	logger.debug('Rendering report template ...', proc = proc.id)
	if template.startswith ('file:'):
		tplfile = Path(template[5:])
		logger.debug("Using report template: %s", tplfile, proc = proc.id)
		template = tplfile.read_text()

	template = proc.template(template, **proc.envs)
	rptdata  = Diot(jobs = [], proc = proc, args = proc.args)
	for job in proc.jobs:
		datafile = job.dir / 'output/job.report.data.toml'
		data = job.data.copy()
		if datafile.is_file():
			with datafile.open() as fdata:
				data.update(toml.load(fdata))
		rptdata.jobs.append(data)

	rptenvs  = Diot(level = 2, pre = '', post = '', title = proc.desc)
	rptenvs.update(proc.config.report_envs)
	rptdata.title = rptenvs.title
	try:
		reportmd = template.render(rptdata)
	except Exception as exc:
		raise RuntimeError(
			'Failed to render report markdown for process: %s' % (proc)) from exc
	reportmd = reportmd.splitlines()

	codeblock = False
	appendix  = False
	for i, line in enumerate(reportmd):
		if line.startswith('## Appendix') or appendix:
			appendix = True
			continue
		if line.startswith('#') and not codeblock:
			reportmd[i] = '#' * (rptenvs.level - 1) + line
		elif codeblock:
			if line.startswith('```') and len(line) - len(line.lstrip('`')) == codeblock:
				codeblock = False
		elif line.startswith('```'):
			codeblock = len(line) - len(line.lstrip('`'))

	report_file.write_text(
		proc.template(rptenvs.pre, **proc.envs).render(rptdata) + '\n\n' +
		'\n'.join(reportmd) + '\n\n' +
		proc.template(rptenvs.post, **proc.envs).render(rptdata) + '\n'
	)

def report(ppl,
	outfile = None,
	title      = 'A report generated by pipeline powered by PyPPL',
	standalone = True,
	template   = False,
	filters    = False):
	"""@API
	Generate report for the pipeline.
	Currently only HTML format supported.
	@params:
		outfile (file): The report file, could be a directory, then the report file will be:
			- `<pipeline name>.report.html`
		title (str): The title of the report.
			- Default: 'A report generated by pipeline powered by PyPPL'
		standalone (bool): A standalone html file? Default: True
			- Otherwise, static files, including js/css/images will be store in a separated directory.
	"""
	timer   = time()
	default_basename = '%s.report.html' % ppl.name
	if not outfile:
		outfile = Path('.').joinpath(default_basename)
	elif Path(outfile).is_dir():
		outfile = outfile.joinpath(default_basename)

	logger.report('Generating report using pandoc ...')
	reports = [proc.workdir.joinpath('proc.report.md') for proc in ppl.procs
		if proc.workdir.joinpath('proc.report.md').exists()]
	# force to add a title.
	title = '# ' + title if not title.startswith('#') else title
	cmd = Report(reports, outfile, title).generate(standalone, template, filters)
	try:
		logger.debug('Running: ' + cmd.cmd)
		cmd.run()
		logger.report('Report generated: %s', outfile)
		logger.report('Time elapsed: %s', format_secs(time() - timer))
	except CmdyReturnCodeException as ex:
		logger.error(str(ex))
		sys.exit(1)

@hookimpl
def pyppl_init(ppl):
	"""Add method to PyPPL instance"""
	ppl.add_method(report, require = 'run')
