from __future__ import absolute_import

import json
from base64 import b64decode, b64encode
from hashlib import sha1

from twisted.internet.defer import Deferred, succeed

from .base_endpoint import BaseEndpoint
from ..attestation.identity.community import IdentityCommunity
from ..attestation.wallet.community import AttestationCommunity
from ..attestation.wallet.primitives.attestation import binary_relativity_sha256_4
from ..attestation.wallet.primitives.cryptosystem.boneh import generate_keypair
from ..keyvault.crypto import default_eccrypto
from ..peer import Peer
from ..util import cast_to_bin, cast_to_unicode


class AttestationEndpoint(BaseEndpoint):
    """
    This endpoint is responsible for handing all requests regarding attestation.
    """

    def __init__(self, session):
        super(AttestationEndpoint, self).__init__()
        self.session = session
        attestation_overlays = [overlay for overlay in session.overlays if isinstance(overlay, AttestationCommunity)]
        identity_overlays = [overlay for overlay in session.overlays if isinstance(overlay, IdentityCommunity)]
        if attestation_overlays and identity_overlays:
            self.attestation_overlay = attestation_overlays[0]
            self.attestation_overlay.set_attestation_request_callback(self.on_request_attestation)
            self.attestation_overlay.set_attestation_request_complete_callback(self.on_attestation_complete)
            self.attestation_overlay.set_verify_request_callback(self.on_verify_request)
            self.identity_overlay = identity_overlays[0]
        self.attestation_requests = {}
        self.verify_requests = {}
        self.verification_output = {}
        self.attestation_metadata = {}

    def on_request_attestation(self, peer, attribute_name, metadata):
        """
        Return the measurement of an attribute for a certain peer.
        """
        deferred = Deferred()
        self.attestation_requests[(b64encode(peer.mid), cast_to_bin(attribute_name))] = \
            (deferred, b64encode(json.dumps(metadata).encode('utf-8')))
        self.attestation_metadata[(peer, attribute_name)] = metadata
        return deferred

    def on_attestation_complete(self, for_peer, attribute_name, attribute_hash, from_peer=None):
        """
        Callback for when an attestation has been completed for another peer.
        We can now sign for it.
        """
        metadata = self.attestation_metadata.get((for_peer, attribute_name), None)
        if for_peer.mid == self.identity_overlay.my_peer.mid:
            self.identity_overlay.request_attestation_advertisement(from_peer, attribute_hash, attribute_name,
                                                                    metadata)
        else:
            self.identity_overlay.add_known_hash(attribute_hash, attribute_name, for_peer.public_key.key_to_bin(),
                                                 metadata)

    def on_verify_request(self, peer, attribute_hash):
        """
        Return the measurement of an attribute for a certain peer.
        """
        block = self.identity_overlay.get_attestation_by_hash(attribute_hash)
        if not block:
            return succeed(None)
        attribute_name = block.transaction[b"name"]
        deferred = Deferred()
        self.verify_requests[(b64encode(peer.mid), attribute_name)] = deferred
        return deferred

    def on_verification_results(self, attribute_hash, values):
        """
        Callback for when verification has concluded.
        """
        references = self.verification_output[attribute_hash]
        out = []
        for i in range(len(references)):
            out.append((references[i][0] if isinstance(references[i], tuple) else references[i], values[i]))
        self.verification_output[attribute_hash] = out

    def get_peer_from_mid(self, mid_b64):
        """
        Find a peer by base64 encoded mid.
        """
        mid = b64decode(mid_b64)
        peers = self.session.network.verified_peers[:]
        matches = [p for p in peers if p.mid == mid]
        return matches[0] if matches else None

    def render_GET(self, request):
        """
        type=drop_identity
        type=outstanding -> [(mid_b64, attribute_name)]
        type=outstanding_verify -> [(mid_b64, attribute_name)]
        type=verification_output -> {hash_b64: [(value_b64, match)]}
        type=peers -> [mid_b64]
        type=attributes&mid=mid_b64 -> [(attribute_name, attribute_hash)]
        """
        if not request.args or b'type' not in request.args:
            return ""
        if request.args[b'type'][0] == b'outstanding':
            formatted = []
            for k, v in self.attestation_requests.items():
                formatted.append(k + (v[1], ))
            return json.dumps([(x.decode('utf-8'), y.decode('utf-8'), z.decode('utf-8'))
                               for x, y, z in formatted]).encode('utf-8')
        if request.args[b'type'][0] == b'outstanding_verify':
            formatted = []
            for k, v in self.verify_requests.items():
                formatted.append(k)
            return json.dumps([(x.decode('utf-8'), y.decode('utf-8')) for x, y in formatted]).encode('utf-8')
        if request.args[b'type'][0] == b'verification_output':
            formatted = {}
            for k, v in self.verification_output.items():
                formatted[b64encode(k).decode('utf-8')] = [(b64encode(a).decode('utf-8'), m) for a, m in v]
            return json.dumps(formatted).encode('utf-8')
        if request.args[b'type'][0] == b'peers':
            peers = self.session.network.get_peers_for_service(self.identity_overlay.master_peer.mid)
            return json.dumps([b64encode(p.mid).decode('utf-8') for p in peers]).encode('utf-8')
        if request.args[b'type'][0] == b'attributes':
            if b'mid' in request.args:
                mid_b64 = request.args[b'mid'][0]
                peer = self.get_peer_from_mid(mid_b64)
            else:
                peer = self.identity_overlay.my_peer
            if peer:
                blocks = self.identity_overlay.persistence.get_latest_blocks(peer.public_key.key_to_bin(), 200)
                trimmed = {}
                for b in blocks:
                    owner = b.public_key
                    if owner != peer.public_key.key_to_bin() or b.link_sequence_number != 0:
                        # We are only interested in blocks we made and are not attestations of other's attributes
                        continue
                    attester = b64encode(sha1(b.link_public_key).digest())
                    previous = trimmed.get((attester, b.transaction[b"name"]), None)
                    if not previous or previous.sequence_number < b.sequence_number:
                        trimmed[(attester, b.transaction[b"name"])] = b
                return json.dumps([(b.transaction[b"name"].decode('utf-8'),
                                    b64encode(b.transaction[b"hash"]).decode('utf-8'),
                                    {cast_to_unicode(k): cast_to_unicode(v)
                                     for k, v in b.transaction[b"metadata"].items()},
                                    b64encode(sha1(b.link_public_key).digest()).decode('utf-8'))
                                   for b in trimmed.values()]).encode('utf-8')
        if request.args[b'type'][0] == b'drop_identity':
            self.identity_overlay.persistence.execute('DELETE FROM blocks')
            self.identity_overlay.persistence.commit()
            self.attestation_overlay.database.execute('DELETE FROM %s' % self.attestation_overlay.database.db_name)
            self.attestation_overlay.database.commit()
            self.attestation_requests.clear()
            my_new_peer = Peer(default_eccrypto.generate_key(u"curve25519"))
            self.identity_overlay.my_peer = my_new_peer
            self.attestation_overlay.my_peer = my_new_peer
        return b""

    def render_POST(self, request):
        """
        type=request&mid=mid_b64&attibute_name=attribute_name
        type=allow_verify&mid=mid_b64&attibute_name=attribute_name
        type=attest&mid=mid_b64&attribute_name=attribute_name&attribute_value=attribute_value_b64
        type=verify&mid=mid_b64&attribute_hash=attribute_hash_b64&attribute_values=attribute_value_b64,...
        """
        if not request.args or b'type' not in request.args:
            return b""
        if request.args[b'type'][0] == b'request':
            mid_b64 = request.args[b'mid'][0]
            attribute_name = request.args[b'attribute_name'][0]
            peer = self.get_peer_from_mid(mid_b64)
            if peer:
                _, key = generate_keypair()
                metadata = {}
                if b'metadata' in request.args:
                    metadata_unicode = json.loads(b64decode(request.args[b'metadata'][0]))
                    for k, v in metadata_unicode.items():
                        metadata[cast_to_bin(k)] = cast_to_bin(v)
                    self.attestation_metadata[(self.identity_overlay.my_peer, attribute_name)] = metadata
                self.attestation_overlay.request_attestation(peer, attribute_name, key, metadata)
            return b""
        if request.args[b'type'][0] == b'attest':
            mid_b64 = request.args[b'mid'][0]
            attribute_name = request.args[b'attribute_name'][0]
            attribute_value_b64 = request.args[b'attribute_value'][0]
            outstanding = self.attestation_requests.pop((mid_b64, attribute_name))
            outstanding[0].callback(b64decode(attribute_value_b64))
            return b""
        if request.args[b'type'][0] == b'allow_verify':
            mid_b64 = request.args[b'mid'][0]
            attribute_name = request.args[b'attribute_name'][0]
            outstanding = self.verify_requests.pop((mid_b64, attribute_name))
            outstanding.callback(True)
            return b""
        if request.args[b'type'][0] == b'verify':
            mid_b64 = request.args[b'mid'][0]
            attribute_hash = b64decode(request.args[b'attribute_hash'][0])
            reference_values = [binary_relativity_sha256_4(b64decode(v))
                                for v in request.args[b'attribute_values'][0].split(b',')]
            peer = self.get_peer_from_mid(mid_b64)
            if peer:
                self.verification_output[b64decode(request.args[b'attribute_hash'][0])] =\
                    [(b64decode(v), 0.0) for v in request.args[b'attribute_values'][0].split(b',')]
                self.attestation_overlay.verify_attestation_values(peer.address, attribute_hash, reference_values,
                                                                   self.on_verification_results)
            return b""
        return b""
