package dispatch

import (
	"encoding/json"
	"strings"
	"testing"
)

func TestWeeklyTimeWindowUsesWeekdayStrings(t *testing.T) {
	t.Parallel()

	data, err := json.Marshal(WeeklyTimeWindow{
		Timezone: "Asia/Shanghai",
		Weekdays: []Weekday{WeekdayMonday, WeekdayFriday},
		Start:    "09:00",
		End:      "18:00",
	})
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"timezone":"Asia/Shanghai","weekdays":["MON","FRI"],"start":"09:00","end":"18:00"}` {
		t.Fatalf("unexpected JSON: %s", data)
	}
}

func TestDraftPolicyVersionIsNull(t *testing.T) {
	t.Parallel()

	data, err := json.Marshal(Policy{Status: PolicyStatusDraft})
	if err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(string(data), `"version":null`) {
		t.Fatalf("draft version must be null: %s", data)
	}
}

func TestAssignActionUsesAssignPayload(t *testing.T) {
	t.Parallel()

	user := "owner@example.com"
	strategy := AssignmentStrategyRoundRobin
	data, err := json.Marshal(Action{
		Type: ActionTypeAssign,
		Assign: &AssignAction{
			Receiver: &Receiver{Type: ReceiverTypeUser, User: &user},
			Strategy: &strategy,
		},
	})
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"type":"assign","assign":{"receiver":{"type":"user","user":"owner@example.com"},"strategy":"round_robin"}}` {
		t.Fatalf("unexpected JSON: %s", data)
	}
}

func TestAssignActionUsesBotReceiver(t *testing.T) {
	t.Parallel()

	bot := "ai-support-bot@pingcap.com"
	data, err := json.Marshal(Action{
		Type: ActionTypeAssign,
		Assign: &AssignAction{
			Receiver: &Receiver{Type: ReceiverTypeBot, Bot: &bot},
		},
	})
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"type":"assign","assign":{"receiver":{"type":"bot","bot":"ai-support-bot@pingcap.com"}}}` {
		t.Fatalf("unexpected JSON: %s", data)
	}
}

func TestAssignL2ActionUsesAssignL2Payload(t *testing.T) {
	t.Parallel()

	user := "owner@example.com"
	strategy := AssignmentStrategyRoundRobin
	data, err := json.Marshal(Action{
		Type: ActionTypeAssignL2,
		AssignL2: &AssignAction{
			Receiver: &Receiver{Type: ReceiverTypeUser, User: &user},
			Strategy: &strategy,
		},
	})
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"type":"assign_l2","assign_l2":{"receiver":{"type":"user","user":"owner@example.com"},"strategy":"round_robin"}}` {
		t.Fatalf("unexpected JSON: %s", data)
	}
}

func TestAssignActionUsesOncallLookup(t *testing.T) {
	t.Parallel()

	strategy := AssignmentStrategyRoundRobin
	data, err := json.Marshal(Action{
		Type: ActionTypeAssignL2,
		AssignL2: &AssignAction{
			Receiver:     &Receiver{Type: ReceiverTypeOncallTeam, Name: "support_tools"},
			Strategy:     &strategy,
			OncallLookup: &OncallLookup{Range: "72h", Step: "1h"},
		},
	})
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"type":"assign_l2","assign_l2":{"receiver":{"type":"oncall_team","name":"support_tools"},"strategy":"round_robin","oncall_lookup":{"range":"72h","step":"1h"}}}` {
		t.Fatalf("unexpected JSON: %s", data)
	}
}

func TestRetriggerActionUsesRetriggerPayload(t *testing.T) {
	t.Parallel()

	data, err := json.Marshal(Action{
		Type:      ActionTypeRetrigger,
		Retrigger: &RetriggerAction{Delay: "1h30m"},
	})
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"type":"retrigger","retrigger":{"delay":"1h30m"}}` {
		t.Fatalf("unexpected JSON: %s", data)
	}
}

