package models

import (
	"strings"
	"testing"

	"tangled.org/core/api/tangled"
	"tangled.org/core/workflow"
)

func sp(s string) *string { return &s }

func TestBuildCloneStep_PushTrigger(t *testing.T) {
	twf := tangled.Pipeline_Workflow{
		Clone: &tangled.Pipeline_CloneOpts{
			Depth:      1,
			Submodules: false,
			Skip:       false,
		},
	}
	tr := tangled.Pipeline_TriggerMetadata{
		Kind: string(workflow.TriggerKindPush),
		Push: &tangled.Pipeline_PushTriggerData{
			NewSha: "abc123",
			OldSha: "def456",
			Ref:    "refs/heads/main",
		},
		Repo: &tangled.Pipeline_TriggerRepo{
			Knot:    "example.com",
			Did:     "did:plc:user123",
			Repo:    sp("my-repo"),
			RepoDid: sp("did:plc:boltless"),
		},
	}

	step := BuildCloneStep(twf, tr, false)

	if step.Kind() != StepKindSystem {
		t.Errorf("Expected StepKindSystem, got %v", step.Kind())
	}

	if step.Name() != "Clone repository into workspace" {
		t.Errorf("Expected 'Clone repository into workspace', got '%s'", step.Name())
	}

	commands := step.Commands()
	if len(commands) != 4 {
		t.Errorf("Expected 4 commands, got %d", len(commands))
	}

	// Verify commands contain expected git operations
	allCmds := strings.Join(commands, " ")
	if !strings.Contains(allCmds, "git init") {
		t.Error("Commands should contain 'git init'")
	}
	if !strings.Contains(allCmds, "git remote add origin") {
		t.Error("Commands should contain 'git remote add origin'")
	}
	if !strings.Contains(allCmds, "git fetch") {
		t.Error("Commands should contain 'git fetch'")
	}
	if !strings.Contains(allCmds, "abc123") {
		t.Error("Commands should contain commit SHA")
	}
	if !strings.Contains(allCmds, "git checkout FETCH_HEAD") {
		t.Error("Commands should contain 'git checkout FETCH_HEAD'")
	}
	if !strings.Contains(allCmds, "https://example.com/did:plc:boltless") {
		t.Error("Commands should contain expected repo URL")
	}
}

func TestBuildCloneStep_PullRequestTrigger(t *testing.T) {
	twf := tangled.Pipeline_Workflow{
		Clone: &tangled.Pipeline_CloneOpts{
			Depth: 1,
			Skip:  false,
		},
	}
	tr := tangled.Pipeline_TriggerMetadata{
		Kind: string(workflow.TriggerKindPullRequest),
		PullRequest: &tangled.Pipeline_PullRequestTriggerData{
			SourceSha:    "pr-sha-789",
			SourceBranch: "feature-branch",
			TargetBranch: "main",
			Action:       "opened",
		},
		Repo: &tangled.Pipeline_TriggerRepo{
			Knot:    "example.com",
			Did:     "did:plc:user123",
			Repo:    sp("my-repo"),
			RepoDid: sp("did:plc:boltless"),
		},
	}

	step := BuildCloneStep(twf, tr, false)

	allCmds := strings.Join(step.Commands(), " ")
	if !strings.Contains(allCmds, "pr-sha-789") {
		t.Error("Commands should contain PR commit SHA")
	}
}

func TestBuildCloneStep_ManualTrigger(t *testing.T) {
	twf := tangled.Pipeline_Workflow{
		Clone: &tangled.Pipeline_CloneOpts{
			Depth: 1,
			Skip:  false,
		},
	}
	tr := tangled.Pipeline_TriggerMetadata{
		Kind: string(workflow.TriggerKindManual),
		Manual: &tangled.Pipeline_ManualTriggerData{
			Inputs: nil,
		},
		Repo: &tangled.Pipeline_TriggerRepo{
			Knot:    "example.com",
			Did:     "did:plc:user123",
			Repo:    sp("my-repo"),
			RepoDid: sp("did:plc:boltless"),
		},
	}

	step := BuildCloneStep(twf, tr, false)

	// Manual triggers don't have a SHA yet (TODO), so git fetch won't include a SHA
	allCmds := strings.Join(step.Commands(), " ")
	// Should still have basic git commands
	if !strings.Contains(allCmds, "git init") {
		t.Error("Commands should contain 'git init'")
	}
	if !strings.Contains(allCmds, "git fetch") {
		t.Error("Commands should contain 'git fetch'")
	}
}

