#!/usr/bin/python

import click
import configparser
import json
import requests
import sys
import time
import uuid
from integrations.sonarqube import SonarqubeBase
from os import path
from tabulate import tabulate
from urllib.parse import urlencode


here = path.abspath(path.dirname(__file__))

with open(path.join(here, "__version__.py"), encoding="utf-8") as f:
    version = f.read().strip().split("=")[1].replace("\"", "")


class State(object):
    def __init__(self):
        self.api_key = None
        self.headers = None
        self.hackedu_url = None
        self.username = None
        self.password = None
        self.url = None
        self.app = None
        self.branch = None
        self.source = None
        self.config_path = "~/.hackedu"
        self.url_prefix = "v1"

pass_state = click.make_pass_decorator(State, ensure=True)

def set_state(config, state, section, option):
    if config.has_section(section):
        if config.has_option(section, option):
            if not state.__dict__[option]:
                state.__dict__[option] = config[section][option]

def set_headers(config, state):
    if config.has_section("default"):
        if config.has_option("default", "api_key"):
            if not state.headers:
                state.headers = {"X-API-Key": config["default"]["api_key"]}

def profile_option(f):
    def callback(ctx, param, value):
        state = ctx.ensure_object(State)
        config = configparser.ConfigParser()

        config_path = path.expanduser(state.config_path)
        config.read(config_path)
        if not path.exists(config_path):
            print("Error")
            print("Config file does not exist: {}".format(value))
            sys.exit()

        if not config.has_section(value):
            print("Error")
            print("Config file does not have profile: {}".format(value))
            sys.exit()

        for key, _ in config.items(value):
            set_state(config, state, value, key)

    return click.option("--profile",
                        expose_value=False,
                        default="default",
                        help="Determines which config profile to use.",
                        callback=callback)(f)

def config_option(f):
    def callback(ctx, param, value):
        state = ctx.ensure_object(State)
        config = configparser.ConfigParser()

        config_path = path.expanduser(value)
        config.read(config_path)
        state.config_path = config_path
        if not path.exists(config_path):
            print("Error")
            print("Config file does not exist: {}".format(value))
            sys.exit()

        if config.has_section("default"):
            set_headers(config, state)
            set_state(config, state, "default", "hackedu_url")

        if config.has_section("sonarqube"):
            set_state(config, state, "sonarqube", "username")
            set_state(config, state, "sonarqube", "password")
            set_state(config, state, "sonarqube", "app")
            set_state(config, state, "sonarqube", "branch")
            set_state(config, state, "sonarqube", "source")

        if state.hackedu_url and "localhost" in state.hackedu_url:
            state.url_prefix = "v1-beta"

    return click.option("--config",
                        expose_value=False,
                        default="~/.hackedu",
                        help="Configuration file used for pre-defined options. ",
                        callback=callback)(f)


def hackedu_url_option(f):
    def callback(ctx, param, value):
        state = ctx.ensure_object(State)
        if value:
            state.hackedu_url = value

        if state.hackedu_url and "localhost" in state.hackedu_url:
            state.url_prefix = "v1-beta"

    return click.option("--hackedu_url",
                        expose_value=False,
                        help="Environment used to determine HackEDU public API host. ",
                        callback=callback)(f)


def api_key_option(f):
    def callback(ctx, param, value):
        state = ctx.ensure_object(State)
        if value:
            state.headers = {"X-API-Key": value}

    return click.option("--api_key",
                        expose_value=False,
                        help="X-API-Key header for interacting with the HackEDU public API.",
                        callback=callback)(f)

def common_options(f):
    f = config_option(f)
    f = profile_option(f)
    f = api_key_option(f)
    f = hackedu_url_option(f)

    return f

@click.group()
@click.version_option(version)
def hackedu():
    """A CLI wrapper for the HackEDU public API."""

@hackedu.command("config")
def config():
    """Command to create your .hackedu config file."""
    api_key = click.prompt("Please enter your HackEDU API Key", type=str)
    if api_key:
        config_location = click.prompt("Where would you like to store your HackEDU config file? Leave empty for default location:", type=str, default="~/")
        file_contents = "[default]\napi_key={}\nhackedu_url=https://api.hackedu.com".format(api_key)
        if config_location != "~/":
            with open(path.expanduser("{}/.hackedu".format(config_location)), "w") as config_file:
                config_file.write(file_contents)
        else:
            with open(path.expanduser("~/.hackedu"), "w") as config_file:
                config_file.write(file_contents)


@hackedu.group()
@pass_state
def issue_source(state):
    """Issue Source commands"""

@issue_source.command("ls")
@common_options
@pass_state
def ls(state):
    """List Issue Sources"""
    issue_source_url = "{}/{}/issue-sources".format(state.hackedu_url, state.url_prefix)

    response = requests.get(issue_source_url, headers=state.headers)
    if response.status_code != 200:
        print("Something went wrong")
        print(response.json())
        return
    items = response.json()["issue_sources"]
    if items:
        header = list(items[0].keys())[0:3]
        rows = [list(x.values())[0:3] for x in items]
        print(tabulate(rows, header))
    else:
        print("Warning: No issue sources returned.")

