"""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):
	"""Convert relative path of a template to absolute"""
	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: .../site-packages/diot.py
			# 3: .../pyppl_report/tests/test_report.py
			# if it fails in the future, check if the callstacks changed from pluggy
			caller = getframeinfo(stack()[3][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
	"""Add log level"""
	logger.add_level('report')

@hookimpl
def proc_init(proc):
	"""Add config"""
	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')

@hookimpl
def cli_addcmd(commands):
	"""Add subcommand to pyppl"""
	commands.report = 'Convert a Markdown file to report.'
	params = commands.report
	params['in'].required = True
	params['in'].desc = 'The input file.'
	params.i = params['in']
	params.i.type = list
	params.out.desc = 'The output file. Default: <in>.html'
	params.out.callback = lambda opt, ps: opt.setValue(Path(ps.i.value[0]).with_suffix('.html')) \
		if not opt.value else None
	params.o = params.out
	params.nonstand = False
	params.nonstand.desc = 'Non-standalone mode. ' + \
		'Save static files in `<filename of --out>.files` separately.'
	params.n = params.nonstand
	params.filter = []
	params.filter.desc = 'The filters for pandoc'
	params.title = 'Untitled document'
	params.title.desc = '''The title of the document.
	If the first element of the document is H1 (#), this will be ignored and the text of H1 will be used as title.
	If the title is specified as "# Title", then a title will be added anyway.
	'''
	params.template = 'bootstrap'
	params.template.desc = 'The template to use. ' + \
		'Either standard template name or full path to template file.'

@hookimpl
def cli_execcmd(command, opts):
	"""Run the command"""
	if command == 'report':
		cmd = Report(opts.i, opts.o, opts.title).generate(not opts.nonstand, opts.template, opts.filter)
		try:
			logger.info('Running: ' + cmd.pipedcmd)
			cmd.run()
			logger.info('Report generated: ' + str(opts.o))
		except CmdyReturnCodeException as ex:
			logger.error(str(ex))
			sys.exit(1)
