//go:build runtime_api_integration

package runtimeapi

import (
	"context"
	"database/sql"
	"encoding/json"
	"os"
	osexec "os/exec"
	"reflect"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/nexuscrm/backend/internal/runtimeassembly"
	database "github.com/nexuscrm/db"
	"github.com/nexuscrm/kernel/pkg/schemacatalog"
	featuremetadata "github.com/nexuscrm/metadata"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/persistence"
	"github.com/pingcap-inc/nexuscrm-api-host/runtimegrpc"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/applicationnavigationv1"
	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	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"
)

func TestRuntimeNavigationRealTiDBConcurrentAuthorizationAndReplay(t *testing.T) {
	if os.Getenv("TIDB_HOST") == "" {
		t.Fatal("navigation walking slice requires TIDB_HOST")
	}
	db, cleanup := openRuntimeAPIScratchDB(t)
	t.Cleanup(cleanup)
	security := durableIntegrationSecurityAdmission(t, db, "navigation-test", semantic.CapabilityName)
	createRuntimeAPICatalogActionAuthorizationFixture(t, db)
	navigationTestTables(t, db, "_System_App", "_System_ApplicationNavigationReceipt", "_System_SetupAuditTrail")
	exec := func(query string, args ...any) {
		t.Helper()
		if _, err := db.Exec(query, args...); err != nil {
			t.Fatal(err)
		}
	}
	exec(`CREATE TABLE UserRole (Id VARCHAR(255) PRIMARY KEY, Name VARCHAR(255), RollupDescription TEXT, ParentRoleId VARCHAR(255), IsDeleted BOOL NOT NULL DEFAULT 0)`)
	exec(`INSERT INTO _System_Permission (Id,ApiName,Label,Category,IsSystem,IsActive,CreatedDate,LastModifiedDate) VALUES ('customize','CustomizeApplication','Customize','runtime',1,1,NOW(),NOW())`)
	exec(`INSERT INTO _System_PermissionSetPermission (Id,PermissionSetId,PermissionApiName,IsEnabled,CreatedDate,LastModifiedDate) VALUES ('customize-grant','profile-permissions','CustomizeApplication',1,NOW(),NOW())`)
	exec(`INSERT INTO _System_App (Id,Name,Label,FrontendAppId,Icon,Color,NavigationItems,NavigationRevision,UtilityItems,AssignedProfiles) VALUES ('app','app','App','test.application','','',JSON_ARRAY(),1,JSON_ARRAY(),JSON_ARRAY())`)
	exec(`INSERT INTO ProfileAppVisibility (Id,ProfileId,AppId,IsVisible,IsDefault,CreatedDate,LastModifiedDate) VALUES ('visible','profile-test','app',1,1,NOW(),NOW())`)
	exec(`INSERT INTO ProfileTabSetting (Id,ProfileId,TabName,Visibility,CreatedDate,LastModifiedDate) VALUES ('start-tab','profile-test','Start','DefaultOn',NOW(),NOW())`)
	admission, err := NewApplicationNavigationAdmission(ApplicationNavigationSelection{OrgID: testOrgID, AppInstallationID: testInstallationID, WorkloadID: testWorkloadID, FrontendAppID: "test.application"})
	if err != nil {
		t.Fatal(err)
	}
	var addresses []string
	start := func() runtimev1.ApplicationNavigationServiceClient {
		connection := database.NewConnectionFromDB(db)
		metadataService := featuremetadata.NewMetadataService(persistence.NewMetadataRepository(db), nil, nil)
		permissionService := permissions.NewPermissionService(persistence.NewPermissionRepository(db), metadataService, persistence.NewUserRepository(db))
		engine, err := runtimeassembly.NewApplicationNavigationRuntime(connection, permissionService)
		if err != nil {
			t.Fatal(err)
		}
		config := ProcessConfig{Version: "navigation-integration", AdminAddress: "127.0.0.1:0", GRPCAddress: "127.0.0.1:0", AdmissionLimit: DefaultAdmissionLimit, ServingEnabled: true, WorkloadAuthority: testWorkloadAuthority, SecurityAdmission: security, ApplicationNavigation: navigationDiagnosticEngine{ApplicationNavigationRuntime: engine, t: t}, ApplicationNavigationAdmission: admission,
			PlatformCapabilityMaxAdmissionLeases: map[string]time.Duration{semantic.CapabilityName: 35 * time.Second},
			PlatformResourceClassAdmissionLimits: map[runtimegrpc.ResourceClass]int{runtimegrpc.ResourceClassHeavyWork: 4},
		}
		process, err := Assemble(config, &readerStub{})
		if err != nil {
			t.Fatal(err)
		}
		if err := process.Start(); err != nil {
			t.Fatal(err)
		}
		t.Cleanup(func() { stopProcess(t, process) })
		addresses = append(addresses, process.GRPCAddress())
		client, err := grpc.NewClient("passthrough:///"+process.GRPCAddress(), grpc.WithTransportCredentials(insecure.NewCredentials()))
		if err != nil {
			t.Fatal(err)
		}
		t.Cleanup(func() { client.Close() })
		return runtimev1.NewApplicationNavigationServiceClient(client)
	}
	first, second := start(), start()
	ctx, cancel := context.WithTimeout(platformProtocolContext(context.Background(), semantic.CapabilityName), 30*time.Second)
	defer cancel()
	save := func(client runtimev1.ApplicationNavigationServiceClient, request semantic.SaveRequest) (semantic.SaveResponse, error) {
		wire, err := mapping.ToWireNavigationSaveRequest(ctx, request)
		if err != nil {
			return semantic.SaveResponse{}, err
		}
		response, err := client.Save(ctx, wire)
		if err != nil {
			return semantic.SaveResponse{}, mapping.FromPlatformStatusError(err, semantic.CapabilityName)
		}
		return mapping.FromWireNavigationSaveResponse(request, response)
	}
	read := func(client runtimev1.ApplicationNavigationServiceClient) semantic.ReadResponse {
		request := semantic.ReadRequest{ApplicationID: "app"}
		wire, err := mapping.ToWireNavigationReadRequest(ctx, request)
		if err != nil {
			t.Fatal(err)
		}
		response, err := client.Read(ctx, wire)
		if err != nil {
			t.Fatal(err)
		}
		result, err := mapping.FromWireNavigationReadResponse(request, response)
		if err != nil {
			t.Fatal(err)
		}
		return result
	}
	original := read(first)
	url := "https://example.test/docs"
	base := semantic.SaveRequest{ApplicationID: "app", ExpectedRevision: 1, IdempotencyKey: "one", Items: []semantic.Item{{ID: "start", Label: "Renamed", Target: semantic.Target{Page: &semantic.PageTarget{ID: "Start"}}}, {ID: "hidden", Label: "Hidden", Target: semantic.Target{Page: &semantic.PageTarget{ID: "Hidden"}}}, {ID: "docs", Label: "Docs", Target: semantic.Target{URL: &url}}}}
	alternate := base
	alternate.IdempotencyKey = "two"
	alternate.Items = []semantic.Item{{ID: "alternate", Label: "Alternative", Target: semantic.Target{URL: &url}}}
	type outcome struct {
		request  semantic.SaveRequest
		response semantic.SaveResponse
		err      error
	}
	outcomes := make(chan outcome, 2)
	var wg sync.WaitGroup
	for index, request := range []semantic.SaveRequest{base, alternate} {
		wg.Add(1)
		go func(index int, request semantic.SaveRequest) {
			defer wg.Done()
			client := first
			if index == 1 {
				client = second
			}
			response, err := save(client, request)
			outcomes <- outcome{request, response, err}
		}(index, request)
	}
	wg.Wait()
	close(outcomes)
	var winner outcome
	wins := 0
	for result := range outcomes {
		if result.err == nil {
			winner = result
			wins++
		} else if failure, ok := result.err.(*common.Failure); !ok || failure.Category != common.FailureConflict {
			t.Fatalf("unexpected concurrent failure: %v", result.err)
		}
	}
	if wins != 1 {
		t.Fatalf("concurrent winners=%d", wins)
	}
	observed := read(second)
	if !reflect.DeepEqual(observed.Document, winner.response.Document) {
		t.Fatal("second Runtime missed committed navigation")
	}
	replay, err := save(second, winner.request)
	if err != nil || !reflect.DeepEqual(replay.Document, winner.response.Document) {
		t.Fatalf("cross-instance replay=%+v %v", replay, err)
	}
	// A deterministic full document proves that effective navigation filters by
	// stable targets while the admin read retains hidden items.
	base.ExpectedRevision = observed.Document.Revision
	base.IdempotencyKey = "full"
	full, err := save(first, base)
	if err != nil {
		t.Fatal(err)
	}
	resolve := semantic.ResolveRequest{ApplicationIDs: []string{"app"}}
	resolveWire, err := mapping.ToWireNavigationResolveRequest(ctx, resolve)
	if err != nil {
		t.Fatal(err)
	}
	resolveReply, err := second.Resolve(ctx, resolveWire)
	if err != nil {
		t.Fatal(err)
	}
	visible, err := mapping.FromWireNavigationResolveResponse(resolve, resolveReply)
	if err != nil {
		t.Fatal(err)
	}
	if len(visible.Documents) != 1 || len(visible.Documents[0].Items) != 2 || visible.Documents[0].Items[0].ID != "start" || visible.Documents[0].Items[1].ID != "docs" {
		t.Fatalf("visibility=%+v", visible.Documents)
	}
	if len(read(second).Document.Items) != 3 {
		t.Fatal("admin read lost hidden item")
	}
	// Menu edits must not advance any security/publication authority.
	var permissionRevision, securityEpoch uint64
	if err := db.QueryRow(`SELECT Revision FROM _System_PermissionStateVersion WHERE SingletonKey=1`).Scan(&permissionRevision); err != nil {
		t.Fatal(err)
	}
	if err := db.QueryRow(`SELECT SecurityEpoch FROM _System_RuntimeSecurityState WHERE OrgId=?`, testOrgID).Scan(&securityEpoch); err != nil {
		t.Fatal(err)
	}
	if permissionRevision != 1 || securityEpoch != 1 || full.Receipts.CatalogRevision != 0 || full.Receipts.SecurityEpoch != original.Receipts.SecurityEpoch {
		t.Fatalf("menu edit changed authority: %d %d %+v", permissionRevision, securityEpoch, full.Receipts)
	}
	exec(`UPDATE _System_PermissionSetPermission SET IsEnabled=0 WHERE Id='customize-grant'`)
	exec(`UPDATE _System_PermissionStateVersion SET Revision=Revision+1 WHERE SingletonKey=1`)
	if _, err := save(second, base); err == nil {
		t.Fatal("revoked administrator replayed a save")
	} else if failure, ok := err.(*common.Failure); !ok || failure.Category != common.FailureForbidden {
		t.Fatalf("revoked save=%v", err)
	}
	readWire, err := mapping.ToWireNavigationReadRequest(ctx, semantic.ReadRequest{ApplicationID: "app"})
	if err != nil {
		t.Fatal(err)
	}
	if _, err := second.Read(ctx, readWire); err == nil {
		t.Fatal("revoked administrator read full navigation")
	}
	var stored []byte
	if err := db.QueryRow(`SELECT NavigationItems FROM _System_App WHERE Id='app'`).Scan(&stored); err != nil {
		t.Fatal(err)
	}
	var current []semantic.Item
	if err := json.Unmarshal(stored, &current); err != nil || !reflect.DeepEqual(current, base.Items) {
		t.Fatal("denied save changed document")
	}
	// Reauthorizing the same session permits an explicit empty edit; a newly
	// constructed Runtime must read the empty document without a template fallback.
	exec(`UPDATE _System_PermissionSetPermission SET IsEnabled=1 WHERE Id='customize-grant'`)
	exec(`UPDATE _System_PermissionStateVersion SET Revision=Revision+1 WHERE SingletonKey=1`)
	empty, err := save(second, semantic.SaveRequest{ApplicationID: "app", ExpectedRevision: full.Document.Revision, IdempotencyKey: "empty", Items: []semantic.Item{}})
	if err != nil {
		t.Fatal(err)
	}
	fresh := start()
	freshRead := read(fresh)
	if !reflect.DeepEqual(freshRead.Document, empty.Document) || freshRead.Document.Items == nil || len(freshRead.Document.Items) != 0 {
		t.Fatal("fresh Runtime replaced explicit empty navigation")
	}

	// The L1 consumer is a separately built test executable. This keeps L0
	// independent of product modules while proving the actual HTTP -> gRPC ->
	// durable navigation path against the already running Runtime instances.
	if consumer := os.Getenv("NEXUSCRM_NAVIGATION_CONSUMER_TEST_BINARY"); consumer != "" {
		consumerTimeout := 30 * time.Second
		if os.Getenv("NEXUSCRM_NAVIGATION_BROWSER_DONE_FILE") != "" {
			consumerTimeout = 4*time.Minute + 10*time.Second
			if _, err := db.Exec(`UPDATE _System_RuntimeSecurityLease SET ExpiresAt=?`, time.Now().UTC().Add(5*time.Minute)); err != nil {
				t.Fatal(err)
			}
			if err := security.state.Refresh(t.Context()); err != nil {
				t.Fatal(err)
			}
		}
		consumerCtx, stop := context.WithTimeout(t.Context(), consumerTimeout)
		defer stop()
		command := osexec.CommandContext(consumerCtx, consumer, "-test.run=^TestNavigationExternalRuntimeWalkingSlice$", "-test.v", "-test.timeout="+consumerTimeout.String())
		command.Env = append(os.Environ(),
			"NEXUSCRM_NAVIGATION_RUNTIME_ADDRESSES="+strings.Join(addresses, ","),
			"NEXUSCRM_NAVIGATION_TEST_DELEGATION="+mustSignTestDelegation(testClaimsFor(time.Now(), semantic.CapabilityName)),
		)
		var output []byte
		var err error
		if os.Getenv("NEXUSCRM_NAVIGATION_BROWSER_DONE_FILE") != "" {
			command.Stdout, command.Stderr = os.Stdout, os.Stderr
			err = command.Run()
		} else {
			output, err = command.CombinedOutput()
		}
		if err != nil {
			t.Fatalf("external AppService walking slice: %v\n%s", err, output)
		}
		t.Logf("external AppService walking slice:\n%s", output)
	}

}

func navigationTestTables(t *testing.T, db *sql.DB, names ...string) {
	t.Helper()
	definitions, err := schemacatalog.SystemTableDefinitions()
	if err != nil {
		t.Fatal(err)
	}
	repository := persistence.NewSchemaRepository(db, nil)
	for _, name := range names {
		found := false
		for _, definition := range definitions {
			if definition.TableName == name {
				found = true
				if err := repository.CreatePhysicalTable(t.Context(), definition); err != nil {
					t.Fatal(err)
				}
			}
		}
		if !found {
			t.Fatalf("missing canonical table %s", name)
		}
	}
}

type navigationDiagnosticEngine struct {
	*runtimeassembly.ApplicationNavigationRuntime
	t *testing.T
}

func (e navigationDiagnosticEngine) Save(ctx context.Context, caller runtimeassembly.NavigationCaller, request semantic.SaveRequest) (persistence.NavigationMutationResult, error) {
	result, err := e.ApplicationNavigationRuntime.Save(ctx, caller, request)
	if err != nil {
		e.t.Logf("navigation owner save diagnostic: %v", err)
	}
	return result, err
}
