package dashboard

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

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/desktop/internal/i18n"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
)

func TestMergeConfig_NonUIFields(t *testing.T) {
	base := &profile.Config{
		Host:     "127.0.0.1",
		Port:     8080,
		Language: "zh-CN",
	}
	patch := &profile.Config{
		Host:     "0.0.0.0",
		Port:     3000,
		Language: "en",
	}

	merged := mergeConfig(base, patch)
	if merged.Host != "0.0.0.0" {
		t.Errorf("Host = %q, want %q", merged.Host, "0.0.0.0")
	}
	if merged.Port != 3000 {
		t.Errorf("Port = %d, want 3000", merged.Port)
	}
	if merged.Language != "en" {
		t.Errorf("Language = %q, want %q", merged.Language, "en")
	}
}

func TestMergeConfig_DesktopUIFields(t *testing.T) {
	optimize := false
	base := &profile.Config{}
	patch := &profile.Config{
		UI: profile.UI{
			OptimizeBackgroundResources: &optimize,
		},
	}

	merged := mergeConfig(base, patch)
	if merged.UI.OptimizeBackgroundResources == nil || *merged.UI.OptimizeBackgroundResources {
		t.Fatalf("OptimizeBackgroundResources = %#v, want false", merged.UI.OptimizeBackgroundResources)
	}
}

func TestUpdateSettingsPersistsPartialAppearancePatch(t *testing.T) {
	dir := t.TempDir()
	t.Setenv(profile.ConfigDirEnvKey, dir)
	cfg := profile.DefaultConfig()
	cfg.UI.Theme = "system"
	cfg.UI.ColorScheme = "dracula"
	if err := profile.Save(cfg, profile.Path()); err != nil {
		t.Fatalf("Save initial config: %v", err)
	}

	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.PUT("/settings", NewUpdateSettingsHandler())

	body := []byte(`{"ui":{"theme":"dark"}}`)
	req := httptest.NewRequest(http.MethodPut, "/settings", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("status = %d, want 200: %s", rec.Code, rec.Body.String())
	}
	loaded, err := profile.Load(profile.Path())
	if err != nil {
		t.Fatalf("Load saved config: %v", err)
	}
	if loaded.UI.Theme != "dark" {
		t.Fatalf("saved theme = %q, want dark", loaded.UI.Theme)
	}
	if loaded.UI.ColorScheme != "dracula" {
		t.Fatalf(
			"saved color scheme = %q, want preserved dracula",
			loaded.UI.ColorScheme,
		)
	}
}

func TestNormalizeConfigUsesUILanguageForBackendI18n(t *testing.T) {
	oldLang := i18n.Lang()
	t.Cleanup(func() { i18n.SetLang(oldLang) })

	cfg := normalizeConfig(&profile.Config{
		Host:     "127.0.0.1",
		Port:     8080,
		Language: "en",
		UI:       profile.UI{Language: "zh"},
	})

	if cfg.Language != "zh-CN" {
		t.Fatalf("Language = %q, want zh-CN", cfg.Language)
	}
	if cfg.UI.Language != "zh-CN" {
		t.Fatalf("UI.Language = %q, want zh-CN", cfg.UI.Language)
	}
	if got := i18n.Lang(); got != "zh-CN" {
		t.Fatalf("i18n language = %q, want zh-CN", got)
	}
}

func TestUpdateSettingsAppliesLanguageChange(t *testing.T) {
	oldLang := i18n.Lang()
	t.Cleanup(func() { i18n.SetLang(oldLang) })

	t.Setenv(profile.ConfigDirEnvKey, t.TempDir())
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.PUT("/settings", NewUpdateSettingsHandler())

	body := []byte(`{"host":"127.0.0.1","port":8080,"ui":{"language":"zh"}}`)
	req := httptest.NewRequest(http.MethodPut, "/settings", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("status = %d, want 200: %s", rec.Code, rec.Body.String())
	}
	var resp struct {
		UI struct {
			Language string `json:"language"`
		} `json:"ui"`
	}
	if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	if resp.UI.Language != "zh-CN" {
		t.Fatalf("response ui.language = %q, want zh-CN", resp.UI.Language)
	}
	if got := i18n.Lang(); got != "zh-CN" {
		t.Fatalf("i18n language = %q, want zh-CN", got)
	}
}

