# -*- coding: utf-8 -*-
from datetime import datetime, timedelta
import hashlib
import base64
import uuid

import requests
from lxml import etree


class Autenticacion():
    SOAP_URL = 'https://cfdidescargamasivasolicitud.clouda.sat.gob.mx/Autenticacion/Autenticacion.svc'
    SOAP_ACTION = 'http://DescargaMasivaTerceros.gob.mx/IAutenticacion/Autentica'
    NSMAP = {
        's': 'http://schemas.xmlsoap.org/soap/envelope/',
        'u': 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'
    }
    S_NSMAP = {
        'o': 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
    }

    def __init__(self, fiel):
        self.fiel = fiel
    
    def __generar_soapreq__(self, id):
        date_created = datetime.utcnow()
        date_expires = date_created + timedelta(seconds=300)
        date_created = date_created.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
        date_expires = date_expires.strftime('%Y-%m-%dT%H:%M:%S.%fZ')

        soap_req = etree.Element('{{{}}}{}'.format(self.NSMAP['s'], 'Envelope'), nsmap=self.NSMAP)
        
        header = etree.SubElement(soap_req, '{{{}}}{}'.format(self.NSMAP['s'], 'Header'))
        
        security = etree.SubElement(header, '{{{}}}{}'.format(self.S_NSMAP['o'], 'Security'), nsmap=self.S_NSMAP)
        security.set('{{{}}}{}'.format(self.NSMAP['s'], 'mustUnderstand'), '1')

        timestamp = etree.SubElement(security, '{{{}}}{}'.format(self.NSMAP['u'], 'Timestamp'))
        timestamp.set('{{{}}}{}'.format(self.NSMAP['u'], 'Id'), '_0')
        
        created = etree.SubElement(timestamp, '{{{}}}{}'.format(self.NSMAP['u'], 'Created'))
        created.text = date_created
        
        expires = etree.SubElement(timestamp, '{{{}}}{}'.format(self.NSMAP['u'], 'Expires'))
        expires.text = date_expires
        
        binarysecuritytoken = etree.SubElement(security, '{{{}}}{}'.format(self.S_NSMAP['o'], 'BinarySecurityToken'))
        binarysecuritytoken.set('{{{}}}{}'.format(self.NSMAP['u'], 'Id'), str(id))
        binarysecuritytoken.set('ValueType', 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3')
        binarysecuritytoken.set('EncodingType', 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary')

        signature = etree.SubElement(security, 'Signature', nsmap={None: 'http://www.w3.org/2000/09/xmldsig#'})

        signedinfo = etree.SubElement(signature, 'SignedInfo', nsmap={None: 'http://www.w3.org/2000/09/xmldsig#'})

        canonicalizationmethod = etree.SubElement(signedinfo, 'CanonicalizationMethod')
        canonicalizationmethod.set('Algorithm', 'http://www.w3.org/2001/10/xml-exc-c14n#')

        signaturemethod = etree.SubElement(signedinfo, 'SignatureMethod')
        signaturemethod.set('Algorithm', 'http://www.w3.org/2000/09/xmldsig#rsa-sha1')

        reference = etree.SubElement(signedinfo, 'Reference')
        reference.set('URI', '#_0')

        transforms = etree.SubElement(reference, 'Transforms')

        transform = etree.SubElement(transforms, 'Transform')
        transform.set('Algorithm', 'http://www.w3.org/2001/10/xml-exc-c14n#')

        digestmethod = etree.SubElement(reference, 'DigestMethod')
        digestmethod.set('Algorithm', 'http://www.w3.org/2000/09/xmldsig#sha1')

        digestvalue = etree.SubElement(reference, 'DigestValue')

        signaturevalue = etree.SubElement(signature, 'SignatureValue')

        keyinfo = etree.SubElement(signature, 'KeyInfo')

        securitytokenreference = etree.SubElement(keyinfo, '{{{}}}{}'.format(self.S_NSMAP['o'], 'SecurityTokenReference'))

        reference = etree.SubElement(securitytokenreference, '{{{}}}{}'.format(self.S_NSMAP['o'], 'Reference'))
        reference.set('ValueType', 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3')
        reference.set('URI', '#{}'.format(id))

        body = etree.SubElement(soap_req, '{{{}}}{}'.format(self.NSMAP['s'], 'Body'))

        etree.SubElement(body, 'Autentica', nsmap={None: 'http://DescargaMasivaTerceros.gob.mx'})

        to_digest = etree.tostring(timestamp, method='c14n', exclusive=1)

        digest = base64.b64encode(hashlib.new('sha1', to_digest).digest())
        
        digestvalue.text = digest

        to_sign = etree.tostring(signedinfo, method='c14n', exclusive=1)

        firma = self.fiel.firmar_sha1(to_sign)

        signaturevalue.text = firma

        binarysecuritytoken.text = self.fiel.cer_to_base64()

        return etree.tostring(soap_req)

    def obtener_token(self, id=uuid.uuid4()):
        
        soapreq = self.__generar_soapreq__(id)

        headers = {
            'Content-type': 'text/xml;charset="utf-8"',
            'Accept': 'text/xml',
            'Cache-Control': 'no-cache',
            'SOAPAction': self.SOAP_ACTION
        }

        response = requests.post(self.SOAP_URL, data=soapreq, headers=headers, verify=True)

        if response.status_code != requests.codes['ok']:
            if not response.text.startswith('<s:Envelope'):
                ex = 'El webservice Autenticacion responde: {}'.format(response.text)
            else:
                resp_xml = etree.fromstring(response.text)
                ex = resp_xml.find('s:Body/s:Fault/faultstring', namespaces=self.NSMAP).text
            raise Exception(ex)

        if not response.text.startswith('<s:Envelope'):
            ex = 'El webservice Autenticacion responde: {}'.format(response.text)
            raise Exception(ex)

        nsmap= {
            's': 'http://schemas.xmlsoap.org/soap/envelope/',
            None: 'http://DescargaMasivaTerceros.gob.mx'
        }

        resp_xml = etree.fromstring(response.text)

        token = resp_xml.find('s:Body/AutenticaResponse/AutenticaResult', namespaces=nsmap)

        return token.text