@issue_source.command("create")
@click.option("--title", help="Name of issue source.")
@click.option("--type", help="Type of issue source.", type=click.Choice(["sonarqube"]))
@common_options
@pass_state
def create(state, title, type):
    """Create Issue Source"""
    issue_source_url = "{}/{}/issue-sources".format(state.hackedu_url, state.url_prefix)
    issue_source_types_path = "{}/{}/issue-source-types".format(state.hackedu_url, state.url_prefix)
    if not title:
        print("Error: Missing required option '--title'")
        return

    print("creating issue source...")

    payload = {
        "uuid":uuid.uuid4(),
        "title": title,
        "settings": json.dumps({}),
    }

    if type == "sonarqube":
        response = requests.get("{}?key={}".format(issue_source_types_path, type), headers=state.headers)
        issue_source_type_id = response.json()["issue_source_types"][0]["issue_source_type_id"]
        payload["issue_source_type_id"] = issue_source_type_id

    response = requests.post(issue_source_url, data=payload, headers=state.headers)
    if response.status_code != 200:
        print("Something went wrong")
        print(response.json())
        return

    print("Success!")
    print(response.json()["uuid"])


@hackedu.group()
@pass_state
def issues(state):
    """Issues command"""

@issues.command("ls")
@click.option("--source", help="HackEDU Issue source uuid.")
@common_options
@pass_state
def ls(state, source):
    """List Issues"""
    issues_url = "{}/{}/issues".format(state.hackedu_url, state.url_prefix)
    if source:
        issues_url = "{}/{}/issues?source={}".format(state.hackedu_url, state.url_prefix, source)

    response = requests.get(issues_url, headers=state.headers)
    if response.status_code != 200:
        print("Something went wrong")
        print(response.json())
        return

    issues = response.json()["issues"]
    header = ["issue uuid", "unique id"]
    rows = []
    for issue in issues:
        rows.append([issue["uuid"], issue["issue_source_unique_id"]])
    print(tabulate(rows, header))



@issues.group()
@pass_state
def sync(state):
    """Sync Issues"""

@sync.command("sonarqube")
@click.option("--source", help="HackEDU Issue source uuid.")
@click.option("--url", help="Sonarqube URL.")
@click.option("--username", help="Sonarqube username.")
@click.option("--password", help="Sonarqube password")
@click.option("--branch", help="Repository branch name that Sonarqube will analyze.")
@click.option("--app", help="Sonarqube app name.")
@common_options
@pass_state
def sonarqube(state, source, url, username, password, branch, app):
    """Sync Issues from Sonarqube to HackEDU"""

    if source:
        state.source = source

    if url:
        state.url = url

    if username:
        state.username = username

    if password:
        state.password = password

    if branch:
        state.branch = branch

    if app:
        state.app = app

    sonarqube = SonarqubeBase(
        state.url,
        state.username,
        state.password,
        state.app,
        state.branch
    )

    issues_url = "{}/{}/issues".format(state.hackedu_url, state.url_prefix)
    vulnerabilities_url = "{}/{}/vulnerabilities".format(state.hackedu_url, state.url_prefix)
    if not state.source:
        print("Error: Missing required option '--source'")
        return

    print("syncing sonarqube...")
    success = False
    sonarqube_vulnerabilities = sonarqube.get_vulnerabilities()
    print("found {} issues".format(len(sonarqube_vulnerabilities)))
    print("syncing issues to hackedu...")

    for sonarqube_vulnerability in sonarqube_vulnerabilities:
        response = requests.get("{}?{}".format(vulnerabilities_url,
                                               urlencode(sonarqube_vulnerability["vulnerability_types"])),
                                headers=state.headers)
        if response.status_code != 200:
            print("Something went wrong")
            print(response.json())
            return

        hackedu_vulnerabilities = response.json()["vulnerabilities"]
        for hackedu_vulnerability in hackedu_vulnerabilities:
            payload = {
                "title": sonarqube_vulnerability["title"],
                "description": "",
                "issue_source_uuid": state.source,
                "issue_source_unique_id": uuid.uuid4(),
                "app_id": state.app,
                "severity": sonarqube_vulnerability["severity"],
                "vulnerability": hackedu_vulnerability["id"],
                "timestamp": sonarqube_vulnerability["timestamp"],
                "url": "{}/project/issues?id={}&types=VULNERABILITY".format(state.url, state.app)
            }
            response = requests.post(issues_url, data=payload, headers=state.headers)
            if response.status_code != 200:
                print("Something went wrong")
                print(response.json())
                return

            success = True
            time.sleep(.5)

        time.sleep(.5)

    if success:
        print("Success!")


if __name__ == "__main__":
    hackedu(prog_name="hackedu")
