package git

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"os/exec"
	"strings"
	"time"

	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/config"
)

const (
	defaultCloneTimeout = 10 * time.Minute
	defaultOpTimeout    = 2 * time.Minute
)

// Runner executes git commands.
type Runner interface {
	Run(ctx context.Context, dir string, args ...string) (stdout, stderr string, err error)
}

// ShellRunner runs git via the local shell.
type ShellRunner struct {
	gitPath     string
	cfg         config.Config
	logger      *slog.Logger
	authGitArgs []string
	authEnv     []string
}

// NewShellRunner creates a git shell runner with Bitbucket HTTP auth when configured.
func NewShellRunner(cfg config.Config, logger *slog.Logger) (*ShellRunner, error) {
	gitPath := os.Getenv("GIT_BIN")
	if gitPath == "" {
		var err error
		gitPath, err = exec.LookPath("git")
		if err != nil {
			return nil, fmt.Errorf("find git binary: %w", err)
		}
	}

	authGitArgs, authEnv := BitbucketCredentialAuth(cfg.Bitbucket.Username, cfg.Bitbucket.Token)

	return &ShellRunner{
		gitPath:     gitPath,
		cfg:         cfg,
		logger:      logger,
		authGitArgs: authGitArgs,
		authEnv:     authEnv,
	}, nil
}

// Run executes a git command in dir with optional auth and timeout.
func (r *ShellRunner) Run(ctx context.Context, dir string, args ...string) (string, string, error) {
	if err := r.ensureSandbox(dir); err != nil {
		return "", "", err
	}

	runCtx, cancel := r.withTimeout(ctx, args)
	defer cancel()

	started := time.Now()
	fullArgs := append(append([]string{}, r.authGitArgs...), args...)

	cmd := exec.CommandContext(runCtx, r.gitPath, fullArgs...)
	if dir != "" {
		cmd.Dir = dir
	}
	cmd.Env = os.Environ()
	if len(r.authEnv) > 0 {
		cmd.Env = append(cmd.Env, r.authEnv...)
	}

	var stdout, stderr strings.Builder
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	err := cmd.Run()
	outStr := stdout.String()
	errStr := stderr.String()
	if err != nil {
		redacted := RedactSecrets(errStr, r.cfg.Bitbucket.Username, r.cfg.Bitbucket.Token)
		if r.logger != nil {
			r.logger.Error("git command failed",
				"dir", dir,
				"args", redactArgs(args),
				"stderr", redacted,
			)
		}
		return outStr, errStr, fmt.Errorf("git %s: %w", redactArgs(args), err)
	}
	if r.logger != nil {
		r.logger.Info("git command completed",
			"dir", dir,
			"args", redactArgs(args),
			"duration_ms", time.Since(started).Milliseconds(),
		)
	}
	return outStr, errStr, nil
}

// RunWithCredentials executes git with per-request HTTP credentials (gateway import path).
func (r *ShellRunner) RunWithCredentials(ctx context.Context, dir, username, password string, args ...string) (string, string, error) {
	authGitArgs, authEnv := CredentialAuth(username, password)
	if err := r.ensureSandbox(dir); err != nil {
		return "", "", err
	}

	runCtx, cancel := r.withTimeout(ctx, args)
	defer cancel()

	fullArgs := append(append([]string{}, authGitArgs...), args...)
	cmd := exec.CommandContext(runCtx, r.gitPath, fullArgs...)
	if dir != "" {
		cmd.Dir = dir
	}
	cmd.Env = os.Environ()
	if len(authEnv) > 0 {
		cmd.Env = append(cmd.Env, authEnv...)
	}

	var stdout, stderr strings.Builder
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	err := cmd.Run()
	outStr := stdout.String()
	errStr := stderr.String()
	if err != nil {
		redacted := RedactSecrets(errStr, username, password)
		if r.logger != nil {
			r.logger.Error("git command failed",
				"dir", dir,
				"args", redactArgs(args),
				"stderr", redacted,
			)
		}
		return outStr, errStr, fmt.Errorf("git %s: %w", redactArgs(args), err)
	}
	return outStr, errStr, nil
}

func (r *ShellRunner) withTimeout(ctx context.Context, args []string) (context.Context, context.CancelFunc) {
	if _, hasDeadline := ctx.Deadline(); hasDeadline {
		return ctx, func() {}
	}
	timeout := defaultOpTimeout
	if len(args) > 0 && args[0] == "clone" {
		timeout = defaultCloneTimeout
	}
	return context.WithTimeout(ctx, timeout)
}

func (r *ShellRunner) ensureSandbox(dir string) error {
	if dir == "" {
		return nil
	}
	workspace := r.cfg.Git.WorkspacePath
	rel, err := isUnderWorkspace(workspace, dir)
	if err != nil {
		return fmt.Errorf("sandbox check: %w", err)
	}
	if !rel {
		return fmt.Errorf("git dir %q is outside workspace %q", dir, workspace)
	}
	return nil
}

func redactArgs(args []string) string {
	if len(args) == 0 {
		return ""
	}
	return strings.Join(args, " ")
}
