# -*- coding: utf-8 -*-
#
# This file is part of REANA.
# Copyright (C) 2017, 2018, 2019, 2020, 2021, 2022 CERN.
#
# REANA is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""REANA client utils."""

import base64
import logging
import os
import sys
import traceback
from uuid import UUID

from reana_commons.utils import get_workflow_status_change_verb
from reana_commons.specification import load_reana_spec

from reana_client.config import reana_yaml_valid_file_names
from reana_client.printer import display_message
from reana_client.validation.utils import validate_reana_spec


def workflow_uuid_or_name(ctx, param, value):
    """Get UUID of workflow from configuration / cache file based on name."""
    if not value:
        display_message(
            "Workflow name must be provided either with "
            "`--workflow` option or with REANA_WORKON "
            "environment variable",
            msg_type="error",
        )
        sys.exit(1)
    else:
        return value


def load_validate_reana_spec(
    filepath,
    access_token=None,
    skip_validation=False,
    skip_validate_environments=True,
    pull_environment_image=False,
    server_capabilities=False,
):
    """Load and validate reana specification file.

    :raises IOError: Error while reading REANA spec file from given `filepath`.
    :raises ValidationError: Given REANA spec file does not validate against
        REANA specification.
    """

    try:
        reana_yaml = load_reana_spec(filepath)
        validate_reana_spec(
            reana_yaml,
            filepath,
            access_token=access_token,
            skip_validation=skip_validation,
            skip_validate_environments=skip_validate_environments,
            pull_environment_image=pull_environment_image,
            server_capabilities=server_capabilities,
        )

        if reana_yaml["workflow"]["type"] == "yadage":
            # We don't send the loaded Yadage workflow spec to the cluster as
            # it may result in inconsistencies between what's displayed in the
            # UI and the actual spec loaded at the workflow engine level.
            # More info: https://github.com/reanahub/reana-client/pull/462#discussion_r585794297
            reana_yaml["workflow"]["specification"] = None

        return reana_yaml
    except Exception as e:
        raise e


def is_uuid_v4(uuid_or_name):
    """Check if given string is a valid UUIDv4."""
    # Based on https://gist.github.com/ShawnMilo/7777304
    try:
        uuid = UUID(uuid_or_name, version=4)
    except Exception:
        return False

    return uuid.hex == uuid_or_name.replace("-", "")


def get_workflow_name_and_run_number(workflow_name):
    """Return name and run_number of a workflow.

    :param workflow_name: String representing Workflow name.

        Name might be in format 'reana.workflow.123' with arbitrary
        number of dot-delimited substrings, where last substring specifies
        the run number of the workflow this workflow name refers to.

        If name does not contain a valid run number, name without run number
        is returned.
    """
    # Try to split a dot-separated string.
    try:
        name, run_number = workflow_name.split(".", 1)

        try:
            float(run_number)
        except ValueError:
            # `workflow_name` was split, so it is a dot-separated string
            # but it didn't contain a valid `run_number`.
            # Assume that this dot-separated string is the name of
            # the workflow and return just this without a `run_number`.
            return workflow_name, ""

        return name, run_number

    except ValueError:
        # Couldn't split. Probably not a dot-separated string.
        # Return the name given as parameter without a `run_number`.
        return workflow_name, ""


def get_workflow_root():
    """Return the current workflow root directory."""
    reana_yaml = get_reana_yaml_file_path()
    workflow_root = os.getcwd()
    while True:
        file_list = os.listdir(workflow_root)
        parent_dir = os.path.dirname(workflow_root)
        if reana_yaml in file_list:
            break
        else:
            if workflow_root == parent_dir:
                display_message(
                    "Not a workflow directory (or any of the parent directories).\n"
                    "Please upload from inside the directory containing "
                    "the reana.yaml file of your workflow.",
                    msg_type="error",
                )
                sys.exit(1)
            else:
                workflow_root = parent_dir
    workflow_root += "/"
    return workflow_root


def get_workflow_status_change_msg(workflow, status):
    """Choose verb conjugation depending on status.

    :param workflow: Workflow name whose status changed.
    :param status: String which represents the status the workflow changed to.
    """
    return "{workflow} {verb} {status}".format(
        workflow=workflow, verb=get_workflow_status_change_verb(status), status=status
    )


def parse_secret_from_literal(literal):
    """Parse a literal string, into a secret dict.

    :param literal: String containg a key and a value. (e.g. 'KEY=VALUE')
    :returns secret: Dictionary in the format suitable for sending
    via http request.
    """
    try:
        key, value = literal.split("=", 1)
        secret = {
            key: {
                "value": base64.b64encode(value.encode("utf-8")).decode("utf-8"),
                "type": "env",
            }
        }
        return secret

    except ValueError as e:
        logging.debug(traceback.format_exc())
        logging.debug(str(e))
        display_message(
            'Option "{0}" is invalid: \n'
            'For literal strings use "SECRET_NAME=VALUE" format'.format(literal),
            msg_type="error",
        )
        sys.exit(1)


def parse_secret_from_path(path):
    """Parse a file path into a secret dict.

    :param path: Path of the file containing secret
    :returns secret: Dictionary in the format suitable for sending
     via http request.
    """
    try:
        with open(os.path.expanduser(path), "rb") as file:
            file_name = os.path.basename(path)
            secret = {
                file_name: {
                    "value": base64.b64encode(file.read()).decode("utf-8"),
                    "type": "file",
                }
            }
        return secret
    except FileNotFoundError as e:
        logging.debug(traceback.format_exc())
        logging.debug(str(e))
        display_message(
            "File {0} could not be uploaded: {0} does not exist.".format(path),
            msg_type="error",
        )
        sys.exit(1)


def get_api_url():
    """Obtain REANA server API URL."""
    server_url = os.getenv("REANA_SERVER_URL")
    return server_url.strip(" \t\n\r/") if server_url else None


def get_reana_yaml_file_path():
    """REANA specification file location."""
    matches = [path for path in reana_yaml_valid_file_names if os.path.exists(path)]
    if len(matches) == 0:
        display_message(
            "No REANA specification file (reana.yaml) found. Exiting.",
            msg_type="error",
        )
        sys.exit(1)
    if len(matches) > 1:
        display_message(
            "Found {0} REANA specification files ({1}). "
            "Please use only one. Exiting.".format(len(matches), ", ".join(matches)),
            msg_type="error",
        )
        sys.exit(1)
    for path in reana_yaml_valid_file_names:
        if os.path.exists(path):
            return path
    # If none of the valid paths exists, fall back to reana.yaml.
    return "reana.yaml"
