package setting_test

import (
	"bytes"
	"encoding/json"
	"net/http"
	"os"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/yaoapp/yao/openapi/tests/testutils"
)

// ---------------------------------------------------------------------------
// Functional tests (system:root token)
// ---------------------------------------------------------------------------

func TestSearchGet(t *testing.T) {
	serverURL := testutils.Prepare(t)
	defer testutils.Clean()
	initSettingRegistry(t)
	token := obtainToken(t, serverURL)

	req, err := http.NewRequest("GET", serverURL+baseURL()+"/setting/search", nil)
	assert.NoError(t, err)
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if !assert.NoError(t, err) || !assert.NotNil(t, resp) {
		return
	}
	defer resp.Body.Close()
	assert.Equal(t, http.StatusOK, resp.StatusCode)

	var body map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&body)
	assert.NoError(t, err)

	assert.Contains(t, body, "presets")
	assert.Contains(t, body, "providers")
	assert.Contains(t, body, "tool_assignment")

	presets, ok := body["presets"].([]interface{})
	assert.True(t, ok)
	assert.Equal(t, 4, len(presets), "should have 4 presets: cloud, tavily, serper, brightdata")

	providers, ok := body["providers"].([]interface{})
	assert.True(t, ok)
	assert.Equal(t, 4, len(providers), "should have 4 provider configs")

	// Cloud provider should be first
	first, _ := providers[0].(map[string]interface{})
	assert.Equal(t, "cloud", first["preset_key"])
}

func TestSearchGetUnauthenticated(t *testing.T) {
	serverURL := testutils.Prepare(t)
	defer testutils.Clean()

	req, err := http.NewRequest("GET", serverURL+baseURL()+"/setting/search", nil)
	assert.NoError(t, err)

	resp, err := http.DefaultClient.Do(req)
	assert.NoError(t, err)
	defer resp.Body.Close()
	assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
}

func TestSearchProviderUpdate(t *testing.T) {
	serverURL := testutils.Prepare(t)
	defer testutils.Clean()
	initSettingRegistry(t)
	token := obtainToken(t, serverURL)

	payload := map[string]interface{}{
		"field_values": map[string]string{
			"api_key": "tvly-test-key-12345",
		},
	}
	raw, _ := json.Marshal(payload)
	req, err := http.NewRequest("PUT", serverURL+baseURL()+"/setting/search/providers/tavily", bytes.NewReader(raw))
	assert.NoError(t, err)
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	assert.NoError(t, err)
	defer resp.Body.Close()
	assert.Equal(t, http.StatusOK, resp.StatusCode)

	var body map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&body)
	assert.Equal(t, "tavily", body["preset_key"])

	// api_key should be masked in response
	fv, _ := body["field_values"].(map[string]interface{})
	maskedKey, _ := fv["api_key"].(string)
	assert.True(t, strings.Contains(maskedKey, "..."), "api_key should be masked")

	// GET should also return masked key
	req2, _ := http.NewRequest("GET", serverURL+baseURL()+"/setting/search", nil)
	req2.Header.Set("Authorization", "Bearer "+token)
	resp2, err := http.DefaultClient.Do(req2)
	assert.NoError(t, err)
	defer resp2.Body.Close()

	var getData map[string]interface{}
	json.NewDecoder(resp2.Body).Decode(&getData)
	providers, _ := getData["providers"].([]interface{})
	for _, p := range providers {
		pm, _ := p.(map[string]interface{})
		if pm["preset_key"] == "tavily" {
			tfv, _ := pm["field_values"].(map[string]interface{})
			assert.True(t, strings.Contains(tfv["api_key"].(string), "..."), "GET should return masked key")
		}
	}
}

func TestSearchProviderUpdateCloud(t *testing.T) {
	serverURL := testutils.Prepare(t)
	defer testutils.Clean()
	initSettingRegistry(t)
	token := obtainToken(t, serverURL)

	payload := map[string]interface{}{
		"field_values": map[string]string{},
	}
	raw, _ := json.Marshal(payload)
	req, err := http.NewRequest("PUT", serverURL+baseURL()+"/setting/search/providers/cloud", bytes.NewReader(raw))
	assert.NoError(t, err)
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	assert.NoError(t, err)
	defer resp.Body.Close()
	assert.Equal(t, http.StatusBadRequest, resp.StatusCode, "cloud provider should be rejected")
}