func TestBuildCloneStep_SkipFlag(t *testing.T) {
	twf := tangled.Pipeline_Workflow{
		Clone: &tangled.Pipeline_CloneOpts{
			Skip: true,
		},
	}
	tr := tangled.Pipeline_TriggerMetadata{
		Kind: string(workflow.TriggerKindPush),
		Push: &tangled.Pipeline_PushTriggerData{
			NewSha: "abc123",
		},
		Repo: &tangled.Pipeline_TriggerRepo{
			Knot:    "example.com",
			Did:     "did:plc:user123",
			Repo:    sp("my-repo"),
			RepoDid: sp("did:plc:boltless"),
		},
	}

	step := BuildCloneStep(twf, tr, false)

	// Empty step when skip is true
	if step.Name() != "" {
		t.Error("Expected empty step name when Skip is true")
	}
	if len(step.Commands()) != 0 {
		t.Errorf("Expected no commands when Skip is true, got %d commands", len(step.Commands()))
	}
}

func TestBuildCloneStep_DevMode(t *testing.T) {
	twf := tangled.Pipeline_Workflow{
		Engine: "nixery",
		Clone: &tangled.Pipeline_CloneOpts{
			Depth: 1,
			Skip:  false,
		},
	}
	tr := tangled.Pipeline_TriggerMetadata{
		Kind: string(workflow.TriggerKindPush),
		Push: &tangled.Pipeline_PushTriggerData{
			NewSha: "abc123",
		},
		Repo: &tangled.Pipeline_TriggerRepo{
			Knot:    "localhost:3000",
			Did:     "did:plc:user123",
			Repo:    sp("my-repo"),
			RepoDid: sp("did:plc:boltless"),
		},
	}

	step := BuildCloneStep(twf, tr, true)

	// In dev mode, should use http:// and replace localhost with host.docker.internal
	allCmds := strings.Join(step.Commands(), " ")
	expectedURL := "http://host.docker.internal:3000/did:plc:boltless"
	if !strings.Contains(allCmds, expectedURL) {
		t.Errorf("Expected dev mode URL '%s' in commands", expectedURL)
	}
}

func TestBuildCloneStep_MicroVMDevMode(t *testing.T) {
	twf := tangled.Pipeline_Workflow{
		Engine: "microvm",
		Clone: &tangled.Pipeline_CloneOpts{
			Depth: 1,
			Skip:  false,
		},
	}
	tr := tangled.Pipeline_TriggerMetadata{
		Kind: string(workflow.TriggerKindPush),
		Push: &tangled.Pipeline_PushTriggerData{
			NewSha: "abc123",
		},
		Repo: &tangled.Pipeline_TriggerRepo{
			Knot:    "localhost:3000",
			Did:     "did:plc:user123",
			Repo:    sp("my-repo"),
			RepoDid: sp("did:plc:boltless"),
		},
	}

	step := BuildCloneStep(twf, tr, true)

	allCmds := strings.Join(step.Commands(), " ")
	expectedURL := "http://localhost:3000/did:plc:boltless"
	if !strings.Contains(allCmds, expectedURL) {
		t.Errorf("Expected microvm dev mode URL '%s' in commands", expectedURL)
	}
	if strings.Contains(allCmds, "10.0.2.2") {
		t.Errorf("MicroVM dev clone command must not route through the QEMU host gateway")
	}
}

func TestBuildCloneStep_DepthAndSubmodules(t *testing.T) {
	twf := tangled.Pipeline_Workflow{
		Clone: &tangled.Pipeline_CloneOpts{
			Depth:      10,
			Submodules: true,
			Skip:       false,
		},
	}
	tr := tangled.Pipeline_TriggerMetadata{
		Kind: string(workflow.TriggerKindPush),
		Push: &tangled.Pipeline_PushTriggerData{
			NewSha: "abc123",
		},
		Repo: &tangled.Pipeline_TriggerRepo{
			Knot:    "example.com",
			Did:     "did:plc:user123",
			Repo:    sp("my-repo"),
			RepoDid: sp("did:plc:boltless"),
		},
	}

	step := BuildCloneStep(twf, tr, false)

	allCmds := strings.Join(step.Commands(), " ")
	if !strings.Contains(allCmds, "--depth=10") {
		t.Error("Commands should contain '--depth=10'")
	}

	if !strings.Contains(allCmds, "--recurse-submodules=yes") {
		t.Error("Commands should contain '--recurse-submodules=yes'")
	}
}

func TestBuildCloneStep_DefaultDepth(t *testing.T) {
	twf := tangled.Pipeline_Workflow{
		Clone: &tangled.Pipeline_CloneOpts{
			Depth: 0, // Default should be 1
			Skip:  false,
		},
	}
	tr := tangled.Pipeline_TriggerMetadata{
		Kind: string(workflow.TriggerKindPush),
		Push: &tangled.Pipeline_PushTriggerData{
			NewSha: "abc123",
		},
		Repo: &tangled.Pipeline_TriggerRepo{
			Knot:    "example.com",
			Did:     "did:plc:user123",
			Repo:    sp("my-repo"),
			RepoDid: sp("did:plc:boltless"),
		},
	}

	step := BuildCloneStep(twf, tr, false)

	allCmds := strings.Join(step.Commands(), " ")
	if !strings.Contains(allCmds, "--depth=1") {
		t.Error("Commands should default to '--depth=1'")
	}
}

