package sfcrmappservice

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"net"
	"net/http"
	"net/http/httptest"
	"os"
	"reflect"
	"strings"
	"testing"
	"time"

	catalogsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/applicationnavigationv1"
	permissionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/envelope"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/metadata"
)

// The Core integration runner invokes this separately compiled L1 executable
// with its real TiDB-backed Runtime servers. Catalog shell/permission envelopes
// are frozen fixtures; navigation reads, writes, visibility and replay are real.
// No product import is added to the L0 dependency graph.
func TestNavigationExternalRuntimeWalkingSlice(t *testing.T) {
	addresses := strings.Split(os.Getenv("NEXUSCRM_NAVIGATION_RUNTIME_ADDRESSES"), ",")
	if addresses[0] == "" {
		t.Skip("invoked by Core's TestRuntimeNavigationRealTiDBConcurrentAuthorizationAndReplay with NEXUSCRM_NAVIGATION_CONSUMER_TEST_BINARY")
	}
	delegation := os.Getenv("NEXUSCRM_NAVIGATION_TEST_DELEGATION")
	if delegation == "" || len(addresses) < 2 {
		t.Fatal("walking slice requires delegated identity and independent Runtime instances")
	}
	clients := make([]runtimev1.ApplicationNavigationServiceClient, 0, len(addresses))
	for _, address := range addresses {
		connection, err := grpc.NewClient("passthrough:///"+address, grpc.WithTransportCredentials(insecure.NewCredentials()))
		if err != nil {
			t.Fatal(err)
		}
		t.Cleanup(func() { _ = connection.Close() })
		clients = append(clients, runtimev1.NewApplicationNavigationServiceClient(connection))
	}
	caller := func(ctx context.Context, bearer string) context.Context {
		if bearer != "user-secret" {
			t.Errorf("native API lost bearer identity")
		}
		return metadata.AppendToOutgoingContext(ctx,
			envelope.ProtocolMajorMetadataKey, "1", envelope.ProtocolMinorMetadataKey, "0",
			envelope.CapabilityMetadataKey, semantic.CapabilityName,
			envelope.RuntimeDelegationMetadataKey, delegation,
		)
	}
	navigation, err := NewRuntimeApplicationNavigationClient(
		func(ctx context.Context, bearer string, request semantic.ReadRequest) (semantic.ReadResponse, error) {
			wire, err := mapping.ToWireNavigationReadRequest(ctx, request)
			if err != nil {
				return semantic.ReadResponse{}, err
			}
			reply, err := clients[1].Read(caller(ctx, bearer), wire)
			if err != nil {
				t.Logf("external Runtime read failed: %v", err)
				return semantic.ReadResponse{}, mapping.FromPlatformStatusError(err, semantic.CapabilityName)
			}
			return mapping.FromWireNavigationReadResponse(request, reply)
		},
		func(ctx context.Context, bearer string, request semantic.ResolveRequest) (semantic.ResolveResponse, error) {
			wire, err := mapping.ToWireNavigationResolveRequest(ctx, request)
			if err != nil {
				return semantic.ResolveResponse{}, err
			}
			reply, err := clients[1].Resolve(caller(ctx, bearer), wire)
			if err != nil {
				return semantic.ResolveResponse{}, mapping.FromPlatformStatusError(err, semantic.CapabilityName)
			}
			return mapping.FromWireNavigationResolveResponse(request, reply)
		},
		func(ctx context.Context, bearer string, request semantic.SaveRequest) (semantic.SaveResponse, error) {
			wire, err := mapping.ToWireNavigationSaveRequest(ctx, request)
			if err != nil {
				return semantic.SaveResponse{}, err
			}
			reply, err := clients[0].Save(caller(ctx, bearer), wire)
			if err != nil {
				t.Logf("external Runtime save failed: %v", err)
				return semantic.SaveResponse{}, mapping.FromPlatformStatusError(err, semantic.CapabilityName)
			}
			return mapping.FromWireNavigationSaveResponse(request, reply)
		},
	)
	if err != nil {
		t.Fatal(err)
	}
	view := uiAppsTestView(t, `{"applications":[{"id":"app","frontendAppId":"test.application","label":"App","navigationItems":[{"id":"stale","type":"web","pageUrl":"https://example.test/stale","label":"Stale Catalog item"}],"utilityItems":[]}]}`)
	view.Receipts.SecurityEpoch = 1
	view.Lineage.ViewDigest = catalogsemantic.ComputeAppMetadataViewV3Digest(view)
	permissions := uiAppsTestPermissions(view)
	permissions.ApplicationPermissions = []permissionsemantic.ApplicationPermission{{ApplicationID: "app", CanView: true, IsDefault: true}}
	service := uiAppsTestService(t, view, permissions)
	service.applicationNavigation = navigation
	service.uiApps.resolveNavigation = navigation.resolve
	browserHandler := http.Handler(service)
	if os.Getenv("NEXUSCRM_NAVIGATION_BROWSER_ADDRESS") != "" {
		browserHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// These unrelated discovery surfaces are empty in this navigation fixture.
			if r.URL.Path == "/services/data/v63.0/sobjects" {
				writeJSON(w, 200, map[string]any{"sobjects": []any{}})
				return
			}
			if r.URL.Path == "/services/data/v63.0/analytics/dashboards" {
				writeJSON(w, 200, []any{})
				return
			}
			if strings.HasPrefix(r.URL.Path, "/services/data/v63.0/tooling/") {
				writeJSON(w, 200, map[string]any{"records": []any{}, "done": true, "totalSize": 0})
				return
			}
			service.ServeHTTP(w, r)
		})
	}
	server := httptest.NewUnstartedServer(browserHandler)
	if address := os.Getenv("NEXUSCRM_NAVIGATION_BROWSER_ADDRESS"); address != "" {
		listener, err := net.Listen("tcp", address)
		if err != nil {
			t.Fatal(err)
		}
		_ = server.Listener.Close()
		server.Listener = listener
	}
	server.Start()
	t.Cleanup(server.Close)
	client := &http.Client{Timeout: 5 * time.Second}
	call := func(method, path string, body any, status int) []byte {
		t.Helper()
		var encoded []byte
		if body != nil {
			encoded, err = json.Marshal(body)
			if err != nil {
				t.Fatal(err)
			}
		}
		request, err := http.NewRequest(method, server.URL+path, bytes.NewReader(encoded))
		if err != nil {
			t.Fatal(err)
		}
		request.Header.Set("Authorization", "Bearer user-secret")
		request.Header.Set("Content-Type", "application/json")
		response, err := client.Do(request)
		if err != nil {
			t.Fatal(err)
		}
		defer response.Body.Close()
		payload, err := io.ReadAll(response.Body)
		if err != nil || response.StatusCode != status {
			t.Fatalf("%s %s: status=%d body=%s error=%v", method, path, response.StatusCode, payload, err)
		}
		return payload
	}
	read := func() semantic.Document {
		var response semantic.ReadResponse
		if err := json.Unmarshal(call(http.MethodGet, ApplicationNavigationPrefix+"app", nil, http.StatusOK), &response); err != nil {
			t.Fatal(err)
		}
		return response.Document
	}
	before := read()
	url := "https://example.test/live?q=1#fragment"
	items := []semantic.Item{
		{ID: "live", Label: "Live link", Icon: "Book", Target: semantic.Target{URL: &url}},
		{ID: "hidden", Label: "Hidden admin page", Target: semantic.Target{Page: &semantic.PageTarget{ID: "Home"}}},
		{ID: "retired", Label: "Retired page", Target: semantic.Target{Page: &semantic.PageTarget{ID: "RetiredPage"}}},
	}
	request := semantic.SaveRequest{ApplicationID: "app", ExpectedRevision: before.Revision, IdempotencyKey: "http-walking-slice", Items: items}
	// The public path owns identity; only the editable request fields are sent.
	body := map[string]any{"expected_revision": request.ExpectedRevision, "idempotency_key": request.IdempotencyKey, "items": items}
	saved := call(http.MethodPut, ApplicationNavigationPrefix+"app", body, http.StatusOK)
	if again := call(http.MethodPut, ApplicationNavigationPrefix+"app", body, http.StatusOK); !bytes.Equal(saved, again) {
		t.Fatal("HTTP retry did not replay the original response")
	}
	if after := read(); !reflect.DeepEqual(after.Items, items) || after.Revision != before.Revision+1 {
		t.Fatalf("cross-Runtime admin read lost full document: %+v", after)
	}
	menu := call(http.MethodGet, "/services/data/v63.0/ui-api/apps", nil, http.StatusOK)
	if !bytes.Contains(menu, []byte("Live link")) || bytes.Contains(menu, []byte("Hidden admin page")) || bytes.Contains(menu, []byte("Retired page")) || bytes.Contains(menu, []byte("Stale Catalog item")) {
		t.Fatalf("UI Apps did not use current visibility-filtered navigation: %s", menu)
	}
	body["idempotency_key"] = "http-conflict"
	call(http.MethodPut, ApplicationNavigationPrefix+"app", body, http.StatusConflict)
	body["expected_revision"] = before.Revision + 1
	body["idempotency_key"] = "http-empty"
	body["items"] = []semantic.Item{}
	call(http.MethodPut, ApplicationNavigationPrefix+"app", body, http.StatusOK)
	var empty struct {
		Apps map[string]uiApp `json:"apps"`
	}
	if err := json.Unmarshal(call(http.MethodGet, "/services/data/v63.0/ui-api/apps", nil, http.StatusOK), &empty); err != nil {
		t.Fatal(err)
	}
	app, found := empty.Apps["app"]
	if !found || app.NavItems == nil || len(app.NavItems) != 0 {
		t.Fatalf("empty menu was not preserved: %+v", empty)
	}
	if view.Lineage.ViewDigest != catalogsemantic.ComputeAppMetadataViewV3Digest(view) {
		t.Fatal("navigation changed Catalog fixture")
	}
	if done := os.Getenv("NEXUSCRM_NAVIGATION_BROWSER_DONE_FILE"); done != "" {
		body["expected_revision"] = before.Revision + 2
		body["idempotency_key"] = "browser-editor-fixture"
		body["items"] = items
		call(http.MethodPut, ApplicationNavigationPrefix+"app", body, http.StatusOK)
		t.Logf("Browser fixture ready: %s", server.URL)
		deadline := time.NewTimer(4 * time.Minute)
		defer deadline.Stop()
		ticker := time.NewTicker(time.Second)
		defer ticker.Stop()
		for {
			select {
			case <-deadline.C:
				t.Fatal("browser fixture exceeded its bounded verification window")
			case <-ticker.C:
				if _, err := os.Stat(done); err == nil {
					return
				}
			}
		}
	}

}
