import os
import sys

import salt.cli.caller
import salt.defaults.exitcodes
import salt.utils.parsers
import salt.utils.tracing
import salt.utils.verify
from salt.config import _expand_glob_path, prepend_root_dir


class SaltCall(salt.utils.parsers.SaltCallOptionParser):
    """
    Used to locally execute a salt command
    """

    def run(self):
        """
        Execute the salt call!
        """
        self.parse_args()

        if self.options.file_root:
            file_roots = []
            for file_root in self.options.file_root:
                # check if the argument is pointing to a file on disk
                file_roots.append(os.path.abspath(file_root))
            self.config["file_roots"] = {"base": _expand_glob_path(file_roots)}

        if self.options.pillar_root:
            pillar_roots = []
            for pillar_root in self.options.pillar_root:
                # check if the argument is pointing to a file on disk
                pillar_roots.append(os.path.abspath(pillar_root))
            self.config["pillar_roots"] = {"base": _expand_glob_path(pillar_roots)}

        if self.options.states_dir:
            states_dirs = []
            for states_dir in self.options.states_dir:
                # check if the argument is pointing to a file on disk
                states_dirs.append(os.path.abspath(states_dir))
            self.config["states_dirs"] = states_dirs

        # Warn when the user passed local-roots overrides without --local.
        # Without --local the remote file client retrieves state/pillar data
        # from the master, which silently overwrites the local file_roots /
        # pillar_roots / states_dirs configured above (see #68137).
        if not self.options.local:
            ignored = []
            if self.options.file_root:
                ignored.append("--file-root")
            if self.options.pillar_root:
                ignored.append("--pillar-root")
            if self.options.states_dir:
                ignored.append("--states-dir")
            if ignored:
                sys.stderr.write(
                    "Warning: {} {} ignored because --local was not "
                    "specified; the remote file client retrieves these "
                    "from the master. Re-run with --local to use the "
                    "local override.\n".format(
                        ", ".join(ignored),
                        "is" if len(ignored) == 1 else "are",
                    )
                )

        if self.options.local:
            self.config["file_client"] = "local"
        if self.options.master:
            self.config["master"] = self.options.master

        if self.options.cachedir and self.config.get(
            "extension_modules"
        ) == os.path.join(self.config.get("__cachedir"), "extmods"):
            # Override `extension_modules`, but only in case if it was autogenerated
            cache_dir = os.path.abspath(self.options.cachedir)
            self.config["cachedir"] = cache_dir
            self.config["extension_modules"] = os.path.join(cache_dir, "extmods")
            prepend_root_dir(self.config, ["cachedir", "extension_modules"])

        if self.config["verify_env"]:
            # When --priv is used, MergeConfigMixIn has already overwritten
            # config["user"] with the --priv value during parse_args().  We need
            # the user that is actually *configured* in the config files so that
            # verify_env chowns directories to the right owner (e.g. 'salt') rather
            # than the temporary execution-time override (e.g. 'root').
            if self.options.user:
                import salt.config as _salt_config

                _raw_opts = _salt_config.minion_config(
                    self.get_config_file_path(), cache_minion_id=False
                )
                _verify_user = _raw_opts.get("user", "root")
            else:
                _verify_user = self.config["user"]

            salt.utils.verify.verify_env(
                [
                    self.config["pki_dir"],
                    self.config["cachedir"],
                    self.config["extension_modules"],
                ],
                _verify_user,
                permissive=self.config["permissive_pki_access"],
                pki_dir=self.config["pki_dir"],
            )

        # config["user"] is already set to the --priv value by MergeConfigMixIn.
        # The explicit assignment below is kept for clarity and backward compatibility
        # in case MergeConfigMixIn behaviour ever changes.
        if self.options.user:
            self.config["user"] = self.options.user

        # Validate the execution user exists
        if self.config["user"] != salt.utils.user.get_user():
            if not salt.utils.verify.check_user(self.config["user"]):
                self.exit(salt.defaults.exitcodes.EX_NOUSER)

        caller = salt.cli.caller.Caller.factory(self.config)

        if self.options.doc:
            caller.print_docs()
            self.exit(salt.defaults.exitcodes.EX_OK)

        if self.options.grains_run:
            caller.print_grains()
            self.exit(salt.defaults.exitcodes.EX_OK)

        salt.utils.tracing.configure({**self.config, "__role": "minion"})
        env_carrier = {
            k: v
            for k, v in (
                ("traceparent", os.environ.get("TRACEPARENT", "")),
                ("tracestate", os.environ.get("TRACESTATE", "")),
            )
            if v
        }
        trace_ctx = salt.utils.tracing.extract(env_carrier)
        with salt.utils.tracing.start_span(
            (
                f"salt-call.{self.config.get('fun', '')}"
                if self.config.get("fun")
                else "salt-call"
            ),
            attributes={"salt.fun": self.config.get("fun", "")},
            context=trace_ctx,
        ):
            try:
                caller.run()
            finally:
                salt.utils.tracing.shutdown()