func TestEscalateActionUsesEscalatePayload(t *testing.T) {
	t.Parallel()

	data, err := json.Marshal(Action{
		Type:     ActionTypeEscalate,
		Escalate: &EscalateAction{EscalateTo: EscalateToL3},
	})
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"type":"escalate","escalate":{"escalate_to":"L3"}}` {
		t.Fatalf("unexpected JSON: %s", data)
	}
}

func TestTransitionIssueActionUsesTransitionIssuePayload(t *testing.T) {
	t.Parallel()

	data, err := json.Marshal(Action{
		Type:            ActionTypeTransitionIssue,
		TransitionIssue: &TransitionIssueAction{TargetStatus: "Waiting for Customer"},
	})
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"type":"transition_issue","transition_issue":{"target_status":"Waiting for Customer"}}` {
		t.Fatalf("unexpected JSON: %s", data)
	}
}

func TestNoopActionUsesNoopPayload(t *testing.T) {
	t.Parallel()

	data, err := json.Marshal(Action{Type: ActionTypeNoop, Noop: &NoopAction{Result: NoopResultFailure}})
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"type":"noop","noop":{"result":"failure"}}` {
		t.Fatalf("unexpected JSON: %s", data)
	}
}

func TestSendWebRequestActionUsesSendWebRequestPayload(t *testing.T) {
	t.Parallel()

	data, err := json.Marshal(Action{
		Type: ActionTypeSendWebRequest,
		SendWebRequest: &SendWebRequestAction{
			URL:    "https://example.com/issues/{{ .issue.project_key }}",
			Method: WebRequestMethodPost,
			Body:   `{"summary":"{{ jsonEncode .issue.summary }}"}`,
			Headers: map[string]string{
				"Content-Type":  "application/json",
				"Authorization": "Bearer token",
			},
		},
	})
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"type":"send_web_request","send_web_request":{"url":"https://example.com/issues/{{ .issue.project_key }}","method":"POST","body":"{\"summary\":\"{{ jsonEncode .issue.summary }}\"}","headers":{"Authorization":"Bearer token","Content-Type":"application/json"}}}` {
		t.Fatalf("unexpected JSON: %s", data)
	}
}

func TestPolicyTriggerJSON(t *testing.T) {
	t.Parallel()

	data, err := json.Marshal(Policy{Trigger: PolicyTrigger{PolicyID: 101}})
	if err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(string(data), `"trigger":{"policy_id":101,"enable":false}`) {
		t.Fatalf("disabled trigger must be present: %s", data)
	}

	data, err = json.Marshal(PolicyTrigger{PolicyID: 101})
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"policy_id":101,"enable":false}` {
		t.Fatalf("wildcard trigger must omit optional predicates: %s", data)
	}
}

func TestTriggerProjectKeyInMissingNullAndEmptyDecodeAsWildcard(t *testing.T) {
	t.Parallel()

	for _, body := range []string{`{"policy_id":101}`, `{"policy_id":101,"project_key_in":null}`, `{"policy_id":101,"project_key_in":[]}`} {
		var trigger PolicyTrigger
		if err := json.Unmarshal([]byte(body), &trigger); err != nil {
			t.Fatal(err)
		}
		if len(trigger.ProjectKeyIn) != 0 {
			t.Fatalf("project_key_in must decode as wildcard: %s", body)
		}
	}
}

func TestTriggerIssueStatusInMissingNullAndEmptyDecodeAsWildcard(t *testing.T) {
	t.Parallel()

	for _, body := range []string{`{"policy_id":101}`, `{"policy_id":101,"issue_status_in":null}`, `{"policy_id":101,"issue_status_in":[]}`} {
		var trigger PolicyTrigger
		if err := json.Unmarshal([]byte(body), &trigger); err != nil {
			t.Fatal(err)
		}
		if len(trigger.IssueStatusIn) != 0 {
			t.Fatalf("issue_status_in must decode as wildcard: %s", body)
		}
	}
}

func TestTriggerIssueLabelAllMissingNullAndEmptyDecodeAsWildcard(t *testing.T) {
	t.Parallel()

	for _, body := range []string{`{"policy_id":101}`, `{"policy_id":101,"issue_label_all":null}`, `{"policy_id":101,"issue_label_all":[]}`} {
		var trigger PolicyTrigger
		if err := json.Unmarshal([]byte(body), &trigger); err != nil {
			t.Fatal(err)
		}
		if len(trigger.IssueLabelAll) != 0 {
			t.Fatalf("issue_label_all must decode as wildcard: %s", body)
		}
	}
}