func TestSearchProviderToggle(t *testing.T) {
	serverURL := testutils.Prepare(t)
	defer testutils.Clean()
	initSettingRegistry(t)
	token := obtainToken(t, serverURL)

	// Save tavily first
	savePayload := map[string]interface{}{
		"field_values": map[string]string{"api_key": "tvly-toggle-key"},
	}
	raw, _ := json.Marshal(savePayload)
	req, _ := http.NewRequest("PUT", serverURL+baseURL()+"/setting/search/providers/tavily", bytes.NewReader(raw))
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	assert.NoError(t, err)
	resp.Body.Close()

	// Enable tavily
	enablePayload := map[string]interface{}{"enabled": true}
	raw, _ = json.Marshal(enablePayload)
	req2, _ := http.NewRequest("PUT", serverURL+baseURL()+"/setting/search/providers/tavily/toggle", bytes.NewReader(raw))
	req2.Header.Set("Authorization", "Bearer "+token)
	req2.Header.Set("Content-Type", "application/json")
	resp2, err := http.DefaultClient.Do(req2)
	assert.NoError(t, err)
	defer resp2.Body.Close()
	assert.Equal(t, http.StatusOK, resp2.StatusCode)

	var body map[string]interface{}
	json.NewDecoder(resp2.Body).Decode(&body)
	assert.Equal(t, true, body["enabled"])

	// Assign tavily to web_search
	assignPayload := map[string]interface{}{"web_search": "tavily"}
	raw, _ = json.Marshal(assignPayload)
	req3, _ := http.NewRequest("PUT", serverURL+baseURL()+"/setting/search/tool-assignment", bytes.NewReader(raw))
	req3.Header.Set("Authorization", "Bearer "+token)
	req3.Header.Set("Content-Type", "application/json")
	resp3, err := http.DefaultClient.Do(req3)
	assert.NoError(t, err)
	resp3.Body.Close()

	// Disable tavily -- should clear tool_assignment
	disablePayload := map[string]interface{}{"enabled": false}
	raw, _ = json.Marshal(disablePayload)
	req4, _ := http.NewRequest("PUT", serverURL+baseURL()+"/setting/search/providers/tavily/toggle", bytes.NewReader(raw))
	req4.Header.Set("Authorization", "Bearer "+token)
	req4.Header.Set("Content-Type", "application/json")
	resp4, err := http.DefaultClient.Do(req4)
	assert.NoError(t, err)
	resp4.Body.Close()

	// Verify tool_assignment cleared
	req5, _ := http.NewRequest("GET", serverURL+baseURL()+"/setting/search", nil)
	req5.Header.Set("Authorization", "Bearer "+token)
	resp5, err := http.DefaultClient.Do(req5)
	assert.NoError(t, err)
	defer resp5.Body.Close()

	var getData map[string]interface{}
	json.NewDecoder(resp5.Body).Decode(&getData)
	ta, _ := getData["tool_assignment"].(map[string]interface{})
	assert.Nil(t, ta["web_search"], "web_search should be cleared after disabling tavily")
}

func TestSearchToolAssignment(t *testing.T) {
	serverURL := testutils.Prepare(t)
	defer testutils.Clean()
	initSettingRegistry(t)
	token := obtainToken(t, serverURL)

	// Save and enable tavily
	savePayload := map[string]interface{}{"field_values": map[string]string{"api_key": "tvly-assign-key"}}
	raw, _ := json.Marshal(savePayload)
	req, _ := http.NewRequest("PUT", serverURL+baseURL()+"/setting/search/providers/tavily", bytes.NewReader(raw))
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")
	resp, _ := http.DefaultClient.Do(req)
	resp.Body.Close()

	enablePayload := map[string]interface{}{"enabled": true}
	raw, _ = json.Marshal(enablePayload)
	req2, _ := http.NewRequest("PUT", serverURL+baseURL()+"/setting/search/providers/tavily/toggle", bytes.NewReader(raw))
	req2.Header.Set("Authorization", "Bearer "+token)
	req2.Header.Set("Content-Type", "application/json")
	resp2, _ := http.DefaultClient.Do(req2)
	resp2.Body.Close()

	// Assign tavily to web_search
	assignPayload := map[string]interface{}{"web_search": "tavily"}
	raw, _ = json.Marshal(assignPayload)
	req3, _ := http.NewRequest("PUT", serverURL+baseURL()+"/setting/search/tool-assignment", bytes.NewReader(raw))
	req3.Header.Set("Authorization", "Bearer "+token)
	req3.Header.Set("Content-Type", "application/json")
	resp3, err := http.DefaultClient.Do(req3)
	assert.NoError(t, err)
	defer resp3.Body.Close()
	assert.Equal(t, http.StatusOK, resp3.StatusCode)

	var body map[string]interface{}
	json.NewDecoder(resp3.Body).Decode(&body)
	assert.Equal(t, "tavily", body["web_search"])
}