func TestUpdateSettingsRejectsInvalidPort(t *testing.T) {
	t.Setenv(profile.ConfigDirEnvKey, t.TempDir())

	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.PUT("/settings", NewUpdateSettingsHandler())

	for _, body := range [][]byte{
		[]byte(`{"port":-1}`),
		[]byte(`{"port":65536}`),
	} {
		req := httptest.NewRequest(http.MethodPut, "/settings", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Fatalf("body %s status = %d, want 400: %s", body, rec.Code, rec.Body.String())
		}
	}
}

func TestNormalizeConfigPreservesAutoPort(t *testing.T) {
	cfg := normalizeConfig(&profile.Config{
		Host:     "127.0.0.1",
		Port:     0,
		Language: "en",
	})

	if cfg.Port != 0 {
		t.Fatalf("Port = %d, want 0", cfg.Port)
	}
}

func TestUpdateSettingsCanResetPortToAuto(t *testing.T) {
	dir := t.TempDir()
	t.Setenv(profile.ConfigDirEnvKey, dir)
	cfg := profile.DefaultConfig()
	cfg.Port = 49152
	if err := profile.Save(cfg, profile.Path()); err != nil {
		t.Fatalf("Save initial config: %v", err)
	}

	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.PUT("/settings", NewUpdateSettingsHandler())

	body := []byte(`{"host":"127.0.0.1","port":0}`)
	req := httptest.NewRequest(http.MethodPut, "/settings", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("status = %d, want 200: %s", rec.Code, rec.Body.String())
	}
	loaded, err := profile.Load(profile.Path())
	if err != nil {
		t.Fatalf("Load saved config: %v", err)
	}
	if loaded.Port != 0 {
		t.Fatalf("saved port = %d, want auto port 0", loaded.Port)
	}

	var resp struct {
		Port int `json:"port"`
	}
	if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	if resp.Port != 0 {
		t.Fatalf("response port = %d, want auto port 0", resp.Port)
	}
}

func TestUpdateSettingsAcceptsValidLogLevel(t *testing.T) {
	t.Setenv(profile.ConfigDirEnvKey, t.TempDir())
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.PUT("/settings", NewUpdateSettingsHandler())

	for _, level := range []string{"debug", "info", "warn", "error"} {
		body := []byte(`{"host":"127.0.0.1","port":8080,"log":{"level":"` + level + `"}}`)
		req := httptest.NewRequest(http.MethodPut, "/settings", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Fatalf("level=%q status = %d, want 200: %s", level, rec.Code, rec.Body.String())
		}
		var resp struct {
			Log struct {
				Level string `json:"level"`
			} `json:"log"`
		}
		if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
			t.Fatalf("decode response: %v", err)
		}
		if resp.Log.Level != level {
			t.Fatalf("response log.level = %q, want %q", resp.Log.Level, level)
		}
	}
}

func TestUpdateSettingsRejectsInvalidLogLevel(t *testing.T) {
	t.Setenv(profile.ConfigDirEnvKey, t.TempDir())

	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.PUT("/settings", NewUpdateSettingsHandler())

	body := []byte(`{"host":"127.0.0.1","port":8080,"log":{"level":"verbose"}}`)
	req := httptest.NewRequest(http.MethodPut, "/settings", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusBadRequest {
		t.Fatalf("status = %d, want 400: %s", rec.Code, rec.Body.String())
	}
}

func TestMergeConfigLogLevel(t *testing.T) {
	base := &profile.Config{
		Host: "127.0.0.1",
		Port: 8080,
		Log:  profile.Log{File: "agentteam.log", Level: "info"},
	}
	patch := &profile.Config{
		Log: profile.Log{Level: "debug"},
	}

	merged := mergeConfig(base, patch)
	if merged.Log.Level != "debug" {
		t.Errorf("Log.Level = %q, want %q", merged.Log.Level, "debug")
	}
	if merged.Log.File != "agentteam.log" {
		t.Errorf("Log.File = %q, want %q", merged.Log.File, "agentteam.log")
	}
}
