// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: BUSL-1.1

package hcl2template

import (
	"testing"
)

func TestGetCoreBuildProvisionerFromBlock_AppliesOverrideForBuild(t *testing.T) {
	parser := getBasicParser()
	cfg := &PackerConfig{
		parser:                  parser,
		CorePackerVersionString: lockedVersion,
	}

	blocks, diags := ParseProvisionerBlocks(`
provisioner "shell" {
  override = {
    "amazon-ebs.ubuntu" = {
      bool = false
    }
  }
}
`)
	if diags.HasErrors() {
		t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
	}

	if len(blocks) != 1 {
		t.Fatalf("expected 1 block, got %d", len(blocks))
	}

	coreProv, diags := cfg.GetCoreBuildProvisionerFromBlock(blocks[0], "amazon-ebs.ubuntu")
	if diags.HasErrors() {
		t.Fatalf("GetCoreBuildProvisionerFromBlock() unexpected error: %v", diags)
	}

	hclProv, ok := coreProv.Provisioner.(*HCL2Provisioner)
	if !ok {
		t.Fatalf("expected *HCL2Provisioner, got %T", coreProv.Provisioner)
	}

	if hclProv.override == nil {
		t.Fatal("expected override to be applied, got nil")
	}

	if got, ok := hclProv.override["bool"]; !ok || got != false {
		t.Fatalf("expected override bool=false, got %#v", hclProv.override["bool"])
	}
}

func TestGetCoreBuildProvisionerFromBlock_OverrideNotAppliedForOtherBuild(t *testing.T) {
	parser := getBasicParser()
	cfg := &PackerConfig{
		parser:                  parser,
		CorePackerVersionString: lockedVersion,
	}

	blocks, diags := ParseProvisionerBlocks(`
provisioner "shell" {
  override = {
    "amazon-ebs.ubuntu" = {
      bool = false
    }
  }
}
`)
	if diags.HasErrors() {
		t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
	}

	if len(blocks) != 1 {
		t.Fatalf("expected 1 block, got %d", len(blocks))
	}

	coreProv, diags := cfg.GetCoreBuildProvisionerFromBlock(blocks[0], "virtualbox-iso.base")
	if diags.HasErrors() {
		t.Fatalf("GetCoreBuildProvisionerFromBlock() unexpected error: %v", diags)
	}

	hclProv, ok := coreProv.Provisioner.(*HCL2Provisioner)
	if !ok {
		t.Fatalf("expected *HCL2Provisioner, got %T", coreProv.Provisioner)
	}

	if hclProv.override != nil {
		t.Fatalf("expected no override to be applied, got %#v", hclProv.override)
	}
}

func TestGetCoreBuildProvisionerFromBlock_IncludesSensitiveVariables(t *testing.T) {
	parser := getBasicParser()
	cfg := &PackerConfig{
		parser:                  parser,
		CorePackerVersionString: lockedVersion,
		InputVariables: Variables{
			"visible": &Variable{Name: "visible"},
			"secret":  &Variable{Name: "secret", Sensitive: true},
		},
	}

	blocks, diags := ParseProvisionerBlocks(`
provisioner "shell" {
	override = {
	  "amazon-ebs.ubuntu" = {
	    bool = false
	  }
	}
}
`)
	if diags.HasErrors() {
		t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
	}

	coreProv, diags := cfg.GetCoreBuildProvisionerFromBlock(blocks[0], "amazon-ebs.ubuntu")
	if diags.HasErrors() {
		t.Fatalf("GetCoreBuildProvisionerFromBlock() unexpected error: %v", diags)
	}

	hclProv, ok := coreProv.Provisioner.(*HCL2Provisioner)
	if !ok {
		t.Fatalf("expected *HCL2Provisioner, got %T", coreProv.Provisioner)
	}

	sensitiveVars, ok := hclProv.builderVariables["packer_sensitive_variables"].([]string)
	if !ok {
		t.Fatalf("expected []string packer_sensitive_variables, got %T", hclProv.builderVariables["packer_sensitive_variables"])
	}

	if len(sensitiveVars) != 1 || sensitiveVars[0] != "secret" {
		t.Fatalf("expected sensitive vars [secret], got %#v", sensitiveVars)
	}
}

