package messagesetting

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gitlink-org/gitlink-cli/internal/client"
	"github.com/gitlink-org/gitlink-cli/shortcuts/common"
)

func runMessageSettingShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
	t.Helper()
	shortcut := findMessageSettingShortcut(t, name)
	ctx := &common.RuntimeContext{
		Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
		Format: "json",
		Args:   args,
	}
	if ctx.Args == nil {
		ctx.Args = map[string]string{}
	}
	return shortcut.Run(ctx)
}

func findMessageSettingShortcut(t *testing.T, name string) *common.Shortcut {
	t.Helper()
	for _, shortcut := range Shortcuts() {
		if shortcut.Name == name {
			return shortcut
		}
	}
	t.Fatalf("shortcut %q not found", name)
	return nil
}

func writeMessageSettingJSON(t *testing.T, w http.ResponseWriter, value interface{}) {
	t.Helper()
	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(value); err != nil {
		t.Fatalf("encode json: %v", err)
	}
}

func decodeMessageSettingJSON(t *testing.T, r *http.Request) map[string]interface{} {
	t.Helper()
	defer r.Body.Close()
	var value map[string]interface{}
	if err := json.NewDecoder(r.Body).Decode(&value); err != nil {
		t.Fatalf("decode request body: %v", err)
	}
	return value
}

func TestMessageSettingsCatalog(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet || r.URL.Path != "/api/template_message_settings.json" {
			t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
		}
		writeMessageSettingJSON(t, w, catalogFixture())
	}))
	defer server.Close()

	if err := runMessageSettingShortcut(t, server, "catalog", nil); err != nil {
		t.Fatalf("catalog failed: %v", err)
	}
}

func TestMessageSettingsViewUsesCurrentUserWhenLoginMissing(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodGet && r.URL.Path == "/users/me.json":
			writeMessageSettingJSON(t, w, map[string]interface{}{"login": "alice"})
		case r.Method == http.MethodGet && r.URL.Path == "/api/template_message_settings.json":
			writeMessageSettingJSON(t, w, catalogFixture())
		case r.Method == http.MethodGet && r.URL.Path == "/api/users/alice/template_message_settings.json":
			writeMessageSettingJSON(t, w, userSettingFixture())
		default:
			t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
		}
	}))
	defer server.Close()

	if err := runMessageSettingShortcut(t, server, "view", nil); err != nil {
		t.Fatalf("view failed: %v", err)
	}
}

func TestMessageSettingsUpdateDryRunDoesNotPost(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodGet && r.URL.Path == "/users/me.json":
			writeMessageSettingJSON(t, w, map[string]interface{}{"login": "alice"})
		case r.Method == http.MethodGet && r.URL.Path == "/api/template_message_settings.json":
			writeMessageSettingJSON(t, w, catalogFixture())
		case r.Method == http.MethodGet && r.URL.Path == "/api/users/alice/template_message_settings.json":
			writeMessageSettingJSON(t, w, userSettingFixture())
		default:
			t.Fatalf("dry-run should not write, got %s %s", r.Method, r.URL.Path)
		}
	}))
	defer server.Close()

	err := runMessageSettingShortcut(t, server, "update", map[string]string{
		"channel": "notification",
		"state":   "off",
		"keys":    "Normal::Permission",
		"dry-run": "true",
	})
	if err != nil {
		t.Fatalf("update dry-run failed: %v", err)
	}
}

func TestMessageSettingsUpdatePostsMergedPayload(t *testing.T) {
	var payload map[string]interface{}
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodGet && r.URL.Path == "/users/me.json":
			writeMessageSettingJSON(t, w, map[string]interface{}{"login": "alice"})
		case r.Method == http.MethodGet && r.URL.Path == "/api/template_message_settings.json":
			writeMessageSettingJSON(t, w, catalogFixture())
		case r.Method == http.MethodGet && r.URL.Path == "/api/users/alice/template_message_settings.json":
			writeMessageSettingJSON(t, w, userSettingFixture())
		case r.Method == http.MethodPost && r.URL.Path == "/api/users/alice/template_message_settings/update_setting.json":
			payload = decodeMessageSettingJSON(t, r)
			writeMessageSettingJSON(t, w, map[string]interface{}{"status": 0, "message": "updated"})
		default:
			t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
		}
	}))
	defer server.Close()

	err := runMessageSettingShortcut(t, server, "update", map[string]string{
		"channel": "email",
		"state":   "on",
		"group":   "ManageProject",
	})
	if err != nil {
		t.Fatalf("update failed: %v", err)
	}

	setting, ok := payload["setting"].(map[string]interface{})
	if !ok {
		t.Fatalf("missing setting payload: %#v", payload)
	}
	notificationBody := setting["notification_body"].(map[string]interface{})
	emailBody := setting["email_body"].(map[string]interface{})

	if notificationBody["ManageProject::Issue"] != true {
		t.Fatalf("notification should stay true for ManageProject::Issue, got %#v", notificationBody["ManageProject::Issue"])
	}
	if emailBody["ManageProject::Issue"] != true {
		t.Fatalf("email should be enabled for ManageProject::Issue, got %#v", emailBody["ManageProject::Issue"])
	}
	if emailBody["Normal::Permission"] != false {
		t.Fatalf("email should preserve unrelated keys, got %#v", emailBody["Normal::Permission"])
	}
	if notificationBody["ManageProject::Praised"] != true {
		t.Fatalf("notification defaults should be preserved for missing keys, got %#v", notificationBody["ManageProject::Praised"])
	}
	if emailBody["ManageProject::Praised"] != true {
		t.Fatalf("selected group keys should be updated even when missing from current settings, got %#v", emailBody["ManageProject::Praised"])
	}
}

