// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package template

import (
	"net/url"
	"reflect"
	"testing"

	"code.gitea.io/gitea/modules/json"
	api "code.gitea.io/gitea/modules/structs"
)

func TestValidate(t *testing.T) {
	tests := []struct {
		name    string
		content string
		wantErr string
	}{
		{
			name:    "miss name",
			content: ``,
			wantErr: "'name' is required",
		},
		{
			name: "miss about",
			content: `
name: "test"
`,
			wantErr: "'about' is required",
		},
		{
			name: "miss body",
			content: `
name: "test"
about: "this is about"
`,
			wantErr: "'body' is required",
		},
		{
			name: "markdown miss value",
			content: `
name: "test"
about: "this is about"
body:
  - type: "markdown"
`,
			wantErr: "body[0](markdown): 'value' is required",
		},
		{
			name: "markdown invalid value",
			content: `
name: "test"
about: "this is about"
body:
  - type: "markdown"
    attributes:
      value: true
`,
			wantErr: "body[0](markdown): 'value' should be a string",
		},
		{
			name: "markdown empty value",
			content: `
name: "test"
about: "this is about"
body:
  - type: "markdown"
    attributes:
      value: ""
`,
			wantErr: "body[0](markdown): 'value' is required",
		},
		{
			name: "textarea invalid id",
			content: `
name: "test"
about: "this is about"
body:
  - type: "textarea"
    id: "?"
`,
			wantErr: "body[0](textarea): 'id' should contain only alphanumeric, '-' and '_'",
		},
		{
			name: "textarea miss label",
			content: `
name: "test"
about: "this is about"
body:
  - type: "textarea"
    id: "1"
`,
			wantErr: "body[0](textarea): 'label' is required",
		},
		{
			name: "textarea conflict id",
			content: `
name: "test"
about: "this is about"
body:
  - type: "textarea"
    id: "1"
    attributes:
      label: "a"
  - type: "textarea"
    id: "1"
    attributes:
      label: "b"
`,
			wantErr: "body[1](textarea): 'id' should be unique",
		},
		{
			name: "textarea invalid description",
			content: `
name: "test"
about: "this is about"
body:
  - type: "textarea"
    id: "1"
    attributes:
      label: "a"
      description: true
`,
			wantErr: "body[0](textarea): 'description' should be a string",
		},
		{
			name: "textarea invalid required",
			content: `
name: "test"
about: "this is about"
body:
  - type: "textarea"
    id: "1"
    attributes:
      label: "a"
    validations:
      required: "on"
`,
			wantErr: "body[0](textarea): 'required' should be a bool",
		},
		{
			name: "input invalid description",
			content: `
name: "test"
about: "this is about"
body:
  - type: "input"
    id: "1"
    attributes:
      label: "a"
      description: true
`,
			wantErr: "body[0](input): 'description' should be a string",
		},
		{
			name: "input invalid is_number",
			content: `
name: "test"
about: "this is about"
body:
  - type: "input"
    id: "1"
    attributes:
      label: "a"
    validations:
      is_number: "yes"
`,
			wantErr: "body[0](input): 'is_number' should be a bool",
		},
		{
			name: "input invalid regex",
			content: `
name: "test"
about: "this is about"
body:
  - type: "input"
    id: "1"
    attributes:
      label: "a"
    validations:
      regex: true
`,
			wantErr: "body[0](input): 'regex' should be a string",
		},
		{
			name: "dropdown invalid description",
			content: `
name: "test"
about: "this is about"
body:
  - type: "dropdown"
    id: "1"
    attributes:
      label: "a"
      description: true
`,
			wantErr: "body[0](dropdown): 'description' should be a string",
		},
		{
			name: "dropdown invalid multiple",
			content: `
name: "test"
about: "this is about"
body:
  - type: "dropdown"
    id: "1"
    attributes:
      label: "a"
      multiple: "on"
`,
			wantErr: "body[0](dropdown): 'multiple' should be a bool",
		},
		{
			name: "checkboxes invalid description",
			content: `
name: "test"
about: "this is about"
body:
  - type: "checkboxes"
    id: "1"
    attributes:
      label: "a"
      description: true
`,
			wantErr: "body[0](checkboxes): 'description' should be a string",
		},
		{
			name: "invalid type",
			content: `
name: "test"
about: "this is about"
body:
  - type: "video"
    id: "1"
    attributes:
      label: "a"
`,
			wantErr: "body[0](video): unknown type",
		},
		{
			name: "dropdown miss options",
			content: `
name: "test"
about: "this is about"
body:
  - type: "dropdown"
    id: "1"
    attributes:
      label: "a"
`,
			wantErr: "body[0](dropdown): 'options' is required and should be a array",
		},
		{
			name: "dropdown invalid options",
			content: `
name: "test"
about: "this is about"
body:
  - type: "dropdown"
    id: "1"
    attributes:
      label: "a"
      options:
        - "a"
        - true
`,
			wantErr: "body[0](dropdown), option[1]: should be a string",
		},
		{
			name: "checkboxes invalid options",
			content: `
name: "test"
about: "this is about"
body:
  - type: "checkboxes"
    id: "1"
    attributes:
      label: "a"
      options:
        - "a"
        - true
`,
			wantErr: "body[0](checkboxes), option[0]: should be a dictionary",
		},
		{
			name: "checkboxes option miss label",
			content: `
name: "test"
about: "this is about"
body:
  - type: "checkboxes"
    id: "1"
    attributes:
      label: "a"
      options:
        - required: true
`,
			wantErr: "body[0](checkboxes), option[0]: 'label' is required and should be a string",
		},
		{
			name: "checkboxes option invalid required",
			content: `
name: "test"
about: "this is about"
body:
  - type: "checkboxes"
    id: "1"
    attributes:
      label: "a"
      options:
        - label: "a"
          required: "on"
`,
			wantErr: "body[0](checkboxes), option[0]: 'required' should be a bool",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tmpl, err := unmarshal("test.yaml", []byte(tt.content))
			if err != nil {
				t.Fatal(err)
			}
			if err := Validate(tmpl); (err == nil) != (tt.wantErr == "") || err != nil && err.Error() != tt.wantErr {
				t.Errorf("Validate() error = %v, wantErr %q", err, tt.wantErr)
			}
		})
	}

	t.Run("valid", func(t *testing.T) {
		content := `
name: Name
title: Title
about: About
labels: ["label1", "label2"]
ref: Ref
body:
  - type: markdown
    id: id1
    attributes:
      value: Value of the markdown
  - type: textarea
    id: id2
    attributes:
      label: Label of textarea
      description: Description of textarea
      placeholder: Placeholder of textarea
      value: Value of textarea
      render: bash
    validations:
      required: true
  - type: input
    id: id3
    attributes:
      label: Label of input
      description: Description of input
      placeholder: Placeholder of input
      value: Value of input
    validations:
      required: true
      is_number: true
      regex: "[a-zA-Z0-9]+"
  - type: dropdown
    id: id4
    attributes:
      label: Label of dropdown
      description: Description of dropdown
      multiple: true
      options:
        - Option 1 of dropdown
        - Option 2 of dropdown
        - Option 3 of dropdown
    validations:
      required: true
  - type: checkboxes
    id: id5
    attributes:
      label: Label of checkboxes
      description: Description of checkboxes
      options:
        - label: Option 1 of checkboxes
          required: true
        - label: Option 2 of checkboxes
          required: false
        - label: Option 3 of checkboxes
          required: true
`
		want := &api.IssueTemplate{
			Name:   "Name",
			Title:  "Title",
			About:  "About",
			Labels: []string{"label1", "label2"},
			Ref:    "Ref",
			Fields: []*api.IssueFormField{
				{
					Type: "markdown",
					ID:   "id1",
					Attributes: map[string]interface{}{
						"value": "Value of the markdown",
					},
				},
				{
					Type: "textarea",
					ID:   "id2",
					Attributes: map[string]interface{}{
						"label":       "Label of textarea",
						"description": "Description of textarea",
						"placeholder": "Placeholder of textarea",
						"value":       "Value of textarea",
						"render":      "bash",
					},
					Validations: map[string]interface{}{
						"required": true,
					},
				},
				{
					Type: "input",
					ID:   "id3",
					Attributes: map[string]interface{}{
						"label":       "Label of input",
						"description": "Description of input",
						"placeholder": "Placeholder of input",
						"value":       "Value of input",
					},
					Validations: map[string]interface{}{
						"required":  true,
						"is_number": true,
						"regex":     "[a-zA-Z0-9]+",
					},
				},
				{
					Type: "dropdown",
					ID:   "id4",
					Attributes: map[string]interface{}{
						"label":       "Label of dropdown",
						"description": "Description of dropdown",
						"multiple":    true,
						"options": []interface{}{
							"Option 1 of dropdown",
							"Option 2 of dropdown",
							"Option 3 of dropdown",
						},
					},
					Validations: map[string]interface{}{
						"required": true,
					},
				},
				{
					Type: "checkboxes",
					ID:   "id5",
					Attributes: map[string]interface{}{
						"label":       "Label of checkboxes",
						"description": "Description of checkboxes",
						"options": []interface{}{
							map[interface{}]interface{}{"label": "Option 1 of checkboxes", "required": true},
							map[interface{}]interface{}{"label": "Option 2 of checkboxes", "required": false},
							map[interface{}]interface{}{"label": "Option 3 of checkboxes", "required": true},
						},
					},
				},
			},
			FileName: "test.yaml",
		}
		got, err := unmarshal("test.yaml", []byte(content))
		if err != nil {
			t.Fatal(err)
		}
		if err := Validate(got); err != nil {
			t.Errorf("Validate() error = %v", err)
		}
		if !reflect.DeepEqual(want, got) {
			jsonWant, _ := json.Marshal(want)
			jsonGot, _ := json.Marshal(got)
			t.Errorf("want:\n%s\ngot:\n%s", jsonWant, jsonGot)
		}
	})
}