func TestParseProvisionerBlocks(t *testing.T) {
	tests := []struct {
		name         string
		blockContent string
		wantCount    int
		wantTypes    []string
		wantErr      bool
	}{
		{
			name: "single shell provisioner",
			blockContent: `
provisioner "shell" {
  inline = ["echo 'Hello from enforced provisioner'"]
}
`,
			wantCount: 1,
			wantTypes: []string{"shell"},
			wantErr:   false,
		},
		{
			name: "multiple provisioners",
			blockContent: `
provisioner "shell" {
  inline = ["echo 'First enforced provisioner'"]
}

provisioner "shell" {
  name   = "security-scan"
  inline = ["echo 'Security scan running...'"]
}
`,
			wantCount: 2,
			wantTypes: []string{"shell", "shell"},
			wantErr:   false,
		},
		{
			name: "provisioner with pause_before",
			blockContent: `
provisioner "shell" {
  pause_before = "10s"
  inline       = ["echo 'Waiting before execution'"]
}
`,
			wantCount: 1,
			wantTypes: []string{"shell"},
			wantErr:   false,
		},
		{
			name: "provisioner with max_retries",
			blockContent: `
provisioner "shell" {
  max_retries = 3
  inline      = ["echo 'Retry test'"]
}
`,
			wantCount: 1,
			wantTypes: []string{"shell"},
			wantErr:   false,
		},
		{
			name: "provisioner with only filter",
			blockContent: `
provisioner "shell" {
  only   = ["amazon-ebs.ubuntu"]
  inline = ["echo 'Only for amazon-ebs.ubuntu'"]
}
`,
			wantCount: 1,
			wantTypes: []string{"shell"},
			wantErr:   false,
		},
		{
			name: "provisioner with except filter",
			blockContent: `
provisioner "shell" {
  except = ["null.test"]
  inline = ["echo 'Except for null.test'"]
}
`,
			wantCount: 1,
			wantTypes: []string{"shell"},
			wantErr:   false,
		},
		{
			name: "provisioner with both only and except",
			blockContent: `
provisioner "shell" {
  only   = ["amazon-ebs.ubuntu"]
  except = ["null.test"]
  inline = ["echo 'invalid filter combination'"]
}
`,
			wantCount: 0,
			wantTypes: nil,
			wantErr:   true,
		},
		{
			name:         "empty block content",
			blockContent: "",
			wantCount:    0,
			wantTypes:    nil,
			wantErr:      false,
		},
		{
			name:         "whitespace only block content",
			blockContent: "\n\n\t  \n",
			wantCount:    0,
			wantTypes:    nil,
			wantErr:      false,
		},
		{
			name:         "invalid HCL syntax",
			blockContent: "this is not valid { hcl }}}",
			wantCount:    0,
			wantTypes:    nil,
			wantErr:      true,
		},
		{
			name: "json single shell provisioner",
			blockContent: `{
  "provisioner": [
    {
      "shell": {
        "inline": ["echo 'Hello from enforced provisioner JSON'"]
      }
    }
  ]
}`,
			wantCount: 1,
			wantTypes: []string{"shell"},
			wantErr:   false,
		},
		{
			name: "json provisioner with escaped newline in string value",
			blockContent: `{
  "provisioner": [
    {
      "shell": {
        "inline": ["echo first line\necho second line"]
      }
    }
  ]
}`,
			wantCount: 1,
			wantTypes: []string{"shell"},
			wantErr:   false,
		},
		{
			name: "json multiple provisioners",
			blockContent: `{
  "provisioner": [
    {
      "shell": {
        "inline": ["echo 'first'"]
      }
    },
    {
      "shell": {
        "name": "security-scan",
        "inline": ["echo 'second'"]
      }
    }
  ]
}`,
			wantCount: 2,
			wantTypes: []string{"shell", "shell"},
			wantErr:   false,
		},
		{
			name:         "invalid json syntax",
			blockContent: `{"provisioner": [ { "shell": { "inline": ["test"] } ] }`,
			wantCount:    0,
			wantTypes:    nil,
			wantErr:      true,
		},
		{
			name: "legacy json provisioners format",
			blockContent: `{
  "provisioners": [
    {
      "type": "shell",
      "inline": ["echo legacy json format"]
    }
  ]
}`,
			wantCount: 1,
			wantTypes: []string{"shell"},
			wantErr:   false,
		},
		{
			name: "legacy json provisioners with escaped newline and multiple types",
			blockContent: `{
  "provisioners": [
    {
      "type": "shell",
      "inline": ["echo legacy line 1\necho legacy line 2"]
    },
    {
      "type": "file",
      "source": "source.txt",
      "destination": "destination.txt"
    }
  ]
}`,
			wantCount: 2,
			wantTypes: []string{"shell", "file"},
			wantErr:   false,
		},
		{
			name:         "hcl provisioners with windows newlines",
			blockContent: "provisioner \"shell\" {\r\n  inline = [\"echo first\"]\r\n}\r\n\r\nprovisioner \"file\" {\r\n  source = \"source.txt\"\r\n  destination = \"destination.txt\"\r\n}\r\n",
			wantCount:    2,
			wantTypes:    []string{"shell", "file"},
			wantErr:      false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			blocks, diags := ParseProvisionerBlocks(tt.blockContent)

			if tt.wantErr {
				if !diags.HasErrors() {
					t.Errorf("ParseProvisionerBlocks() expected error but got none")
				}
				return
			}

			if diags.HasErrors() {
				t.Errorf("ParseProvisionerBlocks() unexpected error: %v", diags)
				return
			}

			if len(blocks) != tt.wantCount {
				t.Errorf("ParseProvisionerBlocks() got %d blocks, want %d", len(blocks), tt.wantCount)
				return
			}

			for i, wantType := range tt.wantTypes {
				if blocks[i].PType != wantType {
					t.Errorf("ParseProvisionerBlocks() block[%d].PType = %q, want %q", i, blocks[i].PType, wantType)
				}
			}
		})
	}
}

