import os
import re
from logging import warning, error

import requests

from api import http
from utilities import types, constants


def build_session():
    command_args = types.Arguments()

    def get_cert():
        if not command_args.cert:
            return True
        return command_args.cert

    def get_proxies():
        proxy_url = command_args.proxy
        if not proxy_url:
            return {}
        return {
            "http": proxy_url,
            "https": proxy_url,
        }

    session = requests.session()
    session.headers.update({
        "PRIVATE-TOKEN": os.getenv(constants.Environment.gitlab_api_token()),
        "USER-AGENT": "token-hunter"
    })
    session.proxies = get_proxies()
    session.verify = get_cert()
    return session


def args():
    return types.Arguments()


class GitLab:

    def __init__(self, base_url, session_builder=build_session, get_args=args):
        self.http = http.Http(session_builder)
        self.base_url = base_url + "/api/v4"
        self.visited_urls = {}
        self.next_page_regex = re.compile(r'<([^<>]*?)>; rel="next"')
        self.args = get_args()

    def get_project_details(self, project):
        return self.get('{}/projects/{}'.format(self.base_url, project))

    def get_merge_request_comments(self, project_id, mr_id):
        return self.get('{}/projects/{}/merge_requests/{}/discussions'.format(self.base_url, project_id, mr_id))

    def get_jobs(self, project_id):
        return self.get('{}/projects/{}/jobs?scope=success&scope=failed'.format(self.base_url, project_id))

    def get_job_logs(self, project_id, job_id):
        return self.get('{}/projects/{}/jobs/{}/trace'.format(self.base_url, project_id, job_id))

    def get_merge_requests(self, project_id):
        return self.get('{}/projects/{}/merge_requests'.format(self.base_url, project_id))

    def get_issue_comments(self, project_id, issue_id):
        return self.get('{}/projects/{}/issues/{}/discussions'.format(self.base_url, project_id, issue_id))

    def get_issues(self, project_id):
        return self.get('{}/projects/{}/issues'.format(self.base_url, project_id))

    def get_project_snippets(self, project):
        return self.get('{}/projects/{}/snippets'.format(self.base_url, project))

    def get_snippet_raw(self, snippet_id):
        return self.get('{}/snippets/{}/raw?line_ending=raw'.format(self.base_url, snippet_id))

    def get_personal_projects(self, member):
        return self.get('{}/users/{}/projects'.format(self.base_url, member))

    def get_group_projects(self, group):
        return self.get('{}/groups/{}/projects'.format(self.base_url, group))

    def get_group(self, group):
        return self.get('{}/groups/{}'.format(self.base_url, group))

    def get_group_members(self, group):
        return self.get('{}/groups/{}/members'.format(self.base_url, group))

    def get_project_members(self, project):
        return self.get('{}/projects/{}/members'.format(self.base_url, project))

    def get_current_user(self):
        details = self.get('{}/user'.format(self.base_url))

        if not details:
            return False

        username = details['username']
        return username

    def __add_current_results__(self, all_results, current_results):
        if self.args.depth is None:
            return {
                "combined_results": [*all_results, *current_results],
                "continue": True,
            }
        else:
            results = [*all_results, *current_results]
            results_count = len(results)
            if results_count < self.args.depth:
                return {
                    "combined_results": results,
                    "continue": True,
                }
            else:
                diff = self.args.depth - results_count
                del results[diff:]
                return {
                    "combined_results": results,
                    "continue": False,
                }

    def __build_paged_results__(self, response):
        all_results = []
        result = self.__add_current_results__(all_results, response.json())
        all_results += result["combined_results"]
        if result["continue"]:
            while 'Link' in response.headers and 'rel="next"' in response.headers['Link']:
                next_url = re.findall(self.next_page_regex, response.headers['Link'])[0]
                response = self.http.get_with_retry_and_paging_adjustment(next_url)
                if response.status_code == 200:
                    result = self.__add_current_results__(all_results, response.json())
                    all_results = result["combined_results"]
                    if not result["continue"]:
                        break
                else:
                    warning("[!] Response %s processing pagination URL: %s", response.status_code, next_url)
        return all_results

    def get(self, url):
        """
        Helper function to interact with GitLab API using python requests

        The important things here are:
            - Adding the PRIVATE-TOKEN header based on env variable
            - interacting with the pagination process via LINK headers
              (https://docs.gitlab.com/ee/api/README.html#pagination)
        """

        response = self.http.get_with_retry_and_paging_adjustment(url)

        if not (response and response.status_code == 200):
            error("[!] Response %s received from %s.  Skipping.", response.status_code, url)
            return False
        if 'Link' not in response.headers:
            return response.json() if response.headers["Content-Type"] == "application/json" else response.text
        return self.__build_paged_results__(response)
