"""
Rust Cargo build actions
"""

import logging
import os

from aws_lambda_builders.actions import ActionFailedError, BaseAction, Purpose
from aws_lambda_builders.architecture import ARM64, X86_64
from aws_lambda_builders.workflow import BuildMode

from .exceptions import CargoLambdaExecutionException
from .utils import OSUtils

LOG = logging.getLogger(__name__)


class RustCargoLambdaBuildAction(BaseAction):
    NAME = "CargoLambdaBuild"
    DESCRIPTION = "Building the project using Cargo Lambda"
    PURPOSE = Purpose.COMPILE_SOURCE

    def __init__(
        self,
        source_dir,
        binaries,
        mode,
        subprocess_cargo_lambda,
        runtime=None,
        architecture=X86_64,
        handler=None,
        flags=None,
    ):
        """
        Build the a Rust executable

        :type source_dir: str
        :param source_dir:
            Path to a folder containing the source code

        :type binaries: dict
        :param binaries:
            Resolved path dependencies

        :type mode: str
        :param mode:
            Mode the build should produce

        :type architecture: str, optional
        :param architecture:
            Target architecture to build the binary, either arm64 or x86_64

        :type handler: str, optional
        :param handler:
            Handler name in `bin_name` format

        :type flags: list, optional
        :param flags:
            Extra list of flags to pass to `cargo lambda build`

        :type subprocess_cargo_lambda: aws_lambda_builders.workflows.rust_cargo.cargo_lambda.SubprocessCargoLambda
        :param subprocess_cargo_lambda: An instance of the Cargo Lambda process wrapper
        """

        self._source_dir = source_dir
        self._mode = mode
        self._binaries = binaries
        self._handler = handler
        self._flags = flags
        self._architecture = architecture
        self._subprocess_cargo_lambda = subprocess_cargo_lambda
        self._runtime = runtime

    def build_command(self):
        cmd = [self._binaries["cargo"].binary_path, "lambda", "build"]
        if self._mode != BuildMode.DEBUG:
            cmd.append("--release")
        # Provided.al2 runtime only has GLIB_2.26 and cargo lambda builds with a higher version by default
        if self._runtime == "provided.al2":
            if self._architecture == ARM64:
                # We cant use the --arm shortcut because then the incorrect version of GLIBC is used
                cmd.append("--target")
                cmd.append("aarch64-unknown-linux-gnu.2.26")
            if self._architecture == X86_64:
                cmd.append("--target")
                cmd.append("x86_64-unknown-linux-gnu.2.26")
        elif self._architecture == ARM64:
            cmd.append("--arm64")
        if self._handler:
            cmd.extend(["--bin", self._handler])
        if self._flags:
            cmd.extend(self._flags)
        return cmd

    def execute(self):
        try:
            return self._subprocess_cargo_lambda.run(command=self.build_command(), cwd=self._source_dir)
        except CargoLambdaExecutionException as ex:
            raise ActionFailedError(str(ex))


class RustCopyAndRenameAction(BaseAction):
    NAME = "RustCopyAndRename"
    DESCRIPTION = "Copy Rust executable, renaming if needed"
    PURPOSE = Purpose.COPY_SOURCE

    def __init__(self, source_dir, artifacts_dir, handler=None, osutils=OSUtils()):
        """
        Copy and rename Rust executable

        Parameters
        ----------
        source_dir : str
            Path to a folder containing the source code

        artifacts_dir : str
            Path to a folder containing the deployable artifacts

        handler : str, optional
            Handler name in `package.bin_name` or `bin_name` format

        osutils : aws_lambda_builders.workflows.rust_cargo.utils.OSUtils, optional
            Optional, External IO utils
        """
        self._source_dir = source_dir
        self._handler = handler
        self._artifacts_dir = artifacts_dir
        self._osutils = osutils

    def base_path(self):
        return os.path.join(self._source_dir, "target", "lambda")

    def binary_path(self):
        base = self.base_path()
        if self._handler:
            binary_path = os.path.join(base, self._handler, "bootstrap")
            LOG.debug("copying function binary from %s", binary_path)
            return binary_path

        output = os.listdir(base)
        if len(output) == 1:
            binary_path = os.path.join(base, output[0], "bootstrap")
            LOG.debug("copying function binary from %s", binary_path)
            return binary_path

        LOG.warning("unexpected list of binary directories: [%s]", ", ".join(output))
        raise CargoLambdaExecutionException(
            message="unable to find function binary, use the option `artifact_executable_name` to specify the name"
        )

    def execute(self):
        self._osutils.makedirs(self._artifacts_dir)
        binary_path = self.binary_path()
        destination_path = os.path.join(self._artifacts_dir, "bootstrap")
        LOG.debug("copying function binary from %s to %s", binary_path, destination_path)
        self._osutils.copyfile(binary_path, destination_path)
