# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/gitlab_api.ipynb (unless otherwise specified).

__all__ = ['MEMRI_PATH', 'MEMRI_GITLAB_BASE_URL', 'ACCESS_TOKEN_PATH', 'GITLAB_API_BASE_URL', 'DEFAULT_PACKAGE_VERSION',
           'TIME_FORMAT_GITLAB', 'PROJET_ID_PATTERN', 'GitlabAPI', 'find_git_repo', 'upload_in_chunks',
           'IterableToFileAdapter', 'rm_tree']

# Cell
from fastprogress.fastprogress import progress_bar
from pathlib import Path
import requests
import os, sys
from getpass import getpass
from datetime import datetime
from git import Repo
import re
from .data.basic import *
from .template.formatter import _plugin_from_template, str_to_gitlab_identifier
import urllib

# Cell
MEMRI_PATH = Path.home() / ".memri"
MEMRI_GITLAB_BASE_URL = "https://gitlab.memri.io"
ACCESS_TOKEN_PATH = Path.home() / ".memri/access_token/access_token.txt"
GITLAB_API_BASE_URL = "https://gitlab.memri.io/api/v4"
DEFAULT_PACKAGE_VERSION = "0.0.1"

TIME_FORMAT_GITLAB = '%Y-%m-%dT%H:%M:%S.%fZ'
PROJET_ID_PATTERN = '(?<=<span class="gl-button-text">Project ID: )[0-9]+(?=</span>)'

# Cell
class GitlabAPI():

    def __init__(self, client=None, request_auth_if_needed=False):
        self.client=client
        self.auth_headers=dict()
        self.auth_params=dict()
        self.auth_initialized=False
        self.request_auth_if_needed = request_auth_if_needed
        self.get_registry_params_headers()


    # export
    def get_registry_params_headers(self):
        job_token = os.environ.get("CI_JOB_TOKEN", None)
        if job_token is not None:
            self.auth_initialized = True
            self.auth_headers = {"JOB-TOKEN": job_token}
        elif self.client is not None:
            self.auth_params = {"access_token": self.client.get_oauth_item().accessToken}
            self.auth_initialized = True
        else:
            ACCESS_TOKEN_PATH.parent.mkdir(parents=True, exist_ok=True)
            if ACCESS_TOKEN_PATH.is_file():
                self.auth_initialized = True
                with open(ACCESS_TOKEN_PATH, "r") as f:
                    self.auth_headers = {"PRIVATE-TOKEN": f.read()}
            else:
                if self.request_auth_if_needed:
                    print(f"""
                    The first time you are uploading a model you need to create an access_token
                    at https://gitlab.memri.io/-/profile/personal_access_tokens?name=Model+Access+token&scopes=api
                    Click at the blue button with 'Create personal access token'"
                    """)
                    access_token = getpass("Then copy your personal access token from 'Your new personal access token', and paste here: ")
                    with open(ACCESS_TOKEN_PATH, "w") as f:
                        f.write(access_token)
                    self.auth_headers = {"PRIVATE-TOKEN": access_token}
                    self.auth_initialized = True

    def write_file_to_package_registry(
        self,
        project_id,
        file_path,
        package_name,
        version=DEFAULT_PACKAGE_VERSION,
        trigger_pipeline=True
    ):
        file_path = Path(file_path)
        file_name = file_path.name

        url = f"{GITLAB_API_BASE_URL}/projects/{project_id}/packages/generic/{package_name}/{version}/{file_name}"
        print(f"uploading {file_path}")
        it = upload_in_chunks(file_path)
        res = requests.put(url=url, data=IterableToFileAdapter(it),
                         headers=self.auth_headers, params=self.auth_params)

        if res.status_code not in [200, 201]:
            print(f"Failed to upload {file_path}: {res.content}")
        else:
            print(f"Succesfully uploaded {file_path}")
            if trigger_pipeline:
                self.trigger_pipeline(project_id)

    def trigger_pipeline(self, project_id):
        url = f"{GITLAB_API_BASE_URL}/projects/{project_id}/pipeline?ref=main"
        res = requests.post(url, headers=self.auth_headers, params=self.auth_params)
        if res.status_code not in [200, 201]:
            print(f"Failed to trigger pipeline")

    # export
    def project_id_from_name(self, project_name):
        iden = str_to_gitlab_identifier(project_name)
        res = requests.get(f"{GITLAB_API_BASE_URL}/projects",
                           headers=self.auth_headers,
                           params={**self.auth_params, **{
                               "owned": True,
                               "search": project_name
                           }})
        if res.status_code not in [200, 201]:
            print(res.content)
            raise RuntimeError(f"Failed to get project id for {project_name}")
        # we need this extra filter (search is not exact match)
        res = [x.get("id") for x in res.json() if x.get("path", None) == iden]
        if len(res) == 0:
            raise ValueError(f"No plugin found with name {project_name}, make sure to enter the name as specified in the url of the repo")
        else:
            return res[0]

    # export
    def get_project_id_from_project_path_unsafe(self, project_path):
        try:
            res = requests.get(f"{MEMRI_GITLAB_BASE_URL}/{project_path}")
            html = str(res.content)
            match = re.search(PROJET_ID_PATTERN, html)
            return match.group()
        except Exception:
            raise ValueError(f"Could not find project with name {project_path}")

    # export
    def download_package_file(self, filename, project_path, package_name, out_dir=None,
                              package_version=DEFAULT_PACKAGE_VERSION, download_if_exists=False):

        project_name = str(project_path).split("/")[-1]
        out_dir = out_dir if out_dir is not None else MEMRI_PATH / "projects" / project_name
        out_dir.mkdir(parents=True, exist_ok=True)

        project_id = self.get_project_id_from_project_path_unsafe(project_path)

        file_path = out_dir / filename
        print(file_path)

        if file_path.exists() and not download_if_exists:
            print(f"{file_path} already exists, and `download_if_exists`==False, using cached version")
            return file_path

        print(f"downloading {filename} from project {project_path}, package {package_name}")

        res = requests.get(
            url=f"{GITLAB_API_BASE_URL}/projects/{project_id}/packages/generic/{package_name}/{package_version}/{filename}"
        )
        res.raise_for_status()
        with open(out_dir / filename, "wb") as f:
            print(f"writing {filename} to {out_dir}")
            f.write(res.content)
        return file_path

    def create_repo(self, repo_name, client=None):
        url = f"{GITLAB_API_BASE_URL}/projects/"
        payload = {"name": repo_name}
        res = requests.post(url=url, json=payload, headers=self.auth_headers, params=self.auth_params)

        if res.status_code not in [200, 201]:
            raise ValueError(f"failed to create repo:\n {res.text}")
        print(f"created project {repo_name}")

    # export
    def get_current_username(self, client=None):
        url = f"{GITLAB_API_BASE_URL}/user/"
        res = requests.get(url=url, headers=self.auth_headers, params=self.auth_params)
        if res.status_code not in [200,201]:
            raise ValueError(f"Could not find current user {res.content}")
        else:
            username = res.json()["username"]
            return username

    # NEVER EXPORT THIS
    def delete_project(self, path_or_id, client=None):
        url_escape_id = urllib.parse.quote(path_or_id, safe='')
        url = f"{GITLAB_API_BASE_URL}/projects/{url_escape_id}"
        res = requests.delete(url=url, headers=self.auth_headers, params=self.auth_params)
        if res.status_code not in [200, 201, 202]:
            raise ValueError(f"failed to delete repo:\n {res.text}")
        print(f"deleted project {path_or_id}")

    # export
    def commit_file(self, project_name, path_in2out, branch="main", client=None):
        project_id = self.project_id_from_name(project_name)
    #     file_out_path_escaped = urllib.parse.quote(file_out_path, safe='')

        actions = []

        for file_in_path, file_out_path in path_in2out.items():
            content = read_file(file_in_path)
            action_payload = {"action": "create", "file_path": file_out_path, "content": content}
            actions.append(action_payload)

        url = f"{GITLAB_API_BASE_URL}/projects/{project_id}/repository/commits"
        payload = {"branch": branch, "commit_message": "automated commit", "content": content, "actions": actions}

        res = requests.post(url=url, json=payload, headers=self.auth_headers, params=self.auth_params)
        files_in = list(path_in2out.keys())
        if res.status_code not in [200, 201, 202]:
            raise ValueError(f"failed to make commit with files {files_in}:\n {res.text}")
        print(f"committed files {files_in}")

    # export
    def write_files_to_git(self, repo, target_dir, **kwargs):
        path_in2out = dict()
        for p in target_dir.rglob("*"):
            if p.is_file():
                path_in_repo = p.relative_to(target_dir)
                path_in2out[str(p)] = str(path_in_repo)
        self.commit_file(str(repo), path_in2out, **kwargs)


    def create_new_project(self, project_name, user=None):
        tmp_dir = Path("/tmp/test") / project_name
        rm_tree(tmp_dir)
        repo_url = f"{MEMRI_GITLAB_BASE_URL}/{user}/{project_name}" if user is not None else f"{MEMRI_GITLAB_BASE_URL}/plugins/{project_name}"

        _plugin_from_template(
            template_name="classifier_plugin",
            description="A transformer based sentiment analyis plugin",
            install_requires="transformers,sentencepiece,protobuf,torch==1.10.0",
            target_dir=str(tmp_dir),
            repo_url=repo_url,
            verbose=False,
            user=user
        )
        self.write_files_to_git(project_name, tmp_dir)




