import platform

if platform.system() != "Java":
    print("Load this file inside jython, if you need the stand-alone tool run: inql")
    exit(-1)

try:
    from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
except ImportError:
    from http.server import BaseHTTPRequestHandler, HTTPServer

try:
    import urllib.request as urllib_request # for Python 3
except ImportError:
    import urllib2 as urllib_request # for Python 2 and Jython

import threading
import json

from email.parser import HeaderParser

from java.awt.event import ActionListener
from javax.swing import JMenuItem
from org.python.core.util import StringUtil

try:
    from burp import IProxyListener, IContextMenuFactory
except ImportError:
    IProxyListener = object
    IContextMenuFactory = object

from inql.constants import *
from inql.utils import string_join, override_headers, make_http_handler, HTTPRequest
from inql.actions.browser import URLOpener


class OmniMenuItem(IContextMenuFactory):
    def __init__(self, helpers=None, callbacks=None, text=''):
        self._helpers = helpers
        self._callbacks = callbacks
        self.menuitem = JMenuItem(text)
        self._burp_menuitem = JMenuItem("inql: %s" % text)
        self.set_enabled(False)
        self._callbacks.registerContextMenuFactory(self)

    def add_action_listener(self, action_listener):
        self._action_listener = action_listener
        self.menuitem.addActionListener(action_listener)
        self._burp_menuitem.addActionListener(action_listener)

    def set_enabled(self, enabled):
        self.menuitem.setEnabled(enabled)
        self._burp_menuitem.setEnabled(enabled)

    def createMenuItems(self, invocation):
        """
        Overrides IContextMenuFactory callback

        :param invocation: handles menu selected invocation
        :return:
        """
        try:
            r = invocation.getSelectedMessages()[0]
            info = self._helpers.analyzeRequest(r)
            url = str(info.getUrl())
            if not any([x in url for x in URLS]):
                return None
            body = r.getRequest()[info.getBodyOffset():].tostring()
            for h in info.getHeaders():
                if h.lower().startswith("host:"):
                    domain = h[5:].strip()

            self._action_listener.ctx(fname='dummy.query', host=domain, payload=body)
            mymenu = []
            mymenu.append(self._burp_menuitem)
        except Exception as ex:
            return None
        return mymenu


class SimpleMenuItem:
    def __init__(self, text=None):
        self.menuitem = JMenuItem(text)
        self.menuitem.setEnabled(False)

    def add_action_listener(self, action_listener):
        self.menuitem.addActionListener(action_listener)

    def set_enabled(self, enabled):
        self.menuitem.setEnabled(enabled)


class EnhancedHTTPMutator(IProxyListener):
    def __init__(self, callbacks=None, helpers=None, overrideheaders=None, requests=None, stub_responses=None):
        self._requests = requests if requests is not None else {}
        self._overrideheaders = overrideheaders if overrideheaders is not None else {}
        self._overrideheaders = overrideheaders if overrideheaders is not None else {}
        self._index = 0
        self._stub_responses = stub_responses if stub_responses is not None else {}

        if helpers and callbacks:
            self._helpers = helpers
            self._callbacks = callbacks
            self._callbacks.registerProxyListener(self)
            for r in self._callbacks.getProxyHistory():
                self._process_request(self._helpers.analyzeRequest(r), r.getRequest())


    def _process_request(self, reqinfo, reqbody):
        """
        Process request and extract key values

        :param reqinfo:
        :param reqbody:
        :return:
        """
        url = str(reqinfo.getUrl())
        if any([url.endswith(x) for x in URLS]):
            for h in reqinfo.getHeaders():
                if h.lower().startswith("host:"):
                    domain = h[5:].strip()

            method = reqinfo.getMethod()
            try:
                self._requests[domain]
            except KeyError:
                self._requests[domain] = {'POST': None, 'PUT': None, 'GET': None, 'url': None}
            self._requests[domain][method] = (reqinfo, reqbody)
            self._requests[domain]['url'] = url

    def processProxyMessage(self, messageIsRequest, message):
        """
        Implements IProxyListener method

        :param messageIsRequest: True if BURP Message is a request
        :param message: message content
        :return: None
        """
        if self._helpers and self._callbacks and messageIsRequest:
            self._process_request(self._helpers.analyzeRequest(message.getMessageInfo()),
                                  message.getMessageInfo().getRequest())

    def get_graphiql_target(self, server_port, host=None, query=None, variables=None):
        base_url = "http://localhost:%s/%s" % (server_port, self._requests[host]['url'])
        arguments = ""
        if query or variables:
            arguments += '?'
            args = []
            if host:
                args.append("query=%s" % urllib_request.quote(query))
            if variables:
                args.append("variables=%s" % urllib_request.quote(json.dumps(variables)))
            arguments += "&".join(args)

        return base_url + arguments

    def has_host(self, host):
        try:
            self._requests[host]
            return True
        except KeyError:
            return False

    def build_python_request(self, endpoint, host, payload):
        req = self._requests[host]['POST'] or self._requests[host]['PUT'] or self._requests[host]['GET']
        if req:
            original_request = HTTPRequest(req[1])
            del original_request.headers['Content-Length']

            # TODO: Implement custom headers in threads. It is not easy to share them with the current architecture.
            return urllib_request.Request(endpoint, payload, headers=original_request.headers)

    def get_stub_response(self, host):
        return self._stub_responses[host] if host in self._stub_responses else None

    def set_stub_response(self, host, payload):
        self._stub_responses[host] = payload

    def send_to_repeater(self, host, payload):
        req = self._requests[host]['POST'] or self._requests[host]['PUT'] or self._requests[host]['GET']
        if req and self._callbacks and self._helpers:
            info = req[0]
            body = req[1]
            nobody = body[:info.getBodyOffset()].tostring()
            rstripoffset = info.getBodyOffset()-len(nobody.rstrip())
            headers = body[:info.getBodyOffset()-rstripoffset].tostring()

            try:
                self._overrideheaders[host]
            except KeyError:
                self._overrideheaders[host] = []

            headers = override_headers(headers, self._overrideheaders[host])
            repeater_body = StringUtil.toBytes(string_join(
                headers,
                body[info.getBodyOffset()-rstripoffset:info.getBodyOffset()].tostring(),
                payload))

            self._callbacks.sendToRepeater(info.getUrl().getHost(), info.getUrl().getPort(),
                                           info.getUrl().getProtocol() == 'https', repeater_body,
                                          'GraphQL #%s' % self._index)
            self._index += 1


