# /user/bin/env python3
# -*- coding: utf-8 -*-
"""Report maker class."""

import os
import re
import tempfile
from datetime import datetime
import traceback
from pprint import pformat

import logging

from lc18report_dev.template import Template
from lc18report_dev.sample import Sample
from lc18report_dev.template import Config
from lc18report_dev.info import Info
from lc18report_dev.exceptions import DuplicatedPcError, GeneFormatError, NoPcError
from lc18report_dev.exceptions import MissingYueDongRecordError
from lc18report_dev.exceptions import WrongKdbFormatError
from lc18report_dev.exceptions import NoKdbRecordFoundError
from lc18report_dev.exceptions import MissingResidentialIdError
from lc18report_dev.aescipher import AESCipher
from lc18report_dev.yuedong import YueDong

DATA_ENCRYPTION_KEY = r'w!z%C*F-JaNdRgUkXp2s5u8x/A?D(G+K'
logger = logging.getLogger(__name__)

class ReportMaker:
    """Class for report making."""

    _cipher = AESCipher(DATA_ENCRYPTION_KEY)

    def __init__(self, force_mode=False, en_mode=False):
        """Report maker init function.

        Args:
            template_file (str): report template file
            config_file (str): report config file
        """
        self.kdb_file_path: str = ''
        self.info = Info()
        self.template = Template(report_type='lc')
        self.yuedong_template = Template(report_type='lc-yuedong')
        self._force_mode = force_mode
        self._en_mode = en_mode
        self.var_gather: list = []
        self.max_vaf: float = 0
        self.avg_vaf: float = 0
        self.selected_sample_list: list = None

    @property
    def config(self) -> Config:
        return self.template.config

    def set_kdb_file_current(self, kdb_file: str) -> None:
        '''指定知识库文件的路径和路径无效时用的版本'''
        self.kdb_file_path = kdb_file

    def set_base_file(self, base_info_file: str, record_xlsx:str) -> None:
        self.base_info_file: str = base_info_file
        self.record_xlsx: str = record_xlsx

    @staticmethod
    def set_db(db_file: str) -> None:
        YueDong.set_db_file(db_file)

    def set_template_version(self, template_version: str, yuedong_template_version: str) -> None:
        """单独设置阅动和非阅动使用的模板版本"""
        self.template.set_version(template_version, self._en_mode)
        self.yuedong_template.set_version(yuedong_template_version)

    def set_info(
            self, merged_info_file: str = '',
            sample_info_file: str = '',
            lab_info_file: str = '',
    ) -> None:
        if merged_info_file:
            self.info.set_merged_info_file(merged_info_file)
        else:
            self.info.set_sample_lab_info_files(
                sample_info_file,
                lab_info_file
            )

    def set_selected_samples(self, samples: str) -> None:
        '''指定要分析的样本，写入self.selected_sample_list

        samples
          多个样本用英文逗号`,`(分隔符)分开；单个样本可以添加分隔符，或者不添加也可以
        '''
        if ',' in samples:
            sample_list = [i for i in samples.split(",") if i]
            # 把编号全部转换成文库编号，Lib开头的
            self.selected_sample_list = list(map(lambda name: re.sub(r"^(A|BD), ", "Lib", name), sample_list))
        elif samples == 'ALL' or samples == "":
            self.sample_list = None
        else:
            # 把编号转换成文库编号，Lib开头的
            samples = re.sub(r"^(A|BD), ", "Lib", samples)
            self.selected_sample_list = [samples,]

    @staticmethod
    def _json2data(data_path: str, json_str: str) -> None:
        if os.path.isfile(data_path):
            raise FileExistsError(f'Data file already exist:\n{data_path}')
        with open(data_path, 'wb') as outf:
            outf.write(ReportMaker._cipher.encrypt(json_str))

    @staticmethod
    def _data2json(data_path: str) -> str:
        if not os.path.isfile(data_path):
            raise FileNotFoundError(f'Data file cannot be found:\n{data_path}')
        with open(data_path, 'rb') as inf:
            return ReportMaker._cipher.decrypt(inf.read())

    @staticmethod
    def _report_path(output_dir, sample_id, lib_id, name, file_type, timed, rprt_type):
        if file_type == 'data':
            return os.path.join(output_dir, f'{lib_id}.{file_type}')
        if timed:
            now = datetime.now()
            today = now.strftime(r"%Y.%m.%d-%H.%M.%S")
            return os.path.join(
                output_dir,
                f'{today}_{sample_id}_{name}_{rprt_type}.{file_type}'
            )
        return os.path.join(
            output_dir, f'{sample_id}_{name}_{rprt_type}.{file_type}'
        )

    def _sample2report(self, samples, output_dir: str, out_type='docx', timed=True) -> int:
        low_cov_sample = []
        gene_err_sample = []
        no_kdb_record_sample = []
        yuedong_no_id_sample = []
        yuedong_missing_record_sample = []
        unknow_err_sample = []
        kdb_format_error = False
        no_pc_error = False
        duplicated_pc_error = False
        err_msg = ''
        return_code = 0

        for sample in samples:
            try:
                sample.set_info(self.info)

                if not sample.is_lc():
                    logger.info(f'样本 {sample.lib_id} 不是肺癌样本，跳过！')
                    continue
                logger.info(f"正在处理样本 {sample.lib_id} ...")

                sample.set_versions(
                    self.template.version,
                    self.yuedong_template.version,
                    en_mode=self._en_mode)
                logger.info(f"ReportMaker._sample2report: kdb_file_path {self.kdb_file_path} ...")
                sample.set_kdb_file(self.kdb_file_path)
                output_path = self._report_path(
                    output_dir, sample.id, sample.lib_id, sample.name,
                    out_type, timed, sample.report_type_zh)
                # 阅动LC报告
                if sample.report_type == 'lc-yuedong':
                    img_file = tempfile.NamedTemporaryFile('w', suffix='.png', delete=False)
                    img_file.close()
                    report_content = YueDong.get_yuedong_content(self,
                        sample, img_file.name, force_mode=self._force_mode)
                    self.yuedong_template.make_report(
                        output_path, report_content)
                    os.remove(img_file.name)
                    YueDong.save_sample(sample)
                # 非阅动报告
                elif out_type == 'docx':
                    report_content = sample.get_report_content(
                        force_mode=self._force_mode, en_mode=self._en_mode)
                    self.template.make_report(output_path, report_content)
                elif out_type == 'data':
                    report_json = sample.get_report_json(
                        force_mode=self._force_mode, en_mode=self._en_mode)
                    self._json2data(output_path, report_json)
                if sample.cov_pos < 100:
                    logger.warning(f'Positive control has low coverage.')
                if sample.cov_t1_t2 < 100:
                    low_cov_sample.append(sample.lib_id)
                    logger.warning(f'Sample {sample.lib_id} has low coverage.')
            except GeneFormatError:
                gene_err_sample.append(sample.lib_id)
                err_msg = 'Gene format error during report generation found.'
                err_msg += f' Sample: {sample.lib_id}\n'
                err_msg += traceback.format_exc()
            except NoKdbRecordFoundError:
                no_kdb_record_sample.append(sample.lib_id)
                err_msg = 'Variant with missing KDB record found.'
                err_msg += f' Sample: {sample.lib_id}\n'
                err_msg += traceback.format_exc()
            except MissingResidentialIdError:
                yuedong_no_id_sample.append(sample.lib_id)
                err_msg = 'YueDong sample with missing residential id found.'
                err_msg += f' Sample: {sample.lib_id}\n'
                err_msg += traceback.format_exc()
            except MissingYueDongRecordError:
                yuedong_missing_record_sample.append(sample.lib_id)
                err_msg = 'YueDong sample with missing previous record found.'
                err_msg += f' Sample: {sample.lib_id}\n'
                err_msg += traceback.format_exc()
            except WrongKdbFormatError:
                sample.set_kdb_format_error()
                kdb_format_error = True
                err_msg = 'KDB file format wrong.'
                err_msg += traceback.format_exc()
            except NoPcError:
                sample.set_error(7)
                no_pc_error = True
                err_msg = 'No pc found.'
                err_msg += traceback.format_exc()
            except DuplicatedPcError:
                sample.set_error(8)
                duplicated_pc_error = True
                err_msg = 'Duplicated pc found.'
                err_msg += traceback.format_exc()
            except Exception as e:
                sample.set_unknown_error()
                unknow_err_sample.append(sample.lib_id)
                err_msg = 'Unknow error during report generation.\n'
                for arg in e.args:
                    err_msg += str(arg) + '\n'
                err_msg += f' Sample: {sample.lib_id}\n'
                err_msg += traceback.format_exc()
            finally:
                if err_msg:
                    err_msg += pformat(vars(sample), indent=4)
                    logger.error(err_msg)

        for sample in samples:
            logger.info(str(sample.to_log()))

        if low_cov_sample:
            return_code = 1
            logger.error(
                'Following sample(s) not having 100% T1 T2 coverage: ' +
                ','.join(low_cov_sample)
            )

        if gene_err_sample:
            return_code = 2
            logger.error(
                'Following sample(s) have wrong gene format: ' +
                ','.join(gene_err_sample)
            )

        if no_kdb_record_sample:
            return_code = 3
            logger.error(
                'Following sample(s) with missing kdb record: ' +
                ','.join(no_kdb_record_sample)
            )

        if yuedong_no_id_sample:
            return_code = 4
            logger.error(
                'Following yuedong sample(s) with missing residential id: ' +
                ','.join(yuedong_no_id_sample)
            )

        if yuedong_missing_record_sample:
            return_code = 5
            logger.error(
                'Following yuedong sample(s) with missing previous record: ' +
                ','.join(yuedong_missing_record_sample)
            )

        if kdb_format_error:
            return_code = 6
            logger.error('KDB file used contains wrong format.')

        if no_pc_error:
            return_code = 7
            logger.error('No pc found.')

        if duplicated_pc_error:
            return_code = 8
            logger.error('Duplicated pc found.')

        if unknow_err_sample:
            return_code = 99
            logger.error(
                'Following sample(s) with unknow error: ' +
                ','.join(unknow_err_sample)
            )

        return return_code

    def make(self, input_xlsx, output_dir, out_type='docx', timed=True) -> int:
        """Generate report docx or json from input xlsx.

        Args:
            input_xlsx (str): input xlsx file
            output_dir (str): output file dir
            out_type (str): output file type, docx or json
        """
        Sample.set_config(self.config)
        samples: list[Sample] = Sample.parse_output_xlsx(self, input_xlsx)

        # 只要用户指定的样本，而非全部样本
        # selected_sample_list为None，表示全部样本
        if self.selected_sample_list is not None:
            samples = [i for i in samples if i.lib_id in self.selected_sample_list]

            # 检查用户是否填错样本编号或填多了，不在这个分析批次
            sample_ids = [i.lib_id for i in samples]
            missing_samples = [i for i in self.selected_sample_list if i not in sample_ids]
            if missing_samples:
                logger.error(f"样本{'、'.join(missing_samples)}未在该批次结果文件中找到")
                exit(-1)

        self.input_xlsxs = input_xlsx
        return self._sample2report(samples, output_dir, out_type, timed)

    def data2report(self, data_path, output_path) -> int:
        return_code = 0
        report_content = {}
        err_msg = ''
        try:
            report_json = self._data2json(data_path)
            report_content = Sample.report_json_to_content(report_json)
            template_version = report_content['template_version']
            yuedong_template_version = report_content['yuedong_template_version']
            self.set_template_version(template_version, yuedong_template_version)
            self.template.make_report(output_path, report_content)
        except Exception:
            err_msg = 'Unknow error during report generation.'
            err_msg += f' Sample: {report_content["id"]}\n'
            err_msg += traceback.format_exc()
            return_code = 1
        finally:
            if err_msg:
                err_msg += pformat(report_content, indent=4)
                logger.error(err_msg)

        return return_code