func TestSearchToolAssignmentDisabledProvider(t *testing.T) {
	serverURL := testutils.Prepare(t)
	defer testutils.Clean()
	initSettingRegistry(t)
	token := obtainToken(t, serverURL)

	// Try to assign a provider that isn't enabled
	assignPayload := map[string]interface{}{"web_search": "serper"}
	raw, _ := json.Marshal(assignPayload)
	req, _ := http.NewRequest("PUT", serverURL+baseURL()+"/setting/search/tool-assignment", bytes.NewReader(raw))
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	assert.NoError(t, err)
	defer resp.Body.Close()
	assert.Equal(t, http.StatusBadRequest, resp.StatusCode, "should reject assignment to disabled provider")
}

func TestSearchProviderTest(t *testing.T) {
	apiKey := os.Getenv("TAVILY_API_KEY")
	if apiKey == "" {
		t.Skip("TAVILY_API_KEY not set, skipping search provider test")
	}

	serverURL := testutils.Prepare(t)
	defer testutils.Clean()
	initSettingRegistry(t)
	token := obtainToken(t, serverURL)

	payload := map[string]interface{}{
		"field_values": map[string]string{"api_key": apiKey},
	}
	raw, _ := json.Marshal(payload)
	req, err := http.NewRequest("POST", serverURL+baseURL()+"/setting/search/providers/tavily/test", bytes.NewReader(raw))
	assert.NoError(t, err)
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	assert.NoError(t, err)
	defer resp.Body.Close()
	assert.Equal(t, http.StatusOK, resp.StatusCode)

	var body map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&body)
	assert.Equal(t, true, body["success"])
}

func TestSearchProviderTestCloud(t *testing.T) {
	serverURL := testutils.Prepare(t)
	defer testutils.Clean()
	initSettingRegistry(t)
	token := obtainToken(t, serverURL)

	req, _ := http.NewRequest("POST", serverURL+baseURL()+"/setting/search/providers/cloud/test", nil)
	req.Header.Set("Authorization", "Bearer "+token)
	resp, err := http.DefaultClient.Do(req)
	assert.NoError(t, err)
	defer resp.Body.Close()
	assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
}

// ---------------------------------------------------------------------------
// ACL permission tests
// ---------------------------------------------------------------------------

func TestSearchACL_ReadOnlyScopeCannotWrite(t *testing.T) {
	serverURL := testutils.Prepare(t)
	defer testutils.Clean()
	initSettingRegistry(t)

	readToken := obtainRestrictedToken(t, serverURL, "setting:search:read:all")

	// GET should work
	req, _ := http.NewRequest("GET", serverURL+baseURL()+"/setting/search", nil)
	req.Header.Set("Authorization", "Bearer "+readToken)
	resp, err := http.DefaultClient.Do(req)
	assert.NoError(t, err)
	defer resp.Body.Close()
	assert.Equal(t, http.StatusOK, resp.StatusCode, "read-only scope should allow GET")

	// PUT should be denied
	payload := map[string]interface{}{
		"field_values": map[string]string{"api_key": "tvly-acl-test"},
	}
	raw, _ := json.Marshal(payload)
	req2, _ := http.NewRequest("PUT", serverURL+baseURL()+"/setting/search/providers/tavily", bytes.NewReader(raw))
	req2.Header.Set("Authorization", "Bearer "+readToken)
	req2.Header.Set("Content-Type", "application/json")
	resp2, err := http.DefaultClient.Do(req2)
	assert.NoError(t, err)
	defer resp2.Body.Close()
	assert.Equal(t, http.StatusForbidden, resp2.StatusCode, "read-only scope should deny PUT")
}