func TestRenderToMarkdown(t *testing.T) {
	type args struct {
		template string
		values   url.Values
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			name: "normal",
			args: args{
				template: `
name: Name
title: Title
about: About
labels: ["label1", "label2"]
ref: Ref
body:
  - type: markdown
    id: id1
    attributes:
      value: Value of the markdown
  - type: textarea
    id: id2
    attributes:
      label: Label of textarea
      description: Description of textarea
      placeholder: Placeholder of textarea
      value: Value of textarea
      render: bash
    validations:
      required: true
  - type: input
    id: id3
    attributes:
      label: Label of input
      description: Description of input
      placeholder: Placeholder of input
      value: Value of input
    validations:
      required: true
      is_number: true
      regex: "[a-zA-Z0-9]+"
  - type: dropdown
    id: id4
    attributes:
      label: Label of dropdown
      description: Description of dropdown
      multiple: true
      options:
        - Option 1 of dropdown
        - Option 2 of dropdown
        - Option 3 of dropdown
    validations:
      required: true
  - type: checkboxes
    id: id5
    attributes:
      label: Label of checkboxes
      description: Description of checkboxes
      options:
        - label: Option 1 of checkboxes
          required: true
        - label: Option 2 of checkboxes
          required: false
        - label: Option 3 of checkboxes
          required: true
`,
				values: map[string][]string{
					"form-field-id2":   {"Value of id2"},
					"form-field-id3":   {"Value of id3"},
					"form-field-id4":   {"0,1"},
					"form-field-id5-0": {"on"},
					"form-field-id5-2": {"on"},
				},
			},
			want: `### Label of textarea

` + "```bash\nValue of id2\n```" + `

### Label of input

Value of id3

### Label of dropdown

Option 1 of dropdown, Option 2 of dropdown

### Label of checkboxes

- [x] Option 1 of checkboxes
- [ ] Option 2 of checkboxes
- [x] Option 3 of checkboxes

`,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			template, err := Unmarshal("test.yaml", []byte(tt.args.template))
			if err != nil {
				t.Fatal(err)
			}
			if got := RenderToMarkdown(template, tt.args.values); got != tt.want {
				t.Errorf("RenderToMarkdown() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_minQuotes(t *testing.T) {
	type args struct {
		value string
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			name: "without quote",
			args: args{
				value: "Hello\nWorld",
			},
			want: "```",
		},
		{
			name: "with 1 quote",
			args: args{
				value: "Hello\nWorld\n`text`\n",
			},
			want: "```",
		},
		{
			name: "with 3 quotes",
			args: args{
				value: "Hello\nWorld\n`text`\n```go\ntext\n```\n",
			},
			want: "````",
		},
		{
			name: "with more quotes",
			args: args{
				value: "Hello\nWorld\n`text`\n```go\ntext\n```\n``````````bash\ntext\n``````````\n",
			},
			want: "```````````",
		},
		{
			name: "not leading quotes",
			args: args{
				value: "Hello\nWorld`text````go\ntext`````````````bash\ntext``````````\n",
			},
			want: "```",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := minQuotes(tt.args.value); got != tt.want {
				t.Errorf("minQuotes() = %v, want %v", got, tt.want)
			}
		})
	}
}