#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    Blockstack-client
    ~~~~~
    copyright: (c) 2014-2015 by Halfmoon Labs, Inc.
    copyright: (c) 2016 by Blockstack.org

    This file is part of Blockstack-client.

    Blockstack-client is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Blockstack-client is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with Blockstack-client. If not, see <http://www.gnu.org/licenses/>.
"""

# use Blockstack Labs as a storage proxy

import os
import sys 
import traceback
import logging
import xmlrpclib
import json
import re
import base64
from ConfigParser import SafeConfigParser

import blockstack_zones
import pybitcoin

# stop common XML attacks 
from defusedxml import xmlrpc
xmlrpc.monkey_patch()

from common import get_logger, DEBUG

SERVER_NAME = None
SERVER_PORT = None 

if os.environ.get("BLOCKSTACK_TEST", None) == "1":
    SERVER_NAME = "localhost"
    SERVER_PORT = 16264

else:
    SERVER_NAME = "node.blockstack.org"
    SERVER_PORT = 6264

log = get_logger("blockstack-storage-driver-blockstack-server")
log.setLevel(logging.DEBUG)


def is_zonefile_hash( data_id ):
    """
    Is the given ID a zonefile hash?
    """
    return (re.match("^[0-9a-fA-F]{40}$", data_id) is not None)


def get_data( data_id, zonefile=False, fqu=None ):
    """
    Get data or a zonefile from the server.
    """

    if os.environ.get("BLOCKSTACK_RPC_PID", None) == str(os.getpid()):
        # don't talk to ourselves 
        log.warn("Calling get_data to ourselves in the same process")

    url = "http://%s:%s/RPC2" % (SERVER_NAME, SERVER_PORT)
    ses = xmlrpclib.ServerProxy( url, allow_none=True )
   
    import blockstack_client

    if zonefile:
        log.debug("Get zonefile for %s" % data_id)

        res = None
        if is_zonefile_hash(data_id):
            res = ses.get_zonefiles([data_id])
        else:
            res = ses.get_zonefiles_by_names( [data_id] )

        try:
            data = json.loads(res)
        except:
            log.error("Failed to parse zonefile from %s" % data_id)
            return None

        if 'error' in data:
            log.error("Get zonefile %s: %s" % (data_id, data['error']))
            return None
        else:
            try:
                return base64.b64decode( data['zonefiles'][data_id] )
            except:
                log.error("Failed to parse zonefile")
                return None

    elif blockstack_client.is_name_valid(data_id):
        log.debug("Get profile for %s" % data_id)
        res = ses.get_profile( data_id )
        try:
            data = json.loads(res)
        except:
            log.error("Failed to parse profile from %s" % data_id)
            return None

        if type(data) != dict:
            log.error("Invalid profile data for %s" % data_id)
            return None

        if 'error' in data:
            log.error("Get profile %s: %s" % (data_id, data['error']))
            return None 

        try:
            return data['profile']
        except:
            log.error("Failed to parse profile")
            return None
    
    elif fqu is not None:
        log.debug("Get mutable data for %s (%s)" % (data_id, fqu))
        res = ses.get_mutable_data( fqu, data_id )

        try:
            res = json.loads(res)
        except: 
            log.error("Failed to parse data for %s" % data_id)
            return None

        if blockstack_client.proxy.json_is_error(res):
            log.error("Failed to get mutable data: {}".format(res['error']))
            return None

        if not isinstance(res, dict):
            log.error("Response is not a dict")
            return None

        if not res.has_key('data'):
            log.error("Response has not data")
            return None

        if type(res['data']) not in [str, unicode]:
            log.error("Response is not data string")
            return None

        return res['data']

    else:
        log.error("No blockchain ID given; cannot load data")
        return None


def put_data( data_id, data_txt, zonefile=False, fqu=None ):
    """
    Put data or a zonefile to the server.
    """
    
    import blockstack_client
    import blockstack

    if os.environ.get("BLOCKSTACK_RPC_PID", None) == str(os.getpid()):
        # don't talk to ourselves 
        log.warn("Calling put_data to ourselves in the same process")

    url = "http://%s:%s/RPC2" % (SERVER_NAME, SERVER_PORT)
    ses = xmlrpclib.ServerProxy( url, allow_none=True )

    if zonefile:
        # don't duplicate effort
        log.error("Driver does not support putting a zonefile")
        return False
        
    elif fqu is not None and data_id == fqu:
        log.debug("Replicate profile for %s" % data_id)

        # data_txt must be a sufficiently small JSON blob
        if len(data_txt) >= blockstack.RPC_MAX_PROFILE_LEN:
            log.error("Data is too big")
            return False
       
        # NOTE: last two arguments are legacy compat
        res = ses.put_profile( data_id, data_txt, '', '')
        if 'error' in res:
            log.error("Failed to put %s: %s" % (data_id, res))
            return False
        else:
            return True

    elif fqu is not None:
        log.debug("Replicate mutable datum {}".format(data_id))

        # data_txt must be a sufficiently small JSON blob
        if len(data_txt) >= blockstack.RPC_MAX_DATA_LEN:
            log.error("Data is too big")
            return False
       
        res = ses.put_mutable_data( fqu, data_txt )
        if 'error' in res:
            log.error("Failed to put %s: %s" % (data_id, res))
            return False
        else:
            return True

    else:
        log.debug("No name given; cannot store data")
        return False


def storage_init(conf):
    # read config options from the config file, if given 
    global SERVER_NAME, SERVER_PORT

    config_path = conf['path']
    if os.path.exists( config_path ):

        parser = SafeConfigParser()
        
        try:
            parser.read(config_path)
        except Exception, e:
            log.exception(e)
            return False

        if parser.has_section('blockstack-server-storage'):
            
            if parser.has_option('blockstack-server-storage', 'server'):
                SERVER_NAME = parser.get('blockstack-server-storage', 'server')
                
            if parser.has_option('blockstack-server-storage', 'port'):
                SERVER_PORT = int(parser.get('blockstack-server-storage', 'port'))
           
    else:
        raise Exception("No such file or directory: %s" % config_path)
            
    # we can't proceed unless we have them.
    if SERVER_NAME is None or SERVER_PORT is None:
        log.error("Config file '%s': section 'blockstack_server_storage' is missing 'host' and/or 'port'")
        return False

    return True


def handles_url( url ):
    if (url.startswith("http://") or url.startswith("https://")) and len(url.split("#")) == 2 and url.split("#")[0].endswith("/RPC2"):
        return True
    else:
        return False

def make_mutable_url( data_id ):
    # xmlrpc endpoint
    return "http://%s:%s/RPC2#%s" % (SERVER_NAME, SERVER_PORT, data_id)

def get_immutable_handler( key, **kw ):
    """
    Only works on user zonefiles, and only works on names
    """
    fqu = kw.get("fqu", None)
    zonefile = kw.get("zonefile", False)
    if fqu is None:
        # fall back to whatever the key is
        fqu = key

    return get_data( fqu, zonefile=zonefile )


def get_mutable_handler( url, **kw ):
    parts = url.split("#")
    if len(parts) != 2:
        log.error("Invalid url '%s'" % url)
        return None

    fqu = kw.get('fqu', None)
    data_id = parts[1]
    return get_data( data_id, zonefile=False, fqu=fqu )


def put_immutable_handler( key, data, txid, **kw ):
    return put_data( key, data, zonefile=True )

def put_mutable_handler( data_id, data_bin, **kw ):
    return put_data( data_id, data_bin, zonefile=False, fqu=kw.get('fqu', None) )

def delete_immutable_handler( key, txid, sig_key_txid, **kw ):
    return True

def delete_mutable_handler( data_id, signature, **kw ):
    return True

if __name__ == "__main__":
    config_path = os.environ.get("BLOCKSTACK_CLIENT_CONFIG", None)
    assert config_path is not None, "You must set BLOCKSTACK_CLIENT_CONFIG"

    import blockstack_client
    config = blockstack_client.get_config(config_path)
    assert config is not None

    print json.dumps(config, indent=4, sort_keys=True)
    storage_init(config)

    assert len(sys.argv) > 1, "You must specify one or more names"
    for name in sys.argv[1:]:
        zonefile = get_data(name, zonefile=True)
        assert zonefile is not None and 'error' not in zonefile, "Bad zonefile: %s" % zonefile
        profile = get_data( name, zonefile=False )
        assert profile is not None and 'error' not in profile, "Bad profile: %s" % profile

        print "zonefile:\n%s" % zonefile
        print "profile:\n%s" % json.dumps(profile, indent=4, sort_keys=True)
        print ""

