import sys
import base64
import json
import subprocess
import traceback
import os.path
from functools import wraps

try:
    import ssl
except ImportError:
    ssl = False

PY2 = sys.version_info < (3, 0)


try:
    import __builtin__
    str_instances = (str, __builtin__.basestring)
except Exception:
    str_instances = (str, )

try:
    import urllib
    from urllib.request import Request, urlopen
    HTTPError = urllib.error.HTTPError
    URLError = urllib.error.URLError
except (AttributeError, ImportError, ValueError):
    import urllib2
    from urllib2 import Request, urlopen
    HTTPError = urllib2.HTTPError
    URLError = urllib2.URLError

try:
    from .. import editor
    from . import cert, msg, shared as G, utils
except ImportError:
    import cert
    import editor
    import msg
    import shared as G
    import utils


def get_basic_auth(host):
    username = G.AUTH.get(host, {}).get('username')
    secret = G.AUTH.get(host, {}).get('secret')
    if username is None or secret is None:
        return
    basic_auth = ('%s:%s' % (username, secret)).encode('utf-8')
    basic_auth = base64.encodestring(basic_auth)
    return basic_auth.decode('ascii').replace('\n', '')


class APIResponse():
    def __init__(self, r):
        self.body = None

        if isinstance(r, bytes):
            r = r.decode('utf-8')

        if isinstance(r, str_instances):
            lines = r.split('\n')
            self.code = int(lines[0])
            if self.code != 204:
                self.body = json.loads('\n'.join(lines[1:]))
        elif hasattr(r, 'code'):
            # Hopefully this is an HTTPError
            self.code = r.code
            if self.code != 204:
                self.body = json.loads(r.read().decode("utf-8"))
        elif hasattr(r, 'reason'):
            # Hopefully this is a URLError
            # horrible hack, but lots of other stuff checks the response code :/
            self.code = 500
            self.body = r.reason
        else:
            # WFIO
            self.code = 500
            self.body = r
        msg.debug('code: %s' % self.code)


def proxy_api_request(host, url, data, method):
    args = ['python', '-m', 'floo.proxy', '--host', host, '--url', url]
    if data:
        args += ["--data", json.dumps(data)]
    if method:
        args += ["--method", method]
    msg.log('Running ', ' '.join(args), ' (', G.PLUGIN_PATH, ')')
    proc = subprocess.Popen(args, cwd=G.PLUGIN_PATH, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    (stdout, stderr) = proc.communicate()
    if stderr:
        raise IOError(stderr)

    if proc.poll() != 0:
        raise IOError(stdout)
    r = APIResponse(stdout)
    return r


def user_agent():
    return 'Floobits Plugin %s %s %s py-%s.%s' % (
        editor.name(),
        G.__PLUGIN_VERSION__,
        editor.platform(),
        sys.version_info[0],
        sys.version_info[1]
    )


def hit_url(host, url, data, method):
    if data:
        data = json.dumps(data).encode('utf-8')
    msg.debug('url: ', url, ' method: ', method, ' data: ', data)
    r = Request(url, data=data)
    r.method = method
    r.get_method = lambda: method
    auth = get_basic_auth(host)
    if auth:
        r.add_header('Authorization', 'Basic %s' % auth)
    r.add_header('Accept', 'application/json')
    r.add_header('Content-type', 'application/json')
    r.add_header('User-Agent', user_agent())
    cafile = os.path.join(G.BASE_DIR, 'floobits.pem')
    with open(cafile, 'wb') as cert_fd:
        cert_fd.write(cert.CA_CERT.encode('utf-8'))
    return urlopen(r, timeout=10, cafile=cafile)


def api_request(host, url, data=None, method=None):
    if data:
        method = method or 'POST'
    else:
        method = method or 'GET'
    if ssl is False:
        return proxy_api_request(host, url, data, method)
    try:
        r = hit_url(host, url, data, method)
    except HTTPError as e:
        r = e
    except URLError as e:
        msg.warn('Error hitting url ', url, ': ', e)
        r = e
        if not PY2:
            msg.warn('Retrying using system python...')
            return proxy_api_request(host, url, data, method)
    return APIResponse(r)


def create_workspace(host, post_data):
    api_url = 'https://%s/api/workspace' % host
    return api_request(host, api_url, post_data)


def delete_workspace(host, owner, workspace):
    api_url = 'https://%s/api/workspace/%s/%s' % (host, owner, workspace)
    return api_request(host, api_url, method='DELETE')


def update_workspace(workspace_url, data):
    result = utils.parse_url(workspace_url)
    api_url = 'https://%s/api/workspace/%s/%s' % (result['host'], result['owner'], result['workspace'])
    return api_request(result['host'], api_url, data, method='PUT')


def get_workspace_by_url(url):
    result = utils.parse_url(url)
    api_url = 'https://%s/api/workspace/%s/%s' % (result['host'], result['owner'], result['workspace'])
    return api_request(result['host'], api_url)


def get_workspace(host, owner, workspace):
    api_url = 'https://%s/api/workspace/%s/%s' % (host, owner, workspace)
    return api_request(host, api_url)


def get_workspaces(host):
    api_url = 'https://%s/api/workspaces/can/view' % (host)
    return api_request(host, api_url)


def get_orgs(host):
    api_url = 'https://%s/api/orgs' % (host)
    return api_request(host, api_url)


def get_orgs_can_admin(host):
    api_url = 'https://%s/api/orgs/can/admin' % (host)
    return api_request(host, api_url)


def request_review(host, owner, workspace, description):
    api_url = 'https://%s/api/workspace/%s/%s/review' % (host, owner, workspace)
    return api_request(host, api_url, data={'description': description})


def send_error(description=None, exception=None):
    G.ERROR_COUNT += 1
    data = {
        'jsondump': {
            'error_count': G.ERROR_COUNT
        },
        'message': {},
        'dir': G.COLAB_DIR,
    }
    stack = ''
    if G.AGENT:
        data['owner'] = getattr(G.AGENT, "owner", None)
        data['username'] = getattr(G.AGENT, "username", None)
        data['workspace'] = getattr(G.AGENT, "workspace", None)
    if exception:
        exc_info = sys.exc_info()
        try:
            stack = traceback.format_exception(*exc_info)
        except Exception:
            if exc_info[0] is None:
                stack = 'No sys.exc_info()'
            else:
                stack = "Python is rtardd"
        try:
            description = str(exception)
        except Exception:
            description = "Python is rtadd"

        data['message'] = {
            'description': description,
            'stack': stack
        }
    msg.log('Floobits plugin error! Sending exception report: ', data['message'])
    if description:
        data['message']['description'] = description
    if G.ERRORS_SENT >= G.MAX_ERROR_REPORTS:
        msg.warn('Already sent ', G.ERRORS_SENT, ' errors this session. Not sending any more.\n', description, exception, stack)
        return
    try:
        # TODO: use G.AGENT.proto.host?
        api_url = 'https://%s/api/log' % (G.DEFAULT_HOST)
        r = api_request(G.DEFAULT_HOST, api_url, data)
        G.ERRORS_SENT += 1
        return r
    except Exception as e:
        print(e)


def send_errors(f):
    @wraps(f)
    def wrapped(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except Exception as e:
            send_error(None, e)
            raise
    return wrapped
