package action

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"

	"github.com/rockliang/poimen/workflows/internal/lock"
)

// CloneRepoInput is input to CloneRepoActivity.
type CloneRepoInput struct {
	RemoteURL      string
	TargetRepoPath string
}

// CloneRepoActivity clones a repo if it doesn't exist, or fetches if it does.
func CloneRepoActivity(ctx context.Context, in CloneRepoInput) error {
	// Check if repo already exists
	gitDir := filepath.Join(in.TargetRepoPath, ".git")
	if _, err := os.Stat(gitDir); err == nil {
		// Repo exists, fetch latest
		cmd := exec.CommandContext(ctx, "git", "-C", in.TargetRepoPath, "fetch", "origin")
		if err := cmd.Run(); err != nil {
			return fmt.Errorf("git fetch failed: %w", err)
		}
		return nil
	}

	// Repo doesn't exist, clone it
	cmd := exec.CommandContext(ctx, "git", "clone", in.RemoteURL, in.TargetRepoPath)
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("git clone failed: %w", err)
	}
	return nil
}

// GitWorktreeAddInput is input to GitWorktreeAddActivity.
type GitWorktreeAddInput struct {
	RepoPath string
	TaskID   string
}

// GitWorktreeAddActivity creates a new git worktree for a task.
func GitWorktreeAddActivity(ctx context.Context, in GitWorktreeAddInput) (string, error) {
	// Acquire lock to synchronize worktree creation
	lockPath := filepath.Join(in.RepoPath, "orchestrator.lock")
	if err := lock.Acquire(lockPath); err != nil {
		return "", fmt.Errorf("failed to acquire lock: %w", err)
	}
	defer lock.Release(lockPath)

	// Create worktrees directory if it doesn't exist
	worktreesDir := filepath.Join(in.RepoPath, "worktrees")
	if err := os.MkdirAll(worktreesDir, 0755); err != nil {
		return "", fmt.Errorf("failed to create worktrees dir: %w", err)
	}

	worktreePath := filepath.Join(worktreesDir, in.TaskID)
	branch := "task/" + in.TaskID

	// Create worktree
	cmd := exec.CommandContext(ctx, "git", "-C", in.RepoPath, "worktree", "add", "-b", branch, worktreePath, "origin/main")
	if err := cmd.Run(); err != nil {
		return "", fmt.Errorf("git worktree add failed: %w", err)
	}

	return worktreePath, nil
}

// GitCommitInput is input to GitCommitActivity.
type GitCommitInput struct {
	WorktreePath string
	Message      string
}

// GitCommitActivity commits changes in a worktree.
func GitCommitActivity(ctx context.Context, in GitCommitInput) error {
	// Configure git user for commits if not already configured
	// (worktrees don't inherit config from main repo)
	exec.CommandContext(ctx, "git", "-C", in.WorktreePath, "config", "user.email", "poimen@riotpiao.com").Run()
	exec.CommandContext(ctx, "git", "-C", in.WorktreePath, "config", "user.name", "Poimen Agent").Run()

	// Stage all changes
	cmd := exec.CommandContext(ctx, "git", "-C", in.WorktreePath, "add", "-A")
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("git add failed: %w", err)
	}

	// Commit
	cmd = exec.CommandContext(ctx, "git", "-C", in.WorktreePath, "commit", "-m", in.Message)
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("git commit failed: %w", err)
	}

	return nil
}

// GitPushInput is input to GitPushActivity.
type GitPushInput struct {
	RepoPath string
}

// GitPushActivity pushes changes to origin.
func GitPushActivity(ctx context.Context, in GitPushInput) error {
	// Acquire lock to synchronize push
	lockPath := filepath.Join(in.RepoPath, "orchestrator.lock")
	if err := lock.Acquire(lockPath); err != nil {
		return fmt.Errorf("failed to acquire lock: %w", err)
	}
	defer lock.Release(lockPath)

	cmd := exec.CommandContext(ctx, "git", "-C", in.RepoPath, "push", "origin", "main")
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("git push failed: %w", err)
	}

	return nil
}

// GitSquashMergeInput is input to GitSquashMergeActivity.
type GitSquashMergeInput struct {
	RepoPath string
	Branches []string
	Message  string
}

// GitSquashMergeActivity performs a squash merge of multiple branches into main.
func GitSquashMergeActivity(ctx context.Context, in GitSquashMergeInput) error {
	// Acquire lock to synchronize merge
	lockPath := filepath.Join(in.RepoPath, "orchestrator.lock")
	if err := lock.Acquire(lockPath); err != nil {
		return fmt.Errorf("failed to acquire lock: %w", err)
	}
	defer lock.Release(lockPath)

	// Fetch origin main
	cmd := exec.CommandContext(ctx, "git", "-C", in.RepoPath, "fetch", "origin", "main")
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("git fetch failed: %w", err)
	}

	// Checkout main and pull with ff-only
	cmd = exec.CommandContext(ctx, "git", "-C", in.RepoPath, "checkout", "main")
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("git checkout main failed: %w", err)
	}

	cmd = exec.CommandContext(ctx, "git", "-C", in.RepoPath, "pull", "--ff-only", "origin", "main")
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("git pull failed: %w", err)
	}

	// Squash merge each branch
	for _, branch := range in.Branches {
		cmd = exec.CommandContext(ctx, "git", "-C", in.RepoPath, "merge", "--squash", branch)
		if err := cmd.Run(); err != nil {
			return fmt.Errorf("git merge --squash %s failed: %w", branch, err)
		}
	}

	// Commit squashed changes
	cmd = exec.CommandContext(ctx, "git", "-C", in.RepoPath, "commit", "-m", in.Message)
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("git commit failed: %w", err)
	}

	// Push to origin
	cmd = exec.CommandContext(ctx, "git", "-C", in.RepoPath, "push", "origin", "main")
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("git push failed: %w", err)
	}

	// Clean up worktrees and branches
	for _, branch := range in.Branches {
		taskID := branch[len("task/"):]
		worktreePath := filepath.Join(in.RepoPath, "worktrees", taskID)

		// Remove worktree
		cmd = exec.CommandContext(ctx, "git", "-C", in.RepoPath, "worktree", "remove", worktreePath, "--force")
		if err := cmd.Run(); err != nil {
			// Log error but continue cleanup
			fmt.Printf("warning: failed to remove worktree %s: %v\n", worktreePath, err)
		}

		// Delete branch
		cmd = exec.CommandContext(ctx, "git", "-C", in.RepoPath, "branch", "-D", branch)
		if err := cmd.Run(); err != nil {
			// Log error but continue cleanup
			fmt.Printf("warning: failed to delete branch %s: %v\n", branch, err)
		}
	}

	return nil
}

// GitDiffInput is input to GitDiffActivity.
type GitDiffInput struct {
	WorktreePath string
}

// GitDiffOutput is output of GitDiffActivity.
type GitDiffOutput struct {
	Diff string
}

// GitDiffActivity gets git diff for a worktree.
func GitDiffActivity(ctx context.Context, in GitDiffInput) (GitDiffOutput, error) {
	out := GitDiffOutput{Diff: ""}

	// Get diff from worktree against main branch
	cmd := exec.CommandContext(ctx, "git", "-C", in.WorktreePath, "diff", "main")
	output, err := cmd.CombinedOutput()
	if err != nil {
		// Diff can fail if branch doesn't exist, treat as no changes
		return out, nil
	}

	out.Diff = string(output)
	return out, nil
}
