# -*- encoding: utf-8 -*-
# 项目：JDG-
# 模块名称：
# 描述：
# Copyright 2018 JDG <www.yunside.com>
# Created by wxh (xianhuo.weng@yunside.com) at  2019/8/9 - 10:23 AM

import requests
from odoo import fields, api, models
from odoo.exceptions import ValidationError
from werkzeug import url_encode
import base64
from xml.etree import ElementTree as ET
import logging
from collections import Counter
import json
import hashlib

import cStringIO
from PIL import Image

import os
from PyPDF2 import PdfFileReader, PdfFileWriter

_logger = logging.getLogger(__name__)


class SignUtil(models.TransientModel):
    _name = 'jd.digital.signature.util'

    @api.model
    def get_config(self):
        """
        获取电子签名供应商的 地址 柜员编码 渠道编码
        :return:
        """
        sign_config = self.env['ir.config_parameter'].sudo().get_param(
            'jdg.digital.signature.connection.config', '').strip()
        if sign_config:
            config_list = sign_config.split(',')
            url = config_list[0]
            operator_code = len(config_list) > 1 and config_list[1] or ''
            channel_code = len(config_list) > 2 and config_list[2] or ''
            return url, operator_code, channel_code
        else:
            raise ValidationError(u'缺少签名配置信息，请联系系统管理员')

    @api.model
    def fetch(self, param, is_operator_code=True, is_channel_code=True):
        """
        请求签名接口
        :param api: 签名api
        :param param: 无需xml化的简单参数
        :param xml_param: 需要xml化的参数
        :return:
        """

        url, operator_code, channel_code = self.get_config()

        if is_operator_code:
            param['operator_code'] = operator_code
        if is_channel_code:
            param['channel_code'] = channel_code

        request_url = url + '/PaperlessServer/PaperlessServlet'
        param = self.obj_under2camel(param)
        return requests.post(request_url, data=param)

    @api.model
    def compound_signature_auto_pdf(self, res_model, res_id, b64_pdf_origin):
        channel_code = self.get_config()[2]
        param = {
            'function_type': 'compoundSignatureAutoPdf',
            # 系统业务流水号
            # 'system_no': '201908171048255047508756198827',
            # 获取场景证书的方式，与证据证书签章策略文件配合使用。默认值为0。0：实时从CFCA CA服务申请下载场景证书；1：使用从CFCA CA服务预先申请下载并存储在本地的场景证书
            'scene_cert_channel': '0',
            # 获取时间戳的方式。默认值为0。0：实时访问CFCA 时间戳服务；1：使用从CFCA购置并在本地部署的时间戳服务器产品；
            'timestamp_channel': '0',
        }

        pdf_bean_xml = {
            'pdf_bean': {
                'pdf': b64_pdf_origin,
                'biz_serial_no': 'ORDER_NUMBER_001',
                'return_pdf_or_not': True,
            }
        }

        # 组装multi_data以及Proof xml节点
        signature_list = self.env['jd.digital.signature.status'].get_certificate_signature(res_model, res_id)
        multi_data = []
        compound_seal_strategy_xml = ''
        for signature in signature_list:
            file_data = signature['file_data'] or self.image_scale(signature['file_data'], signature['extension'])
            multi_data.append({
                'type': '2',
                'fileName': signature['file_name'],
                'fileData': file_data
            })

            # CJK Unified Ideographs 转 Kangxi Radicals
            signature['keyword'] = signature['keyword'].replace(u'\u4eba', u'\u2f08').replace(u'\u65b9', u'\u2f45'). \
                replace(u'\u884c', u'\u2f8f')

            compound_seal_strategy = {
                'request': {
                    'handwriting_image': file_data,
                    # 'seal_layer2_text': '翁仙活',
                    'seal_layer2_text_style': 'font-size:20;font-color:FF0000;width:100;',
                    # 'seal_person': u'1',
                    'seal_location': '',
                    'seal_reason': '',
                    #
                    # 'identification_type': 'Z',
                    # 'identification_no': '113000222000000000',

                    'key_alg': 'RSA',
                    'fill_opacity': '1.0',
                    'visible': 'true',

                    'type': '3',
                    'keyword': signature['keyword'],
                    'keyword_position_index': '1',
                    'page': '1',

                    'location_style': 'R',
                    'offset_x': '5',
                    'offset_y': '0',
                    'proof_hash_xml': ''
                }
            }

            elm = self.obj2xml_elm(compound_seal_strategy)

            # 增加 身份认证信息
            elm_identification_name = ET.Element('SealPerson')
            elm_identification_name.text = signature['identification']['seal_person']
            elm.append(elm_identification_name)

            elm_identification_type = ET.Element('IdentificationType')
            elm_identification_type.text = signature['identification']['id_type']
            elm.append(elm_identification_type)

            elm_identification_no = ET.Element('IdentificationNo')
            elm_identification_no.text = signature['identification']['id_number']
            elm.append(elm_identification_no)

            elm_proof = ET.Element('Proof', {
                'fileName': signature['file_name'],
                'hash': hashlib.sha1(file_data).hexdigest(),
                'type': '2',
                'descr': '',
            })
            elm.find('ProofHashXml').append(elm_proof)

            compound_seal_strategy_xml += ET.tostring(elm)

        compound_seal_strategy_xml = '<List>' + compound_seal_strategy_xml + '</List>'
        channel_bean_xml = {
            'channel': {
                'channel_code': channel_code
            }
        }

        param.update({
            'pdf_bean_xml': ET.tostring(self.obj2xml_elm(pdf_bean_xml)),
            'multiData': json.dumps(multi_data).replace(' ', ''),
            'compound_seal_strategy_xml': base64.b64encode(compound_seal_strategy_xml),
            'channel_bean_xml': self.obj2xml_b64(channel_bean_xml),
        })
        # 返回的结果可能是一个xml格式文件 或 base64 或 pdf文件
        result = self.fetch(param)

        # 处理返回的pdf文件
        # file_tmp = ''
        # for tmp in result.iter_content(10000):
        #     file_tmp += tmp
        # b64_pdf = base64.b64encode(file_tmp)

        elm = ET.fromstring(result.text)
        print result.text
        elm_pdf = elm.find('Pdf')
        elm_error_msg = elm.find('ErrorMessage')
        if elm_pdf is not None:
            b64_pdf = elm_pdf.text
            # pdf = base64.b64decode(b64_pdf)
            # play_file = open('/Users/xianhuoweng/signed.pdf', 'wb')
            # play_file.write(pdf)
            # play_file.close()
            return b64_pdf
        else:
            _logger.error(elm_error_msg.text)
            raise ValidationError(u'带证书电子签名错误：' + elm_error_msg.text)

    @api.model
    def infer_report_name(self, res_model):
        module = ''
        ir_model = self.env['ir.model'].sudo().search([('model', '=', res_model)], limit=1)
        if not ir_model:
            _logger.error(u'模型【%s】获取对应的【ir.model】记录不存在，report_name推断失败')
            return
        module_list = ir_model.modules.split(', ')
        if len(module_list) == 1:
            module = module_list[0]
        else:
            # 从fields推断
            fields_recs = self.env['ir.model.fields'].sudo().search([('model_id', '=', ir_model.id)])
            fields_module_list = []
            for field_rec in fields_recs:
                fields_module_list += [module for module in field_rec.modules.split(', ') if
                                       module != 'jd_base' and self.env['ir.actions.report.xml'].sudo().search(
                                           [('model', '=', res_model), ('report_name', 'ilike', module + '%')])]
            if len(fields_module_list) > 0:
                # 获取fields_module_list数组中数量最多的module
                module = Counter(fields_module_list).most_common(1)[0][0]

        if not module:
            _logger.error(u'模型【%s】module获取不成功，report_name推断失败' % res_model)
            return

        report_name = '%s.pdf_order_%s' % (module, res_model.replace('.', '_'))
        return report_name

    @api.model
    def create_preview_pdf(self, res_model, res_id, report_name=None):
        """
        创建单据预览pdf 若存在则更新
        :param res_model:
        :param res_id:
        :param report_name:
        :return:
        """
        res_id = int(res_id)
        rec_order = self.env[res_model].sudo().browse(res_id)
        if report_name is None:
            report_name = self.infer_report_name(res_model)
        pdf = self.env['report'].sudo().get_pdf([res_id], report_name)
        b64_pdf = base64.encodestring(pdf)

        preview_attachment = self.env['ir.attachment'].sudo().search(
            [('res_model', '=', res_model), ('res_id', '=', res_id), ('name', 'ilike', '%_preview.pdf')], limit=1,
            order='write_date desc')
        # 存在则更新
        if preview_attachment:
            preview_attachment.datas = b64_pdf
            return preview_attachment.id
        else:
            attachment_name = u'【%s】%s_preview.pdf' % (rec_order._description, rec_order.number)
            rec = self.env['ir.attachment'].sudo().create({
                'name': attachment_name,
                'type': 'binary',
                'datas': b64_pdf,
                'datas_fname': attachment_name,
                'store_fname': attachment_name,
                'res_model': res_model,
                'res_id': res_id,
                # 'res_field': 'attachment_pdf',
                'mimetype': 'application/pdf'
            })
            return rec.id

    @api.model
    def create_local_signed_pdf(self, rec_order, b64_pdf):
        """
        本地合成签名pdf 若存在则更新，不存在则创建
        :param rec_order:
        :param b64_pdf:
        :return:
        """
        # 更新签名图片状态
        res_model = rec_order._name
        res_id = rec_order.id

        # 存在则更新，否则新建
        signed_attachment = self.env['ir.attachment'].sudo().search(
            [('res_model', '=', res_model), ('res_id', '=', res_id), ('name', 'ilike', '%_signed.pdf')])
        if signed_attachment:
            signed_attachment.datas = b64_pdf
            return signed_attachment.id
        else:
            attachment_name = u'【%s】%s_signed.pdf' % (rec_order._description, rec_order.number)
            rec = self.env['ir.attachment'].sudo().create({
                'name': attachment_name,
                'type': 'binary',
                'datas': b64_pdf,
                'datas_fname': attachment_name,
                'store_fname': attachment_name,
                'res_model': res_model,
                'res_id': res_id,
                'mimetype': 'application/pdf'
            })
            return rec.id

    @api.model
    def create_certificate_signed_pdf(self, rec_order, b64_pdf_origin):
        # 带证书单据若已完成签名则不允许再更新
        res_model = rec_order._name
        res_id = rec_order.id
        if self.is_signature_finished(res_model, res_id):
            _logger.error(u'带证书单据【%s】%s已完成签名，不允许重复' % (rec_order._description, rec_order.number))
            return

        # 复合签章接口
        b64_pdf = self.compound_signature_auto_pdf(res_model, res_id, b64_pdf_origin)
        if not b64_pdf:
            _logger.error(u'带证书单据【%s】%s自动化复合签章失败' % (rec_order._description, rec_order.number))
            return
        attachment_name = u'【%s】%s_signed.pdf' % (rec_order._description, rec_order.number)
        rec = self.env['ir.attachment'].sudo().create({
            'name': attachment_name,
            'type': 'binary',
            'datas': b64_pdf,
            'datas_fname': attachment_name,
            'store_fname': attachment_name,
            'res_model': res_model,
            'res_id': res_id,
            'mimetype': 'application/pdf'
        })
        return rec.id

    @api.model
    def create_signed_pdf(self, res_model, res_id, report_name=None):
        """
        创建单据签名pdf 若存在则更新
        :param res_model:
        :param res_id:
        :param report_name:
        :return:
        """
        res_id = int(res_id)
        rec_order = self.env[res_model].sudo().browse(res_id)
        # 若所有签名已完成则进行pdf合成 - 二次判断
        is_finish, missing_info = self.env['jd.digital.signature.status'].is_signature_upload_finish(res_model, res_id)
        if not is_finish:
            _logger.error(u'【%s】%s部分人员未签字，生成签名单据失败\r\n%s' % (rec_order._description, rec_order.number, missing_info))
            return

        if report_name is None:
            module = ''
            ir_model = self.env['ir.model'].sudo().search([('model', '=', res_model)], limit=1)
            if not ir_model:
                _logger.error(u'模型【%s】获取对应的【ir.model】记录不存在，pdf生成失败')
                return
            module_list = ir_model.modules.split(', ')
            if len(module_list) == 1:
                module = module_list[0]
            else:
                # 从fields推断
                fields_recs = self.env['ir.model.fields'].sudo().search([('model_id', '=', ir_model.id)])
                fields_module_list = []
                for field_rec in fields_recs:
                    fields_module_list += [module for module in field_rec.modules.split(', ') if
                                           module != 'jd_base']
                if len(fields_module_list) > 0:
                    module = Counter(fields_module_list).most_common(1)[0][0]

            if not module:
                _logger.error(u'模型【%s】推断module失败，pdf生成失败' % res_model)
                return

            report_name = '%s.pdf_order_%s' % (module, res_model.replace('.', '_'))

        # 获取最新pdf模板
        pdf = self.env['report'].sudo().get_pdf([res_id], report_name)
        b64_pdf = base64.encodestring(pdf)

        order_range = rec_order.get_signature_range()
        config = self.env['jd.digital.signature.config'].sudo().search(
            [('model_id.model', '=', res_model), ('range', '=', order_range)])
        if not config:
            _logger.error(u'【%s】没有维护电子签名配置信息' % self.env[res_model]._description)

        # 更新签名状态
        if not config.is_certificate:
            attachment_id = self.create_local_signed_pdf(rec_order, b64_pdf)
        else:
            attachment_id = self.create_certificate_signed_pdf(rec_order, b64_pdf)

        if attachment_id:
            self.env['jd.digital.signature.status'].update_status_signed(res_model, res_id, is_signed=True)
            return attachment_id

    @api.model
    def is_signature_finished(self, res_model, res_id):
        # 查看是否存在签名文件： 【XXX单据】_signed.pdf （因为单据名称可能会更改，只判断后缀_signed.pdf即可）
        signed_attachment = self.env['ir.attachment'].sudo().search(
            [('res_model', '=', res_model), ('res_id', '=', res_id), ('name', 'ilike', '%_signed.pdf')],
            order='create_date desc', limit=1)
        if signed_attachment:
            return signed_attachment.id

    @api.model
    def get_signed_attachment(self, res_model, res_id):
        signed_attachment = self.env['ir.attachment'].sudo().search(
            [('res_model', '=', res_model), ('res_id', '=', res_id), ('name', 'ilike', '%_signed.pdf')],
            order='create_date desc', limit=1)
        if signed_attachment:
            return signed_attachment

    #############################################################################
    ############################# xml 工具方法 ###################################
    #############################################################################
    def _dict2xml_elm(self, obj):
        """
        字典的递归解析 只用于方法obj2xml_elm
        :param obj:
        :return:
        """
        # 不处理字典value为数组的情况
        if type(obj) != dict:
            raise ValidationError(u'方法dict2xml_elm的参数必须为字典')
        elm_list = []
        for key, value in obj.items():
            if type(value) == dict:
                parent = ET.Element(key)
                sub_elm_list = self._dict2xml_elm(value)
                for sub_elm in sub_elm_list:
                    parent.append(sub_elm)
                elm_list.append(parent)
            else:
                # 简单类型
                elm = ET.Element(key)
                elm.text = str(value)
                elm_list.append(elm)
        return elm_list

    @api.model
    def obj2xml_elm(self, obj):
        """
        对象转换成etree的elm
        :param obj:
        :return:
        """

        if type(obj) == dict:
            # 最后结果为长度为1 的elm数组
            obj = self.obj_under2pascal(obj)
            return self._dict2xml_elm(obj) and self._dict2xml_elm(obj)[0]
        elif type(obj) == list:
            # 数组内只有字典 没有数组
            root = ET.Element('List')
            for item in obj:
                # 数组内只嵌套字典
                if type(item) == dict:
                    # 字典内不嵌套数组 只有字典或基本类型 最后收束成一个elm
                    elm = self.obj2xml_elm(item)
                    if len(elm) > 0:
                        root.append(elm)
            return root

    @api.model
    def obj2xml_b64(self, obj):
        """
        对象转换成xml的base64文件
        []转List标签 {}key为tag value为text
        若需要额外增加属性 应该使用elm.find(xpath相对路径).set(key, value)
        若需要为某个元素增加多个相同元素应该使用elm.find(xpaht相对路径).append(ET.Element(tag, attr))
        :param obj:
        :return:
        """
        elm = self.obj2xml_elm(obj)
        if elm is not None and elm is not False:
            result = ET.tostring(elm)
        else:
            result = ''
        return base64.b64encode(result)

    @api.model
    def make_img_xml_base64(self, img_list):
        """
        测试图片xml
        :param img_list:
        :return:
        """
        root = ET.Element('ImageList', attrib={'hashAlg': 'sha1', 'savedBizXmlId': '', 'savedTimeIncrement': ''})
        for img in img_list:
            sub_el = ET.SubElement(root, 'Image')
            pascal_img = self.obj_under2pascal(img)
            for img_key, img_value in pascal_img.items():
                ET.SubElement(sub_el, img_key).text = str(img_value)
        return base64.b64encode(ET.tostring(root))

    def under2camel(self, name):
        list_sub = name.split('_')
        list_sub_capital = [sub.capitalize() for sub in list_sub[1:]]
        list_sub_capital.insert(0, list_sub[0])
        return ''.join(list_sub_capital)

    def obj_under2camel(self, obj):
        """
        浅层 下划线转转驼峰命名，用于http请求参数
        :param obj: 下划线命名的字典
        :return: 驼峰命名的字典
        """
        if type(obj) != dict:
            raise ValidationError(u'命名格式转换 方法的参数必须为字典')
        obj_result = {}
        for key in obj.keys():
            obj_result[self.under2camel(key)] = obj[key]
        return obj_result

    def under2pascal(self, name):
        list_sub = name.split('_')
        list_sub = [sub.capitalize() for sub in list_sub]
        return ''.join(list_sub)

    def obj_under2pascal(self, obj):
        """
        深层 下划线转pascal命名
        :param obj: 下划线命名的字典
        :return: 驼峰命名的字典
        """
        if type(obj) == dict:
            result = {}
            for key, value in obj.items():
                key_pascal = self.under2pascal(key)
                if type(value) == dict:
                    handle_result = self.obj_under2pascal(value)
                    result[key_pascal] = handle_result
                elif type(value) == list:
                    result[key_pascal] = self.obj_under2pascal(value)
                else:
                    # 除数组、字典类型外的基本类型 则直接赋值
                    result[key_pascal] = value
            return result
        if type(obj) == list:
            # list 里面不再嵌套list
            result = []
            for item in obj:
                if type(item) == dict:
                    result.append(self.obj_under2pascal(item))
                else:
                    # 非字典类型的不做处理 直接添加
                    result.append(item)
            return result
        else:
            raise ValidationError(u'方法obj_under2pascal参数必须为字典或数组')

    @api.model
    def image_scale(self, b64_img, extension, width_target=60):
        """
        图片压缩
        :param b64_img:
        :param extension:
        :param width_target:
        :return:
        """
        origin_file = cStringIO.StringIO(base64.b64decode(b64_img))
        img = Image.open(origin_file)
        w, h = img.size
        height_target = float(width_target) / w * h
        img = img.resize((int(width_target), int(height_target)), Image.ANTIALIAS)
        image_buffer = cStringIO.StringIO()
        dpi = 300.0
        img.save(image_buffer, extension, quality=100, dpi=(dpi, dpi))
        # img.save('/Users/xianhuoweng/test.png', extension, quality=100, dpi=(dpi, dpi))

        return base64.b64encode(image_buffer.getvalue())

    @api.model
    def discard_pdf(self, b64_pdf):
        # b64_pdf = self.env['ir.attachment'].browse(1075).datas
        origin_file = cStringIO.StringIO(base64.b64decode(b64_pdf))
        pdf_reader = PdfFileReader(origin_file)
        pdf_writer = PdfFileWriter()
        watermark_path = os.path.join(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')), 'assets',
                                      'discard.pdf')
        print watermark_path
        watermark_reader = PdfFileReader(watermark_path)
        watermark_page = watermark_reader.getPage(0)

        for page in range(pdf_reader.getNumPages()):
            page = pdf_reader.getPage(page)
            page.mergePage(watermark_page)
            pdf_writer.addPage(page)

        image_buffer = cStringIO.StringIO()
        pdf_writer.write(image_buffer)

        # with open('/Users/xianhuoweng/test.pdf', 'wb') as out:
        #     pdf_writer.write(out)

        return base64.b64encode(image_buffer.getvalue())