func TestBuildCloneStep_NilPushData(t *testing.T) {
	twf := tangled.Pipeline_Workflow{
		Clone: &tangled.Pipeline_CloneOpts{
			Depth: 1,
			Skip:  false,
		},
	}
	tr := tangled.Pipeline_TriggerMetadata{
		Kind: string(workflow.TriggerKindPush),
		Push: nil, // Nil push data should create error step
		Repo: &tangled.Pipeline_TriggerRepo{
			Knot:    "example.com",
			Did:     "did:plc:user123",
			Repo:    sp("my-repo"),
			RepoDid: sp("did:plc:boltless"),
		},
	}

	step := BuildCloneStep(twf, tr, false)

	// Should return an error step
	if !strings.Contains(step.Name(), "error") {
		t.Error("Expected error in step name when push data is nil")
	}

	allCmds := strings.Join(step.Commands(), " ")
	if !strings.Contains(allCmds, "Failed to get clone info") {
		t.Error("Commands should contain error message")
	}
	if !strings.Contains(allCmds, "exit 1") {
		t.Error("Commands should exit with error")
	}
}

func TestBuildCloneStep_NilPRData(t *testing.T) {
	twf := tangled.Pipeline_Workflow{
		Clone: &tangled.Pipeline_CloneOpts{
			Depth: 1,
			Skip:  false,
		},
	}
	tr := tangled.Pipeline_TriggerMetadata{
		Kind:        string(workflow.TriggerKindPullRequest),
		PullRequest: nil, // Nil PR data should create error step
		Repo: &tangled.Pipeline_TriggerRepo{
			Knot:    "example.com",
			Did:     "did:plc:user123",
			Repo:    sp("my-repo"),
			RepoDid: sp("did:plc:boltless"),
		},
	}

	step := BuildCloneStep(twf, tr, false)

	// Should return an error step
	if !strings.Contains(step.Name(), "error") {
		t.Error("Expected error in step name when pull request data is nil")
	}

	allCmds := strings.Join(step.Commands(), " ")
	if !strings.Contains(allCmds, "Failed to get clone info") {
		t.Error("Commands should contain error message")
	}
}

func TestBuildCloneStep_UnknownTriggerKind(t *testing.T) {
	twf := tangled.Pipeline_Workflow{
		Clone: &tangled.Pipeline_CloneOpts{
			Depth: 1,
			Skip:  false,
		},
	}
	tr := tangled.Pipeline_TriggerMetadata{
		Kind: "unknown_trigger",
		Repo: &tangled.Pipeline_TriggerRepo{
			Knot:    "example.com",
			Did:     "did:plc:user123",
			Repo:    sp("my-repo"),
			RepoDid: sp("did:plc:boltless"),
		},
	}

	step := BuildCloneStep(twf, tr, false)

	// Should return an error step
	if !strings.Contains(step.Name(), "error") {
		t.Error("Expected error in step name for unknown trigger kind")
	}

	allCmds := strings.Join(step.Commands(), " ")
	if !strings.Contains(allCmds, "unknown trigger kind") {
		t.Error("Commands should contain error message about unknown trigger kind")
	}
}

func TestBuildCloneStep_NilCloneOpts(t *testing.T) {
	twf := tangled.Pipeline_Workflow{
		Clone: nil, // Nil clone options should use defaults
	}
	tr := tangled.Pipeline_TriggerMetadata{
		Kind: string(workflow.TriggerKindPush),
		Push: &tangled.Pipeline_PushTriggerData{
			NewSha: "abc123",
		},
		Repo: &tangled.Pipeline_TriggerRepo{
			Knot:    "example.com",
			Did:     "did:plc:user123",
			Repo:    sp("my-repo"),
			RepoDid: sp("did:plc:boltless"),
		},
	}

	step := BuildCloneStep(twf, tr, false)

	// Should still work with default options
	if step.Kind() != StepKindSystem {
		t.Errorf("Expected StepKindSystem, got %v", step.Kind())
	}

	allCmds := strings.Join(step.Commands(), " ")
	if !strings.Contains(allCmds, "--depth=1") {
		t.Error("Commands should default to '--depth=1' when Clone is nil")
	}
	if !strings.Contains(allCmds, "git init") {
		t.Error("Commands should contain 'git init'")
	}
}
