#!/usr/bin/env python3
# Copyright © 2020 - 2022 Collabora Ltd.
# Authors:
#   Tomeu Vizoso <tomeu.vizoso@collabora.com>
#   David Heidelberg <david.heidelberg@collabora.com>
#   Guilherme Gallo <guilherme.gallo@collabora.com>
#
# SPDX-License-Identifier: MIT
'''Shared functions between the scripts.'''

import logging
import os
import re
import time
from functools import cache
from pathlib import Path

GITLAB_URL = "https://gitlab.freedesktop.org"
TOKEN_DIR = Path(os.getenv("XDG_CONFIG_HOME") or Path.home() / ".config")

# Known GitLab token prefixes: https://docs.gitlab.com/ee/security/token_overview.html#token-prefixes
TOKEN_PREFIXES: dict[str, str] = {
    "Personal access token": "glpat-",
    "OAuth Application Secret": "gloas-",
    "Deploy token": "gldt-",
    "Runner authentication token": "glrt-",
    "CI/CD Job token": "glcbt-",
    "Trigger token": "glptt-",
    "Feed token": "glft-",
    "Incoming mail token": "glimt-",
    "GitLab Agent for Kubernetes token": "glagent-",
    "SCIM Tokens": "glsoat-",
}


@cache
def print_once(*args, **kwargs):
    """Print without spamming the output"""
    print(*args, **kwargs)


def pretty_duration(seconds):
    """Pretty print duration"""
    hours, rem = divmod(seconds, 3600)
    minutes, seconds = divmod(rem, 60)
    if hours:
        return f"{hours:0.0f}h{minutes:02.0f}m{seconds:02.0f}s"
    if minutes:
        return f"{minutes:0.0f}m{seconds:02.0f}s"
    return f"{seconds:0.0f}s"


def get_gitlab_pipeline_from_url(gl, pipeline_url) -> tuple:
    """
    Extract the project and pipeline object from the url string
    :param gl: Gitlab object
    :param pipeline_url: string with a url to a pipeline
    :return: ProjectPipeline, Project objects
    """
    pattern = rf"^{re.escape(GITLAB_URL)}/(.*)/-/pipelines/([0-9]+)$"
    match = re.match(pattern, pipeline_url)
    if not match:
        raise AssertionError(f"url {pipeline_url} doesn't follow the pattern {pattern}")
    namespace_with_project, pipeline_id = match.groups()
    cur_project = gl.projects.get(namespace_with_project)
    pipe = cur_project.pipelines.get(pipeline_id)
    return pipe, cur_project


def get_gitlab_project(glab, name: str):
    """Finds a specified gitlab project for given user"""
    if "/" in name:
        project_path = name
    else:
        glab.auth()
        username = glab.user.username
        project_path = f"{username}/{name}"
    return glab.projects.get(project_path)


def get_token_from_default_dir() -> str:
    """
    Retrieves the GitLab token from the default directory.

    Returns:
        str: The path to the GitLab token file.

    Raises:
        FileNotFoundError: If the token file is not found.
    """
    token_file = TOKEN_DIR / "gitlab-token"
    try:
        return str(token_file.resolve())
    except FileNotFoundError as ex:
        print(
            f"Could not find {token_file}, please provide a token file as an argument"
        )
        raise ex


def validate_gitlab_token(token: str) -> bool:
    token_suffix = token.split("-")[-1]
    # Basic validation of the token suffix based on:
    # https://gitlab.com/gitlab-org/gitlab/-/blob/master/gems/gitlab-secret_detection/lib/gitleaks.toml
    if not re.match(r"(\w+-)?[0-9a-zA-Z_\-]{20,64}", token_suffix):
        return False

    for token_type, token_prefix in TOKEN_PREFIXES.items():
        if token.startswith(token_prefix):
            logging.info(f"Found probable token type: {token_type}")
            return True

    # If the token type is not recognized, return False
    return False


def get_token_from_arg(token_arg: str | Path | None) -> str | None:
    if not token_arg:
        logging.info("No token provided.")
        return None

    token_path = Path(token_arg)
    if token_path.is_file():
        return read_token_from_file(token_path)

    return handle_direct_token(token_path, token_arg)


def read_token_from_file(token_path: Path) -> str:
    token = token_path.read_text().strip()
    logging.info(f"Token read from file: {token_path}")
    return token


def handle_direct_token(token_path: Path, token_arg: str | Path) -> str | None:
    if token_path == Path(get_token_from_default_dir()):
        logging.warning(
            f"The default token file {token_path} was not found. "
            "Please provide a token file or a token directly via --token arg."
        )
        return None
    logging.info("Token provided directly as an argument.")
    return str(token_arg)


def read_token(token_arg: str | Path | None) -> str | None:
    token = get_token_from_arg(token_arg)
    if token and not validate_gitlab_token(token):
        logging.warning("The provided token is either an old token or does not seem to "
                        "be a valid token.")
        logging.warning("Newer tokens are the ones created from a Gitlab 14.5+ instance.")
        logging.warning("See https://about.gitlab.com/releases/2021/11/22/"
                        "gitlab-14-5-released/"
                        "#new-gitlab-access-token-prefix-and-detection")
    return token


def wait_for_pipeline(projects, sha: str, timeout=None):
    """await until pipeline appears in Gitlab"""
    project_names = [project.path_with_namespace for project in projects]
    print(f"⏲ for the pipeline to appear in {project_names}..", end="")
    start_time = time.time()
    while True:
        for project in projects:
            pipelines = project.pipelines.list(sha=sha)
            if pipelines:
                print("", flush=True)
                return (pipelines[0], project)
        print("", end=".", flush=True)
        if timeout and time.time() - start_time > timeout:
            print(" not found", flush=True)
            return (None, None)
        time.sleep(1)