# Cell
def find_git_repo():
    path = "."
    for i in range(10):
        try:
            repo = Repo(f"{path + ('.' * i)}/")
        except:
            pass
        else:
            break
    if i == 9:
        raise ValueError(f"could not fine git repo in {os.path.abspath('')}")

    repo_name = repo.remotes.origin.url.split('.git')[0].split('/')[-1]
    return repo_name


# Cell
class upload_in_chunks(object):
    def __init__(self, filename, chunksize=1 << 14):
        self.filename = filename
        self.chunksize = chunksize
        self.totalsize = os.path.getsize(filename)
        self.readsofar = 0

    def __iter__(self):
        n = 100
        pb = progress_bar(range(n))
        pb_iter = iter(pb)
        i = 1
        delta = 1 / n
        next(pb_iter, None)

        with open(self.filename, 'rb') as file:
            while True:
                data = file.read(self.chunksize)
                if not data:
                    sys.stderr.write("\n")
                    break
                self.readsofar += len(data)
                percent = self.readsofar * 1e2 / self.totalsize
                while (percent / 100) > i * delta:
                    next(pb_iter, None)
                    i += 1
                yield data
        pb.update_bar(n)

    def __len__(self):
        return self.totalsize

class IterableToFileAdapter(object):
    def __init__(self, iterable):
        self.iterator = iter(iterable)
        self.length = len(iterable)

    def read(self, size=-1): # TBD: add buffer for `len(data) > size` case
        return next(self.iterator, b'')

    def __len__(self):
        return self.length

# Cell
def rm_tree(pth):
    pth = Path(pth)
    for child in pth.glob('*'):
        if child.is_file():
            child.unlink()
        else:
            rm_tree(child)

    try:
        pth.rmdir()
    except FileNotFoundError as e:
        pass