func TestParseProvisionerBlocksWithPauseBefore(t *testing.T) {
	blockContent := `
provisioner "shell" {
  pause_before = "30s"
  inline       = ["echo 'test'"]
}
`
	blocks, diags := ParseProvisionerBlocks(blockContent)
	if diags.HasErrors() {
		t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
	}

	if len(blocks) != 1 {
		t.Fatalf("Expected 1 block, got %d", len(blocks))
	}

	// pause_before should be parsed as 30 seconds
	if blocks[0].PauseBefore.Seconds() != 30 {
		t.Errorf("Expected PauseBefore=30s, got %v", blocks[0].PauseBefore)
	}
}

func TestParseProvisionerBlocksWithMaxRetries(t *testing.T) {
	blockContent := `
provisioner "shell" {
  max_retries = 5
  inline      = ["echo 'test'"]
}
`
	blocks, diags := ParseProvisionerBlocks(blockContent)
	if diags.HasErrors() {
		t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
	}

	if len(blocks) != 1 {
		t.Fatalf("Expected 1 block, got %d", len(blocks))
	}

	if blocks[0].MaxRetries != 5 {
		t.Errorf("Expected MaxRetries=5, got %d", blocks[0].MaxRetries)
	}
}

func TestParseProvisionerBlocksWithOnlyExcept(t *testing.T) {
	blockContent := `
provisioner "shell" {
  only   = ["amazon-ebs.ubuntu", "azure-arm.windows"]
  inline = ["echo 'test'"]
}
`
	blocks, diags := ParseProvisionerBlocks(blockContent)
	if diags.HasErrors() {
		t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
	}

	if len(blocks) != 1 {
		t.Fatalf("Expected 1 block, got %d", len(blocks))
	}

	// Check only filter
	if len(blocks[0].OnlyExcept.Only) != 2 {
		t.Errorf("Expected 2 only values, got %d", len(blocks[0].OnlyExcept.Only))
	}

	// Skip should return true for sources not in the only list
	if !blocks[0].OnlyExcept.Skip("null.test") {
		t.Error("Skip() should return true for source not in only list")
	}

	// Skip should return false for sources in the only list
	if blocks[0].OnlyExcept.Skip("amazon-ebs.ubuntu") {
		t.Error("Skip() should return false for source in only list")
	}
}

func TestParseProvisionerBlocksJSONWithOptions(t *testing.T) {
	blockContent := `{
  "provisioner": [
    {
      "shell": {
        "pause_before": "15s",
        "max_retries": 2,
        "only": ["docker.ubuntu"],
        "inline": ["echo 'json test'"]
      }
    }
  ]
}`

	blocks, diags := ParseProvisionerBlocks(blockContent)
	if diags.HasErrors() {
		t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
	}

	if len(blocks) != 1 {
		t.Fatalf("Expected 1 block, got %d", len(blocks))
	}

	if blocks[0].PauseBefore.Seconds() != 15 {
		t.Errorf("Expected PauseBefore=15s, got %v", blocks[0].PauseBefore)
	}

	if blocks[0].MaxRetries != 2 {
		t.Errorf("Expected MaxRetries=2, got %d", blocks[0].MaxRetries)
	}

	if blocks[0].OnlyExcept.Skip("docker.ubuntu") {
		t.Error("Skip() should return false for source in only list")
	}

	if !blocks[0].OnlyExcept.Skip("null.test") {
		t.Error("Skip() should return true for source not in only list")
	}
}