func TestMessageSettingsPresetPostsSelectedKeys(t *testing.T) {
	var payload map[string]interface{}
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodGet && r.URL.Path == "/users/me.json":
			writeMessageSettingJSON(t, w, map[string]interface{}{"login": "alice"})
		case r.Method == http.MethodGet && r.URL.Path == "/api/template_message_settings.json":
			writeMessageSettingJSON(t, w, catalogFixture())
		case r.Method == http.MethodGet && r.URL.Path == "/api/users/alice/template_message_settings.json":
			writeMessageSettingJSON(t, w, userSettingFixture())
		case r.Method == http.MethodPost && r.URL.Path == "/api/users/alice/template_message_settings/update_setting.json":
			payload = decodeMessageSettingJSON(t, r)
			writeMessageSettingJSON(t, w, map[string]interface{}{"status": 0, "message": "updated"})
		default:
			t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
		}
	}))
	defer server.Close()

	err := runMessageSettingShortcut(t, server, "preset", map[string]string{
		"name": "all-off",
		"keys": "Permission,ManageProject::Issue",
		"all":  "false",
	})
	if err != nil {
		t.Fatalf("preset failed: %v", err)
	}

	setting := payload["setting"].(map[string]interface{})
	notificationBody := setting["notification_body"].(map[string]interface{})
	emailBody := setting["email_body"].(map[string]interface{})

	if notificationBody["Normal::Permission"] != false || emailBody["Normal::Permission"] != false {
		t.Fatalf("Normal::Permission should be turned off by preset")
	}
	if notificationBody["ManageProject::Issue"] != false || emailBody["ManageProject::Issue"] != false {
		t.Fatalf("ManageProject::Issue should be turned off by preset")
	}
	if notificationBody["ManageProject::PullRequest"] != true {
		t.Fatalf("unselected keys should remain unchanged, got %#v", notificationBody["ManageProject::PullRequest"])
	}
}

func TestSelectKeysRequiresSelector(t *testing.T) {
	registry := buildRegistry(mustCatalogResponse(t, catalogFixture()), mustUserSettingResponse(t, userSettingFixture()))
	if _, _, err := selectKeys(selectorArgs{}, registry); err == nil {
		t.Fatal("expected selector validation error")
	}
}

func TestParseGroupFilterRejectsUnknownGroup(t *testing.T) {
	registry := buildRegistry(mustCatalogResponse(t, catalogFixture()), nil)
	if _, err := parseGroupFilter("UnknownGroup", registry); err == nil {
		t.Fatal("expected unknown group error")
	}
}

func TestResolveKeyFilterRejectsAmbiguousShortKey(t *testing.T) {
	catalog := &catalogResponse{
		SettingTypes: []catalogGroup{
			{
				Type:     "TemplateMessageSetting::Normal",
				TypeName: "Normal",
				Settings: []catalogSettingRow{
					{Name: "Issue", Key: "Issue"},
				},
			},
			{
				Type:     "TemplateMessageSetting::ManageProject",
				TypeName: "Manage",
				Settings: []catalogSettingRow{
					{Name: "Issue", Key: "Issue"},
				},
			},
		},
	}
	registry := buildRegistry(catalog, nil)
	if _, err := resolveKeyFilter("Issue", registry); err == nil {
		t.Fatal("expected ambiguous key error")
	}
}

func catalogFixture() map[string]interface{} {
	return map[string]interface{}{
		"setting_types": []map[string]interface{}{
			{
				"type":      "TemplateMessageSetting::Normal",
				"type_name": "My status",
				"settings": []map[string]interface{}{
					{
						"name":                  "Permission changed",
						"key":                   "Permission",
						"notification_disabled": false,
						"email_disabled":        false,
					},
				},
			},
			{
				"type":      "TemplateMessageSetting::ManageProject",
				"type_name": "Managed repositories",
				"settings": []map[string]interface{}{
					{
						"name":                  "New issue",
						"key":                   "Issue",
						"notification_disabled": false,
						"email_disabled":        false,
					},
					{
						"name":                  "New pull request",
						"key":                   "PullRequest",
						"notification_disabled": false,
						"email_disabled":        false,
					},
					{
						"name":                  "Praised",
						"key":                   "Praised",
						"notification_disabled": false,
						"email_disabled":        true,
					},
				},
			},
		},
	}
}

func userSettingFixture() map[string]interface{} {
	return map[string]interface{}{
		"user": map[string]interface{}{
			"login": "alice",
			"name":  "Alice",
		},
		"notification_body": map[string]interface{}{
			"Normal::Permission":         true,
			"ManageProject::Issue":       true,
			"ManageProject::PullRequest": true,
		},
		"email_body": map[string]interface{}{
			"Normal::Permission":         false,
			"ManageProject::Issue":       false,
			"ManageProject::PullRequest": false,
		},
	}
}

func mustCatalogResponse(t *testing.T, value map[string]interface{}) *catalogResponse {
	t.Helper()
	raw, err := json.Marshal(value)
	if err != nil {
		t.Fatalf("marshal catalog fixture: %v", err)
	}
	var result catalogResponse
	if err := json.Unmarshal(raw, &result); err != nil {
		t.Fatalf("unmarshal catalog fixture: %v", err)
	}
	return &result
}

func mustUserSettingResponse(t *testing.T, value map[string]interface{}) *userSettingResponse {
	t.Helper()
	raw, err := json.Marshal(value)
	if err != nil {
		t.Fatalf("marshal user setting fixture: %v", err)
	}
	var result userSettingResponse
	if err := json.Unmarshal(raw, &result); err != nil {
		t.Fatalf("unmarshal user setting fixture: %v", err)
	}
	return &result
}