class RepeaterSenderAction(ActionListener):
    def __init__(self, omnimenu, http_mutator):
        self._http_mutator = http_mutator
        self._omnimenu = omnimenu
        self._omnimenu.add_action_listener(self)
        self.menuitem = self._omnimenu.menuitem
        self._host = None
        self._payload = None
        self._fname = None

    def actionPerformed(self, e):
        """
        Overrides ActionListener behaviour. Send current query to repeater.

        :param e: unused
        :return: None
        """
        self._http_mutator.send_to_repeater(self._host, self._payload)

    def ctx(self, host=None, payload=None, fname=None):
        """
        When a fname is specified and is a query file or a request is selected in the other tabs,
        enables the context menu to send to repeater tab

        :param host: should be not null
        :param payload: should be not null
        :param fname: should be not null
        :return: None
        """
        self._host = host
        self._payload = payload
        self._fname = fname

        if not self._fname.endswith('.query'):
            self._omnimenu.set_enabled(False)
            return

        if self._http_mutator.has_host(host):
            self._omnimenu.set_enabled(True)
        else:
            self._omnimenu.set_enabled(False)


class GraphiQLSenderAction(ActionListener):
    def __init__(self, omnimenu, http_mutator):
        self._http_mutator = http_mutator
        self._omnimenu = omnimenu
        self._omnimenu.add_action_listener(self)
        self.menuitem = self._omnimenu.menuitem
        self._server = HTTPServer(('127.0.0.1', 0), make_http_handler(http_mutator))
        t = threading.Thread(target=self._server.serve_forever)
        #t.daemon = True
        t.start()

    def actionPerformed(self, e):
        """
        Override the ActionListener method. Usually setup in combination with a menuitem click.
        :param e: unused
        :return:
        """
        content = json.loads(self._payload)
        if isinstance(content, list):
            content = content[0]

        URLOpener().open(self._http_mutator.get_graphiql_target(
            self._server.server_port, self._host,
            content['query'] if 'query' in content else None,
            content['variables'] if 'variables' in content else None))

    def ctx(self, host=None, payload=None, fname=None):
        """
        When a fname is specified and is a query file or a request is selected in the other tabs,
        enables the context menu to send to repeater tab

        :param host: should be not null
        :param payload: should be not null
        :param fname: should be not null
        :return: None
        """
        self._host = host
        self._payload = payload
        self._fname = fname

        if not self._fname.endswith('.query'):
            self._omnimenu.set_enabled(False)
            return

        if self._http_mutator.has_host(host):
            self._omnimenu.set_enabled(True)
        else:
            self._omnimenu.set_enabled(False)