package sfcrmappservice

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"reflect"
	"strings"
	"testing"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/applicationnavigationv1"
	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
)

func TestNavigationEditorPreservesCompleteDocumentAndRejectsMissingItems(t *testing.T) {
	view := uiAppsTestView(t, uiAppsTestNavigation)
	service := uiAppsTestService(t, view, uiAppsTestPermissions(view))
	url := "https://example.test/docs"
	items := []semantic.Item{{ID: "docs", Label: "Documentation", Icon: "Link", Target: semantic.Target{URL: &url}}, {ID: "hidden", Label: "Restricted collection", Target: semantic.Target{Resource: &semantic.ResourceTarget{ResourceType: "PrivateResource"}}}}
	items = append(items, semantic.Item{ID: "retired", Label: "Retired page", Target: semantic.Target{Page: &semantic.PageTarget{ID: "RetiredPage"}}})
	document := semantic.Document{ApplicationID: "Sales", Revision: 7, Items: items}
	receipts := common.Receipts{ProtocolVersion: "1.0", SecurityEpoch: 1, CapabilityVersion: semantic.CapabilityName}
	calls := 0
	service.applicationNavigation = &RuntimeApplicationNavigationClient{
		read: func(_ context.Context, bearer string, r semantic.ReadRequest) (semantic.ReadResponse, error) {
			if bearer == "" || r.ApplicationID != "Sales" {
				t.Fatal("identity lost")
			}
			return semantic.ReadResponse{Document: document, Receipts: receipts}, nil
		},
		save: func(_ context.Context, _ string, r semantic.SaveRequest) (semantic.SaveResponse, error) {
			calls++
			document = semantic.Document{ApplicationID: r.ApplicationID, Revision: r.ExpectedRevision + 1, Items: r.Items}
			return semantic.SaveResponse{Document: document, Changed: true, Receipts: receipts}, nil
		},
	}
	read := httptest.NewRecorder()
	service.ServeHTTP(read, uiAppsRequest(http.MethodGet, ApplicationNavigationPrefix+"Sales"))
	var result semantic.ReadResponse
	if read.Code != http.StatusOK || json.Unmarshal(read.Body.Bytes(), &result) != nil || !reflect.DeepEqual(result.Document.Items, items) {
		t.Fatalf("full admin document lost: %d %s", read.Code, read.Body.String())
	}
	for _, body := range []string{`{"expected_revision":7,"idempotency_key":"save"}`, `{"expected_revision":7,"idempotency_key":"save","items":null}`, `{"expected_revision":7,"idempotency_key":"save","items":[],"application_id":"another"}`, `{"expected_revision":7,"idempotency_key":"save","items":[{"id":"x","label":"x","target":{"page":{"id":"Home","path":"/chatter"}}}]}`} {
		request := httptest.NewRequest(http.MethodPut, ApplicationNavigationPrefix+"Sales", strings.NewReader(body))
		request.Header.Set("Authorization", "Bearer user-token")
		response := httptest.NewRecorder()
		service.ServeHTTP(response, request)
		if response.Code != http.StatusBadRequest || calls != 0 {
			t.Fatalf("invalid body reached mutation: %d %s", response.Code, response.Body.String())
		}
	}
	fullBody, _ := json.Marshal(map[string]any{"expected_revision": 7, "idempotency_key": "keep-hidden", "items": items})
	fullRequest := httptest.NewRequest(http.MethodPut, ApplicationNavigationPrefix+"Sales", strings.NewReader(string(fullBody)))
	fullRequest.Header.Set("Authorization", "Bearer user-token")
	fullResponse := httptest.NewRecorder()
	service.ServeHTTP(fullResponse, fullRequest)
	if fullResponse.Code != http.StatusOK || !reflect.DeepEqual(document.Items, items) || calls != 1 {
		t.Fatalf("full hidden-item save=%d %s", fullResponse.Code, fullResponse.Body.String())
	}
	request := httptest.NewRequest(http.MethodPut, ApplicationNavigationPrefix+"Sales", strings.NewReader(`{"expected_revision":8,"idempotency_key":"save","items":[]}`))
	request.Header.Set("Authorization", "Bearer user-token")
	saved := httptest.NewRecorder()
	service.ServeHTTP(saved, request)
	if saved.Code != http.StatusOK || document.Items == nil || len(document.Items) != 0 || calls != 2 {
		t.Fatalf("explicit empty save=%d %s", saved.Code, saved.Body.String())
	}
	unauthenticated := httptest.NewRecorder()
	service.ServeHTTP(unauthenticated, httptest.NewRequest(http.MethodGet, ApplicationNavigationPrefix+"Sales", nil))
	if unauthenticated.Code != http.StatusUnauthorized {
		t.Fatal("unauthenticated editor read accepted")
	}
}

// Availability belongs to serving. It cannot block an unrelated full-document
// edit or revalidation of the same idempotent request after a release upgrade.
func TestNavigationPageAvailabilityDoesNotChangeAuthoring(t *testing.T) {
	items := []semantic.Item{
		{ID: "retired", Label: "My old page", Target: semantic.Target{Page: &semantic.PageTarget{ID: "RetiredPage"}}},
		{ID: "home", Label: "My start", Target: semantic.Target{Page: &semantic.PageTarget{ID: "Home"}}},
	}
	if err := validateApplicationNavigationTargets(items); err != nil {
		t.Fatal(err)
	}
	projected, err := projectCurrentNavigation(items)
	if err != nil || len(projected) != 1 || projected[0].ID != "home" || projected[0].Label != "My start" || projected[0].ObjectAPIName != "Home" {
		t.Fatalf("available navigation = %+v, %v", projected, err)
	}
	if len(items) != 2 || items[0].Target.Page.ID != "RetiredPage" {
		t.Fatal("serving projection mutated the authoring document")
	}
}
