import urlparse
import logging
import requests
import os
import base64
import json

from lbrynet.lbrynet_daemon.auth.util import load_api_keys, APIKey, API_KEY_NAME, get_auth_message
from lbrynet import conf
from jsonrpc.proxy import JSONRPCProxy

log = logging.getLogger(__name__)
USER_AGENT = "AuthServiceProxy/0.1"
TWISTED_SESSION = "TWISTED_SESSION"
LBRY_SECRET = "LBRY_SECRET"
HTTP_TIMEOUT = 30


class JSONRPCException(Exception):
    def __init__(self, rpc_error):
        Exception.__init__(self)
        self.error = rpc_error


class AuthAPIClient(object):
    def __init__(self, key, timeout, connection, count, service, cookies, auth, url, login_url):
        self.__service_name = service
        self.__api_key = key
        self.__service_url = login_url
        self.__id_count = count
        self.__url = url
        self.__auth_header = auth
        self.__conn = connection
        self.__cookies = cookies

    def __getattr__(self, name):
        if name.startswith('__') and name.endswith('__'):
            # Python internal stuff
            raise AttributeError
        if self.__service_name is not None:
            name = "%s.%s" % (self.__service_name, name)
        return AuthAPIClient(key=self.__api_key,
                             timeout=HTTP_TIMEOUT,
                             connection=self.__conn,
                             count=self.__id_count,
                             service=name,
                             cookies=self.__cookies,
                             auth=self.__auth_header,
                             url=self.__url,
                             login_url=self.__service_url)

    def __call__(self, *args):
        self.__id_count += 1
        pre_auth_postdata = {'version': '1.1',
                             'method': self.__service_name,
                             'params': args,
                             'id': self.__id_count}
        to_auth = get_auth_message(pre_auth_postdata)
        token = self.__api_key.get_hmac(to_auth)
        pre_auth_postdata.update({'hmac': token})
        postdata = json.dumps(pre_auth_postdata)
        service_url = self.__service_url
        auth_header = self.__auth_header
        cookies = self.__cookies
        host = self.__url.hostname

        req = requests.Request(method='POST',
                               url=service_url,
                               data=postdata,
                               headers={'Host': host,
                                        'User-Agent': USER_AGENT,
                                        'Authorization': auth_header,
                                        'Content-type': 'application/json'},
                               cookies=cookies)
        r = req.prepare()
        http_response = self.__conn.send(r)
        cookies = http_response.cookies
        headers = http_response.headers
        next_secret = headers.get(LBRY_SECRET, False)
        if next_secret:
            self.__api_key.secret = next_secret
            self.__cookies = cookies

        if http_response is None:
            raise JSONRPCException({
                'code': -342, 'message': 'missing HTTP response from server'})

        http_response.raise_for_status()

        response = http_response.json()

        if response['error'] is not None:
            raise JSONRPCException(response['error'])
        elif 'result' not in response:
            raise JSONRPCException({
                'code': -343, 'message': 'missing JSON-RPC result'})
        else:
            return response['result']

    @classmethod
    def config(cls, key_name=None, key=None, pw_path=None,
               timeout=HTTP_TIMEOUT,
               connection=None, count=0,
               service=None,
               cookies=None, auth=None,
               url=None, login_url=None):

        api_key_name = API_KEY_NAME if not key_name else key_name
        pw_path = os.path.join(conf.settings.data_dir, ".api_keys") if not pw_path else pw_path
        if not key:
            keys = load_api_keys(pw_path)
            api_key = keys.get(api_key_name, False)
        else:
            api_key = APIKey(name=api_key_name, secret=key)
        if login_url is None:
            service_url = "http://%s:%s@%s:%i/%s" % (api_key_name,
                                                     api_key.secret,
                                                     conf.settings.API_INTERFACE,
                                                     conf.settings.api_port,
                                                     conf.settings.API_ADDRESS)
        else:
            service_url = login_url
        id_count = count

        if auth is None and connection is None and cookies is None and url is None:
            # This is a new client instance, initialize the auth header and start a session
            url = urlparse.urlparse(service_url)
            (user, passwd) = (url.username, url.password)
            try:
                user = user.encode('utf8')
            except AttributeError:
                pass
            try:
                passwd = passwd.encode('utf8')
            except AttributeError:
                pass
            authpair = user + b':' + passwd
            auth_header = b'Basic ' + base64.b64encode(authpair)
            conn = requests.Session()
            conn.auth = (user, passwd)
            req = requests.Request(method='POST',
                                   url=service_url,
                                   auth=conn.auth,
                                   headers={'Host': url.hostname,
                                            'User-Agent': USER_AGENT,
                                            'Authorization': auth_header,
                                            'Content-type': 'application/json'},)
            r = req.prepare()
            http_response = conn.send(r)
            cookies = http_response.cookies
            uid = cookies.get(TWISTED_SESSION)
            api_key = APIKey.new(seed=uid)
        else:
            # This is a client that already has a session, use it
            auth_header = auth
            conn = connection
            assert cookies.get(LBRY_SECRET, False), "Missing cookie"
            secret = cookies.get(LBRY_SECRET)
            api_key = APIKey(secret, api_key_name)
        return cls(api_key, timeout, conn, id_count, service, cookies,
                   auth_header, url, service_url)


class LBRYAPIClient(object):
    @staticmethod
    def config(service=None, params=None):
        if conf.settings.use_auth_http:
            if service is None:
                return AuthAPIClient.config()
            log.error("Try auth")
            if params is not None:
                return AuthAPIClient.config(service=service)(params)
            return AuthAPIClient.config(service=service)()
        url = conf.settings.API_CONNECTION_STRING
        if service is None:
            return JSONRPCProxy.from_url(url)
        return JSONRPCProxy.from_url(url).call(service, params)