# Copyright Daytona Platforms Inc.
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

from daytona_toolbox_api_client import (
    GitAddRemoteRequest,
    GitAddRequest,
    GitApi,
    GitAuthenticateRequest,
    GitBranchRequest,
    GitCheckoutRequest,
    GitCloneRequest,
    GitCommitRequest,
    GitConfigureUserRequest,
    GitDeleteBranchRequest,
    GitInitRequest,
    GitPullRequest,
    GitPushRequest,
    GitResetRequest,
    GitRestoreRequest,
    GitSetConfigRequest,
    GitStatus,
    ListBranchResponse,
    ListRemotesResponse,
)

from .._utils.errors import intercept_errors
from .._utils.otel_decorator import with_instrumentation
from .._utils.timeout import http_timeout
from ..common.git import GitCommitResponse


class Git:
    """Provides Git operations within a Sandbox.

    Example:
        ```python
        # Clone a repository
        sandbox.git.clone(
            url="https://github.com/user/repo.git",
            path="workspace/repo"
        )

        # Check repository status
        status = sandbox.git.status("workspace/repo")
        print(f"Modified files: {status.modified}")

        # Stage and commit changes
        sandbox.git.add("workspace/repo", ["file.txt"])
        sandbox.git.commit(
            path="workspace/repo",
            message="Update file",
            author="John Doe",
            email="john@example.com"
        )
        ```
    """

    def __init__(
        self,
        api_client: GitApi,
    ):
        """Initializes a new Git handler instance.

        Args:
            api_client (GitApi): API client for Sandbox Git operations.
        """
        self._api_client: GitApi = api_client

    @intercept_errors(message_prefix="Failed to add files: ")
    @with_instrumentation()
    def add(self, path: str, files: list[str], request_timeout: float | None = None) -> None:
        """Stages the specified files for the next commit, similar to
        running 'git add' on the command line.

        Args:
            path (str): Path to the Git repository root. Relative paths are resolved based on
            the sandbox working directory.
            files (list[str]): List of file paths or directories to stage, relative to the repository root.
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Example:
            ```python
            # Stage a single file
            sandbox.git.add("workspace/repo", ["file.txt"])

            # Stage multiple files
            sandbox.git.add("workspace/repo", [
                "src/main.py",
                "tests/test_main.py",
                "README.md"
            ])
            ```
        """
        self._api_client.add_files(
            request=GitAddRequest(path=path, files=files),
            _request_timeout=http_timeout(request_timeout),
        )

    @intercept_errors(message_prefix="Failed to list branches: ")
    @with_instrumentation()
    def branches(self, path: str, request_timeout: float | None = None) -> ListBranchResponse:
        """Lists branches in the repository.

        Args:
            path (str): Path to the Git repository root. Relative paths are resolved based on
            the sandbox working directory.
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Returns:
            ListBranchResponse: List of branches in the repository.

        Example:
            ```python
            response = sandbox.git.branches("workspace/repo")
            print(f"Branches: {response.branches}")
            ```
        """
        return self._api_client.list_branches(
            path=path,
            _request_timeout=http_timeout(request_timeout),
        )

    @intercept_errors(message_prefix="Failed to clone repository: ")
    @with_instrumentation()
    def clone(
        self,
        url: str,
        path: str,
        branch: str | None = None,
        commit_id: str | None = None,
        username: str | None = None,
        password: str | None = None,
        insecure_skip_tls: bool | None = None,
        depth: int | None = None,
        request_timeout: float | None = None,
    ) -> None:
        """Clones a Git repository into the specified path. It supports
        cloning specific branches or commits, and can authenticate with the remote
        repository if credentials are provided.

        Args:
            url (str): Repository URL to clone from.
            path (str): Path where the repository should be cloned. Relative paths are resolved
            based on the sandbox working directory.
            branch (str | None): Specific branch to clone. If not specified,
                clones the default branch.
            commit_id (str | None): Specific commit to clone. If specified,
                the repository will be left in a detached HEAD state at this commit.
            username (str | None): Git username for authentication.
            password (str | None): Git password or token for authentication.
            insecure_skip_tls (bool | None): Skip TLS certificate verification (insecure).
                Use only for trusted internal Git servers with self-signed or private-CA certs;
                credentials, if supplied, are transmitted over an unverified TLS connection.
            depth (int | None): Create a shallow clone truncated to the given number of commits.
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Example:
            ```python
            # Clone the default branch
            sandbox.git.clone(
                url="https://github.com/user/repo.git",
                path="workspace/repo"
            )

            # Clone a specific branch with authentication
            sandbox.git.clone(
                url="https://github.com/user/private-repo.git",
                path="workspace/private",
                branch="develop",
                username="user",
                password="token"
            )

            # Clone a specific commit
            sandbox.git.clone(
                url="https://github.com/user/repo.git",
                path="workspace/repo-old",
                commit_id="abc123"
            )
            ```
        """
        self._api_client.clone_repository(
            request=GitCloneRequest(
                url=url,
                branch=branch,
                path=path,
                username=username,
                password=password,
                commit_id=commit_id,
                insecure_skip_tls=insecure_skip_tls,
                depth=depth,
            ),
            _request_timeout=http_timeout(request_timeout),
        )

    @intercept_errors(message_prefix="Failed to commit changes: ")
    @with_instrumentation()
    def commit(
        self,
        path: str,
        message: str,
        author: str,
        email: str,
        allow_empty: bool = False,
        request_timeout: float | None = None,
    ) -> GitCommitResponse:
        """Creates a new commit with the staged changes. Make sure to stage
        changes using the add() method before committing.

        Args:
            path (str): Path to the Git repository root. Relative paths are resolved based on
            the sandbox working directory.
            message (str): Commit message describing the changes.
            author (str): Name of the commit author.
            email (str): Email address of the commit author.
            allow_empty (bool, optional): Allow creating an empty commit when no changes are staged. Defaults to False.
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Example:
            ```python
            # Stage and commit changes
            sandbox.git.add("workspace/repo", ["README.md"])
            sandbox.git.commit(
                path="workspace/repo",
                message="Update documentation",
                author="John Doe",
                email="john@example.com",
                allow_empty=True
            )
            ```
        """
        response = self._api_client.commit_changes(
            request=GitCommitRequest(
                path=path,
                message=message,
                author=author,
                email=email,
                allow_empty=allow_empty,
            ),
            _request_timeout=http_timeout(request_timeout),
        )
        return GitCommitResponse(sha=response.hash)

    @intercept_errors(message_prefix="Failed to push changes: ")
    @with_instrumentation()
    def push(
        self,
        path: str,
        username: str | None = None,
        password: str | None = None,
        branch: str | None = None,
        remote: str | None = None,
        set_upstream: bool = False,
        request_timeout: float | None = None,
    ) -> None:
        """Pushes all local commits on the current branch to the remote
        repository. If the remote repository requires authentication, provide
        username and password/token.

        Args:
            path (str): Path to the Git repository root. Relative paths are resolved based on
            the sandbox working directory.
            username (str | None): Git username for authentication.
            password (str | None): Git password or token for authentication.
            branch (str | None): Branch to push. Defaults to the current branch.
            remote (str | None): Remote to push to. Defaults to "origin".
            set_upstream (bool, optional): Record the pushed branch as the upstream tracking
                branch. Defaults to False.
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Example:
            ```python
            # Push without authentication (for public repos or SSH)
            sandbox.git.push("workspace/repo")

            # Push with authentication
            sandbox.git.push(
                path="workspace/repo",
                username="user",
                password="github_token"
            )

            # Push a new branch and set its upstream
            sandbox.git.push("workspace/repo", branch="feature", set_upstream=True)
            ```
        """
        self._api_client.push_changes(
            request=GitPushRequest(
                path=path,
                username=username,
                password=password,
                branch=branch,
                remote=remote,
                set_upstream=set_upstream,
            ),
            _request_timeout=http_timeout(request_timeout),
        )

    @intercept_errors(message_prefix="Failed to pull changes: ")
    @with_instrumentation()
    def pull(
        self,
        path: str,
        username: str | None = None,
        password: str | None = None,
        branch: str | None = None,
        remote: str | None = None,
        request_timeout: float | None = None,
    ) -> None:
        """Pulls changes from the remote repository. If the remote repository requires authentication,
        provide username and password/token.

        Args:
            path (str): Path to the Git repository root. Relative paths are resolved based on
            the sandbox working directory.
            username (str | None): Git username for authentication.
            password (str | None): Git password or token for authentication.
            branch (str | None): Branch to pull. Defaults to the current branch's upstream.
            remote (str | None): Remote to pull from. Defaults to "origin".
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Example:
            ```python
            # Pull without authentication
            sandbox.git.pull("workspace/repo")

            # Pull with authentication
            sandbox.git.pull(
                path="workspace/repo",
                username="user",
                password="github_token"
            )

            # Pull a specific branch from a specific remote
            sandbox.git.pull("workspace/repo", remote="upstream", branch="main")
            ```
        """
        self._api_client.pull_changes(
            request=GitPullRequest(
                path=path,
                username=username,
                password=password,
                branch=branch,
                remote=remote,
            ),
            _request_timeout=http_timeout(request_timeout),
        )

    @intercept_errors(message_prefix="Failed to get status: ")
    @with_instrumentation()
    def status(self, path: str, request_timeout: float | None = None) -> GitStatus:
        """Gets the current Git repository status.

        Args:
            path (str): Path to the Git repository root. Relative paths are resolved based on
            the sandbox working directory.
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Returns:
            GitStatus: Repository status information including:
                - current_branch: Current branch name
                - file_status: List of file statuses
                - ahead: Number of local commits not pushed to remote
                - behind: Number of remote commits not pulled locally
                - branch_published: Whether the branch has been published to the remote repository

        Example:
            ```python
            status = sandbox.git.status("workspace/repo")
            print(f"On branch: {status.current_branch}")
            print(f"Commits ahead: {status.ahead}")
            print(f"Commits behind: {status.behind}")
            ```
        """
        return self._api_client.get_status(
            path=path,
            _request_timeout=http_timeout(request_timeout),
        )

    @intercept_errors(message_prefix="Failed to checkout branch: ")
    @with_instrumentation()
    def checkout_branch(self, path: str, branch: str, request_timeout: float | None = None) -> None:
        """Checkout branch in the repository.

        Args:
            path (str): Path to the Git repository root. Relative paths are resolved based on
            the sandbox working directory.
            branch (str): Name of the branch to checkout
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Example:
            ```python
            # Checkout a branch
            sandbox.git.checkout_branch("workspace/repo", "feature-branch")
            ```
        """
        self._api_client.checkout_branch(
            request=GitCheckoutRequest(
                path=path,
                branch=branch,
            ),
            _request_timeout=http_timeout(request_timeout),
        )

    @intercept_errors(message_prefix="Failed to create branch: ")
    @with_instrumentation()
    def create_branch(self, path: str, name: str, request_timeout: float | None = None) -> None:
        """Create branch in the repository.

        Args:
            path (str): Path to the Git repository root. Relative paths are resolved based on
            the sandbox working directory.
            name (str): Name of the new branch to create
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Example:
            ```python
            # Create a new branch
            sandbox.git.create_branch("workspace/repo", "new-feature")
            ```
        """
        self._api_client.create_branch(
            request=GitBranchRequest(
                path=path,
                name=name,
            ),
            _request_timeout=http_timeout(request_timeout),
        )

    @intercept_errors(message_prefix="Failed to delete branch: ")
    @with_instrumentation()
    def delete_branch(self, path: str, name: str, request_timeout: float | None = None) -> None:
        """Delete branch in the repository.

        Args:
            path (str): Path to the Git repository root. Relative paths are resolved based on
            the sandbox working directory.
            name (str): Name of the branch to delete
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Example:
            ```python
            # Delete a branch
            sandbox.git.delete_branch("workspace/repo", "old-feature")
            ```
        """
        self._api_client.delete_branch(
            request=GitDeleteBranchRequest(
                path=path,
                name=name,
            ),
            _request_timeout=http_timeout(request_timeout),
        )

    @intercept_errors(message_prefix="Failed to initialize repository: ")
    @with_instrumentation()
    def init(
        self, path: str, bare: bool = False, initial_branch: str | None = None, request_timeout: float | None = None
    ) -> None:
        """Initializes a new Git repository at the specified path.

        Args:
            path (str): Path where the repository should be initialized. Relative paths are
            resolved based on the sandbox working directory.
            bare (bool, optional): Create a bare repository without a working tree. Defaults to False.
            initial_branch (str | None): Name of the initial branch. If not specified, uses the
                Git default.
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Example:
            ```python
            sandbox.git.init("workspace/repo", initial_branch="main")
            ```
        """
        self._api_client.init_repository(
            request=GitInitRequest(
                path=path,
                bare=bare,
                initial_branch=initial_branch,
            ),
            _request_timeout=http_timeout(request_timeout),
        )

    @intercept_errors(message_prefix="Failed to reset: ")
    @with_instrumentation()
    def reset(
        self,
        path: str,
        mode: str | None = None,
        target: str | None = None,
        files: list[str] | None = None,
        request_timeout: float | None = None,
    ) -> None:
        """Resets the current HEAD to the specified state.

        Args:
            path (str): Path to the Git repository root. Relative paths are resolved based on
            the sandbox working directory.
            mode (str | None): Reset mode, one of "soft", "mixed" (default), "hard", "merge" or "keep".
            target (str | None): Revision to reset to. Defaults to HEAD.
            files (list[str] | None): Constrain the reset to the given paths.
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Example:
            ```python
            # Unstage all changes (mixed reset to HEAD)
            sandbox.git.reset("workspace/repo")

            # Hard reset to a previous commit
            sandbox.git.reset("workspace/repo", mode="hard", target="HEAD~1")
            ```
        """
        self._api_client.reset_changes(
            request=GitResetRequest(
                path=path,
                mode=mode,
                target=target,
                files=files,
            ),
            _request_timeout=http_timeout(request_timeout),
        )

    @intercept_errors(message_prefix="Failed to restore files: ")
    @with_instrumentation()
    def restore(
        self,
        path: str,
        files: list[str],
        staged: bool | None = None,
        worktree: bool | None = None,
        source: str | None = None,
        request_timeout: float | None = None,
    ) -> None:
        """Restores working tree files or unstages changes.

        Args:
            path (str): Path to the Git repository root. Relative paths are resolved based on
            the sandbox working directory.
            files (list[str]): File paths to restore.
            staged (bool | None): Restore the staging index for the given files.
            worktree (bool | None): Restore the working tree for the given files. Defaults to
                True when neither staged nor worktree is provided.
            source (str | None): Restore file contents from the given revision instead of the index.
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Example:
            ```python
            # Discard working tree changes
            sandbox.git.restore("workspace/repo", ["file.txt"])

            # Unstage changes
            sandbox.git.restore("workspace/repo", ["file.txt"], staged=True)
            ```
        """
        self._api_client.restore_files(
            request=GitRestoreRequest(
                path=path,
                files=files,
                staged=staged,
                worktree=worktree,
                source=source,
            ),
            _request_timeout=http_timeout(request_timeout),
        )

    @intercept_errors(message_prefix="Failed to add remote: ")
    @with_instrumentation()
    def remote_add(
        self,
        path: str,
        name: str,
        url: str,
        fetch: bool = False,
        overwrite: bool = False,
        request_timeout: float | None = None,
    ) -> None:
        """Adds (or overwrites) a remote in the repository.

        Args:
            path (str): Path to the Git repository root. Relative paths are resolved based on
            the sandbox working directory.
            name (str): Name of the remote.
            url (str): URL of the remote.
            fetch (bool, optional): Fetch from the remote immediately after adding it. Defaults to False.
            overwrite (bool, optional): Replace an existing remote with the same name. Defaults to False.
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Example:
            ```python
            sandbox.git.remote_add("workspace/repo", "origin", "https://github.com/user/repo.git")
            ```
        """
        self._api_client.add_remote(
            request=GitAddRemoteRequest(
                path=path,
                name=name,
                url=url,
                fetch=fetch,
                overwrite=overwrite,
            ),
            _request_timeout=http_timeout(request_timeout),
        )

    @intercept_errors(message_prefix="Failed to list remotes: ")
    @with_instrumentation()
    def remotes(self, path: str, request_timeout: float | None = None) -> ListRemotesResponse:
        """Lists the remotes configured in the repository.

        Args:
            path (str): Path to the Git repository root. Relative paths are resolved based on
            the sandbox working directory.
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Returns:
            ListRemotesResponse: The configured remotes (name + URL).

        Example:
            ```python
            response = sandbox.git.remotes("workspace/repo")
            for remote in response.remotes:
                print(f"{remote.name}: {remote.url}")
            ```
        """
        return self._api_client.list_remotes(
            path=path,
            _request_timeout=http_timeout(request_timeout),
        )

    @intercept_errors(message_prefix="Failed to get remote: ")
    @with_instrumentation()
    def remote_get(self, path: str, name: str, request_timeout: float | None = None) -> str | None:
        """Gets the URL of a remote, or None when it does not exist.

        Args:
            path (str): Path to the Git repository root. Relative paths are resolved based on
            the sandbox working directory.
            name (str): Name of the remote.
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Returns:
            str | None: The remote URL, or None when the remote does not exist.

        Example:
            ```python
            url = sandbox.git.remote_get("workspace/repo", "origin")
            ```
        """
        response = self._api_client.list_remotes(path=path, _request_timeout=http_timeout(request_timeout))
        for remote in response.remotes:
            if remote.name == name:
                return remote.url
        return None

    @intercept_errors(message_prefix="Failed to set config: ")
    @with_instrumentation()
    def set_config(
        self, key: str, value: str, scope: str = "global", path: str | None = None, request_timeout: float | None = None
    ) -> None:
        """Sets a Git config value at the given scope.

        Args:
            key (str): Config key in dotted form (e.g. "user.name").
            value (str): Config value.
            scope (str, optional): Config scope, one of "global" (default), "local" or "system".
            path (str | None): Repository path, required when scope is "local".
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Example:
            ```python
            sandbox.git.set_config("user.name", "John Doe")
            ```
        """
        self._api_client.set_git_config(
            request=GitSetConfigRequest(
                path=path,
                key=key,
                value=value,
                scope=scope,
            ),
            _request_timeout=http_timeout(request_timeout),
        )

    @intercept_errors(message_prefix="Failed to get config: ")
    @with_instrumentation()
    def get_config(
        self, key: str, scope: str = "global", path: str | None = None, request_timeout: float | None = None
    ) -> str | None:
        """Gets a Git config value at the given scope, or None when unset.

        Args:
            key (str): Config key in dotted form (e.g. "user.name").
            scope (str, optional): Config scope, one of "global" (default), "local" or "system".
            path (str | None): Repository path, required when scope is "local".
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Returns:
            str | None: The config value, or None when the key is not set.

        Example:
            ```python
            name = sandbox.git.get_config("user.name")
            ```
        """
        response = self._api_client.get_git_config(
            key=key, scope=scope, path=path, _request_timeout=http_timeout(request_timeout)
        )
        return response.value

    @intercept_errors(message_prefix="Failed to configure user: ")
    @with_instrumentation()
    def configure_user(
        self,
        name: str,
        email: str,
        scope: str = "global",
        path: str | None = None,
        request_timeout: float | None = None,
    ) -> None:
        """Configures the Git user name and email at the given scope.

        Args:
            name (str): User name (user.name).
            email (str): User email (user.email).
            scope (str, optional): Config scope, one of "global" (default), "local" or "system".
            path (str | None): Repository path, required when scope is "local".
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Example:
            ```python
            sandbox.git.configure_user("John Doe", "john@example.com")
            ```
        """
        self._api_client.configure_user(
            request=GitConfigureUserRequest(
                path=path,
                name=name,
                email=email,
                scope=scope,
            ),
            _request_timeout=http_timeout(request_timeout),
        )

    @intercept_errors(message_prefix="Failed to authenticate: ")
    @with_instrumentation()
    def dangerously_authenticate(
        self,
        username: str,
        password: str,
        host: str | None = None,
        protocol: str | None = None,
        request_timeout: float | None = None,
    ) -> None:
        """Persists Git credentials globally so that subsequent operations against the
        given host authenticate automatically.

        Warning:
            This stores the password in plaintext on disk via the Git credential store.

        Args:
            username (str): Git username.
            password (str): Git password or token.
            host (str | None): Host to authenticate against. Defaults to "github.com".
            protocol (str | None): Protocol to authenticate against. Defaults to "https".
            request_timeout (float | None): Optional client-side request timeout in seconds. Client-side
                only. It bounds how long the SDK waits for the HTTP response and does not cancel
                the operation on the server. Positive values under 1 second are rounded up to 1
                second; 0 disables the client-side timeout and negative values are rejected.

        Example:
            ```python
            sandbox.git.dangerously_authenticate("user", "github_token")
            ```
        """
        self._api_client.authenticate(
            request=GitAuthenticateRequest(
                username=username,
                password=password,
                host=host,
                protocol=protocol,
            ),
            _request_timeout=http_timeout(request_timeout),
        )
