package app

import (
	"context"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"GoNavi-Wails/internal/connection"
	"GoNavi-Wails/internal/jvm"
)

type fakeJVMProvider struct {
	testErr    error
	probe      []jvm.Capability
	probeErr   error
	list       []jvm.ResourceSummary
	listErr    error
	value      jvm.ValueSnapshot
	valueErr   error
	preview    jvm.ChangePreview
	previewSet bool
	previewErr error
	apply      jvm.ApplyResult
	applyErr   error
	applyFn    func(context.Context, connection.ConnectionConfig, jvm.ChangeRequest) (jvm.ApplyResult, error)
	previewReq *jvm.ChangeRequest
	applyReq   *jvm.ChangeRequest
}

func (f fakeJVMProvider) Mode() string { return jvm.ModeJMX }
func (f fakeJVMProvider) TestConnection(context.Context, connection.ConnectionConfig) error {
	return f.testErr
}
func (f fakeJVMProvider) ProbeCapabilities(context.Context, connection.ConnectionConfig) ([]jvm.Capability, error) {
	return f.probe, f.probeErr
}
func (f fakeJVMProvider) ListResources(context.Context, connection.ConnectionConfig, string) ([]jvm.ResourceSummary, error) {
	return f.list, f.listErr
}
func (f fakeJVMProvider) GetValue(context.Context, connection.ConnectionConfig, string) (jvm.ValueSnapshot, error) {
	return f.value, f.valueErr
}
func (f fakeJVMProvider) PreviewChange(_ context.Context, _ connection.ConnectionConfig, req jvm.ChangeRequest) (jvm.ChangePreview, error) {
	if f.previewReq != nil {
		*f.previewReq = req
	}
	if !f.previewSet {
		return jvm.ChangePreview{Allowed: true, Summary: "preview", RiskLevel: "low"}, f.previewErr
	}
	return f.preview, f.previewErr
}
func (f fakeJVMProvider) ApplyChange(ctx context.Context, cfg connection.ConnectionConfig, req jvm.ChangeRequest) (jvm.ApplyResult, error) {
	if f.applyReq != nil {
		*f.applyReq = req
	}
	if f.applyFn != nil {
		return f.applyFn(ctx, cfg, req)
	}
	return f.apply, f.applyErr
}

func swapJVMProviderFactory(factory func(mode string) (jvm.Provider, error)) func() {
	prev := newJVMProvider
	newJVMProvider = factory
	return func() { newJVMProvider = prev }
}

func forceAuditAppendFailureAfterPending(t *testing.T, auditDir string) {
	t.Helper()

	auditPath := filepath.Join(auditDir, "jvm_audit.jsonl")
	if err := os.Remove(auditPath); err != nil && !errors.Is(err, os.ErrNotExist) {
		t.Fatalf("Remove audit file returned error: %v", err)
	}
	if err := os.RemoveAll(auditDir); err != nil {
		t.Fatalf("RemoveAll audit dir returned error: %v", err)
	}
	if err := os.WriteFile(auditDir, []byte("blocker"), 0o600); err != nil {
		t.Fatalf("WriteFile audit dir blocker returned error: %v", err)
	}
}

func TestTestJVMConnectionUsesPreferredProvider(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	var gotMode string
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		gotMode = mode
		return fakeJVMProvider{}, nil
	})
	defer restore()

	res := app.TestJVMConnection(connection.ConnectionConfig{
		Type: "jvm",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			PreferredMode: "endpoint",
			AllowedModes:  []string{"jmx", "endpoint"},
		},
	})

	if !res.Success {
		t.Fatalf("expected success, got %+v", res)
	}
	if gotMode != "endpoint" {
		t.Fatalf("expected provider mode endpoint, got %q", gotMode)
	}
	if res.Message != "JVM 连接成功" {
		t.Fatalf("expected success message %q, got %q", "JVM 连接成功", res.Message)
	}
}

func TestTestJVMConnectionReturnsProviderError(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{testErr: errors.New("dial failed")}, nil
	})
	defer restore()

	res := app.TestJVMConnection(connection.ConnectionConfig{
		Type: "jvm",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	})

	if res.Success {
		t.Fatalf("expected failure, got %+v", res)
	}
	if res.Message != "dial failed" {
		t.Fatalf("expected message %q, got %q", "dial failed", res.Message)
	}
}

func TestTestJVMConnectionTranslatesJMXBusinessPortError(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{testErr: errors.New("jmx test connection failed: jmx helper ping failed for localhost:18080: JMX command ping failed for localhost:18080: Failed to retrieve RMIServer stub: javax.naming.CommunicationException [Root exception is java.rmi.ConnectIOException: non-JRMP server at remote endpoint]; details={\"exception\":\"java.lang.IllegalStateException\"}")}, nil
	})
	defer restore()

	res := app.TestJVMConnection(connection.ConnectionConfig{
		Type: "jvm",
		Host: "localhost",
		Port: 18080,
		JVM: connection.JVMConfig{
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	})

	if res.Success {
		t.Fatalf("expected failure, got %+v", res)
	}
	if !strings.Contains(res.Message, "不是标准 JMX 远程管理端口") {
		t.Fatalf("expected translated summary, got %q", res.Message)
	}
	if !strings.Contains(res.Message, "业务 `server.port`") {
		t.Fatalf("expected actionable suggestion, got %q", res.Message)
	}
	if !strings.Contains(res.Message, "技术细节：") {
		t.Fatalf("expected raw technical detail to be preserved, got %q", res.Message)
	}
}

func TestTestJVMConnectionTranslatesAgentConnectionRefused(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{testErr: errors.New("agent probe request failed: Get \"http://127.0.0.1:19090/gonavi/agent/jvm\": dial tcp 127.0.0.1:19090: connect: connection refused")}, nil
	})
	defer restore()

	res := app.TestJVMConnection(connection.ConnectionConfig{
		Type: "jvm",
		Host: "127.0.0.1",
		JVM: connection.JVMConfig{
			PreferredMode: "agent",
			AllowedModes:  []string{"agent"},
		},
	})

	if res.Success {
		t.Fatalf("expected failure, got %+v", res)
	}
	if !strings.Contains(res.Message, "目标 Agent 管理端口未监听") {
		t.Fatalf("expected translated summary, got %q", res.Message)
	}
	if !strings.Contains(res.Message, "`-javaagent`") {
		t.Fatalf("expected actionable suggestion, got %q", res.Message)
	}
}

func TestTestJVMConnectionReturnsProviderFactoryError(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return nil, errors.New("factory unavailable")
	})
	defer restore()

	res := app.TestJVMConnection(connection.ConnectionConfig{
		Type: "jvm",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			PreferredMode: "endpoint",
			AllowedModes:  []string{"endpoint"},
		},
	})

	if res.Success {
		t.Fatalf("expected failure, got %+v", res)
	}
	if res.Message != "factory unavailable" {
		t.Fatalf("expected message %q, got %q", "factory unavailable", res.Message)
	}
}

func TestJVMProbeCapabilitiesReturnsCapabilityArray(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			probe: []jvm.Capability{{Mode: jvm.ModeJMX, CanBrowse: true, CanWrite: false, CanPreview: false, DisplayLabel: "JMX"}},
		}, nil
	})
	defer restore()

	res := app.JVMProbeCapabilities(connection.ConnectionConfig{
		Type: "jvm",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	})

	if !res.Success {
		t.Fatalf("expected success, got %+v", res)
	}
	items, ok := res.Data.([]jvm.Capability)
	if !ok || len(items) != 1 {
		t.Fatalf("expected one capability, got %#v", res.Data)
	}
}

func TestJVMProbeCapabilitiesIncludesReasonWhenProbeFails(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			probeErr: errors.New("probe failed"),
		}, nil
	})
	defer restore()

	res := app.JVMProbeCapabilities(connection.ConnectionConfig{
		Type: "jvm",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	})

	if !res.Success {
		t.Fatalf("expected success, got %+v", res)
	}
	items, ok := res.Data.([]jvm.Capability)
	if !ok || len(items) != 1 {
		t.Fatalf("expected one capability, got %#v", res.Data)
	}
	if items[0].Reason != "probe failed" {
		t.Fatalf("expected reason %q, got %#v", "probe failed", items[0])
	}
}

func TestJVMProbeCapabilitiesTranslatesJMXProbeErrorUsingCurrentMode(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			probeErr: errors.New("jmx test connection failed: jmx helper ping failed for localhost:18080: JMX command ping failed for localhost:18080: Failed to retrieve RMIServer stub: javax.naming.CommunicationException [Root exception is java.rmi.ConnectIOException: non-JRMP server at remote endpoint]; details={\"exception\":\"java.lang.IllegalStateException\"}"),
		}, nil
	})
	defer restore()

	res := app.JVMProbeCapabilities(connection.ConnectionConfig{
		Type: "jvm",
		Host: "localhost",
		Port: 18080,
		JVM: connection.JVMConfig{
			PreferredMode: "endpoint",
			AllowedModes:  []string{"jmx"},
		},
	})

	if !res.Success {
		t.Fatalf("expected success, got %+v", res)
	}
	items, ok := res.Data.([]jvm.Capability)
	if !ok || len(items) != 1 {
		t.Fatalf("expected one capability, got %#v", res.Data)
	}
	if !strings.Contains(items[0].Reason, "不是标准 JMX 远程管理端口") {
		t.Fatalf("expected translated JMX reason, got %#v", items[0])
	}
}

func TestJVMProbeCapabilitiesIncludesReasonWhenProviderFactoryFails(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return nil, errors.New("provider disabled")
	})
	defer restore()

	res := app.JVMProbeCapabilities(connection.ConnectionConfig{
		Type: "jvm",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			PreferredMode: "endpoint",
			AllowedModes:  []string{"endpoint"},
		},
	})

	if !res.Success {
		t.Fatalf("expected success, got %+v", res)
	}
	items, ok := res.Data.([]jvm.Capability)
	if !ok || len(items) != 1 {
		t.Fatalf("expected one capability, got %#v", res.Data)
	}
	if items[0].Reason != "provider disabled" {
		t.Fatalf("expected reason %q, got %#v", "provider disabled", items[0])
	}
	if items[0].DisplayLabel != "Endpoint" {
		t.Fatalf("expected display label %q, got %#v", "Endpoint", items[0])
	}
}

func TestJVMProbeCapabilitiesUsesReadableLabelForAgentValidationError(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	restore := swapJVMProviderFactory(jvm.NewProvider)
	defer restore()

	res := app.JVMProbeCapabilities(connection.ConnectionConfig{
		Type: "jvm",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			PreferredMode: "agent",
			AllowedModes:  []string{"agent"},
		},
	})

	if !res.Success {
		t.Fatalf("expected success, got %+v", res)
	}
	items, ok := res.Data.([]jvm.Capability)
	if !ok || len(items) != 1 {
		t.Fatalf("expected one capability, got %#v", res.Data)
	}
	if items[0].DisplayLabel != "Agent" {
		t.Fatalf("expected display label %q, got %#v", "Agent", items[0])
	}
	if !strings.Contains(items[0].Reason, "未填写 Agent Base URL") {
		t.Fatalf("expected agent validation error, got %#v", items[0])
	}
}

func TestJVMProbeCapabilitiesUsesReadableLabelForEndpointValidationError(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	restore := swapJVMProviderFactory(jvm.NewProvider)
	defer restore()

	res := app.JVMProbeCapabilities(connection.ConnectionConfig{
		Type: "jvm",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			PreferredMode: "endpoint",
			AllowedModes:  []string{"endpoint"},
		},
	})

	if !res.Success {
		t.Fatalf("expected success, got %+v", res)
	}
	items, ok := res.Data.([]jvm.Capability)
	if !ok || len(items) != 1 {
		t.Fatalf("expected one capability, got %#v", res.Data)
	}
	if items[0].DisplayLabel != "Endpoint" {
		t.Fatalf("expected display label %q, got %#v", "Endpoint", items[0])
	}
	if !strings.Contains(items[0].Reason, "未填写 Endpoint Base URL") {
		t.Fatalf("expected endpoint validation error, got %#v", items[0])
	}
}

func TestJVMListResourcesReturnsProviderPayload(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			list: []jvm.ResourceSummary{
				{
					ID:           "memory.heap",
					Kind:         "folder",
					Name:         "Heap",
					Path:         "/memory/heap",
					ProviderMode: jvm.ModeJMX,
					CanRead:      true,
					HasChildren:  true,
				},
			},
		}, nil
	})
	defer restore()

	res := app.JVMListResources(connection.ConnectionConfig{
		Type: "jvm",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	}, "/memory")

	if !res.Success {
		t.Fatalf("expected success, got %+v", res)
	}
	items, ok := res.Data.([]jvm.ResourceSummary)
	if !ok || len(items) != 1 {
		t.Fatalf("expected one resource summary, got %#v", res.Data)
	}
	if items[0].Path != "/memory/heap" {
		t.Fatalf("expected resource path %q, got %#v", "/memory/heap", items[0])
	}
}

func TestJVMGetValueReturnsProviderPayload(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{
				ResourceID: "memory.heap.used",
				Kind:       "metric",
				Format:     "number",
				Value:      128,
				Metadata: map[string]any{
					"unit": "MiB",
				},
			},
		}, nil
	})
	defer restore()

	res := app.JVMGetValue(connection.ConnectionConfig{
		Type: "jvm",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	}, "/memory/heap/used")

	if !res.Success {
		t.Fatalf("expected success, got %+v", res)
	}
	snapshot, ok := res.Data.(jvm.ValueSnapshot)
	if !ok {
		t.Fatalf("expected value snapshot, got %#v", res.Data)
	}
	if snapshot.ResourceID != "memory.heap.used" {
		t.Fatalf("expected resource id %q, got %#v", "memory.heap.used", snapshot)
	}
	if snapshot.Metadata["unit"] != "MiB" {
		t.Fatalf("expected unit metadata %q, got %#v", "MiB", snapshot.Metadata)
	}
}

func TestJVMApplyChangeRequiresConfirmationTokenForHighRiskPreview(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	app.configDir = t.TempDir()
	readOnly := false
	var applyReq jvm.ChangeRequest
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{
				ResourceID: "/cache/orders",
				Kind:       "entry",
				Format:     "json",
				Value: map[string]any{
					"status": "stale",
				},
			},
			previewSet: true,
			preview: jvm.ChangePreview{
				Allowed:   true,
				Summary:   "risky change",
				RiskLevel: "high",
			},
			applyReq: &applyReq,
			apply: jvm.ApplyResult{
				Status: "applied",
			},
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			ReadOnly:      &readOnly,
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	}, jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload: map[string]any{
			"status": "ready",
		},
	})

	if res.Success {
		t.Fatalf("expected missing confirmation token to fail, got %+v", res)
	}
	if !strings.Contains(res.Message, "确认") && !strings.Contains(res.Message, "重新预览") {
		t.Fatalf("expected confirmation guidance message, got %q", res.Message)
	}
	if applyReq.ResourceID != "" {
		t.Fatalf("expected provider ApplyChange not to run, got %#v", applyReq)
	}
}

func TestJVMApplyChangeReturnsProviderPayload(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	app.configDir = t.TempDir()
	readOnly := false
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{
				ResourceID: "/cache/orders",
				Kind:       "entry",
				Format:     "json",
				Value: map[string]any{
					"status": "stale",
				},
			},
			apply: jvm.ApplyResult{
				Status:  "applied",
				Message: "ok",
				UpdatedValue: jvm.ValueSnapshot{
					ResourceID: "/cache/orders",
					Kind:       "entry",
					Format:     "json",
					Value: map[string]any{
						"status": "ready",
					},
				},
			},
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			ReadOnly:      &readOnly,
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	}, jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload: map[string]any{
			"status": "ready",
		},
	})

	if !res.Success {
		t.Fatalf("expected success, got %+v", res)
	}
	result, ok := res.Data.(jvm.ApplyResult)
	if !ok {
		t.Fatalf("expected apply result, got %#v", res.Data)
	}
	if result.Status != "applied" {
		t.Fatalf("expected status %q, got %#v", "applied", result)
	}
	if result.UpdatedValue.ResourceID != "/cache/orders" {
		t.Fatalf("expected updated resource id %q, got %#v", "/cache/orders", result.UpdatedValue)
	}
}

func TestJVMApplyChangePreviewTokenAllowsConfirmedApply(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	app.configDir = t.TempDir()
	readOnly := false

	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{
				ResourceID: "/cache/orders",
				Kind:       "entry",
				Format:     "json",
				Value: map[string]any{
					"status": "stale",
				},
			},
			previewSet: true,
			preview: jvm.ChangePreview{
				Allowed:              true,
				RequiresConfirmation: true,
				Summary:              "risky change",
				RiskLevel:            "high",
			},
			apply: jvm.ApplyResult{
				Status: "applied",
				UpdatedValue: jvm.ValueSnapshot{
					ResourceID: "/cache/orders",
					Kind:       "entry",
					Format:     "json",
					Value: map[string]any{
						"status": "ready",
					},
				},
			},
		}, nil
	})
	defer restore()

	cfg := connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			Environment:   jvm.EnvPROD,
			ReadOnly:      &readOnly,
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	}

	req := jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload: map[string]any{
			"status": "ready",
		},
	}

	previewRes := app.JVMPreviewChange(cfg, req)
	if !previewRes.Success {
		t.Fatalf("expected preview success, got %+v", previewRes)
	}
	preview, ok := previewRes.Data.(jvm.ChangePreview)
	if !ok {
		t.Fatalf("expected preview payload, got %#v", previewRes.Data)
	}
	if strings.TrimSpace(preview.ConfirmationToken) == "" {
		t.Fatalf("expected confirmation token, got %#v", preview)
	}

	req.ConfirmationToken = preview.ConfirmationToken
	applyRes := app.JVMApplyChange(cfg, req)
	if !applyRes.Success {
		t.Fatalf("expected apply success with confirmation token, got %+v", applyRes)
	}

	listRes := app.JVMListAuditRecords("conn-orders", 10)
	if !listRes.Success {
		t.Fatalf("expected audit list success, got %+v", listRes)
	}
	records, ok := listRes.Data.([]jvm.AuditRecord)
	if !ok {
		t.Fatalf("expected audit record slice, got %#v", listRes.Data)
	}
	var hasPending, hasApplied bool
	for _, record := range records {
		switch record.Result {
		case "pending":
			hasPending = true
		case "applied":
			hasApplied = true
		}
	}
	if !hasPending || !hasApplied {
		t.Fatalf("expected pending+applied records, got %#v", records)
	}
}

func TestJVMApplyChangeRejectsUnissuedDeterministicConfirmationToken(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	app.configDir = t.TempDir()
	readOnly := false
	var applyReq jvm.ChangeRequest

	provider := fakeJVMProvider{
		value: jvm.ValueSnapshot{
			ResourceID: "/cache/orders",
			Kind:       "entry",
			Format:     "json",
			Value: map[string]any{
				"status": "stale",
			},
		},
		previewSet: true,
		preview: jvm.ChangePreview{
			Allowed:              true,
			RequiresConfirmation: true,
			Summary:              "risky change",
			RiskLevel:            "high",
		},
		applyReq: &applyReq,
		apply: jvm.ApplyResult{
			Status: "applied",
		},
	}
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return provider, nil
	})
	defer restore()

	cfg := connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			Environment:   jvm.EnvPROD,
			ReadOnly:      &readOnly,
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	}
	req := jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload: map[string]any{
			"status": "ready",
		},
	}

	preview, err := jvm.BuildChangePreview(context.Background(), provider, cfg, req)
	if err != nil {
		t.Fatalf("BuildChangePreview returned error: %v", err)
	}
	if strings.TrimSpace(preview.ConfirmationToken) == "" {
		t.Fatalf("expected deterministic preview token, got %#v", preview)
	}

	req.ConfirmationToken = preview.ConfirmationToken
	res := app.JVMApplyChange(cfg, req)
	if res.Success {
		t.Fatalf("expected unissued confirmation token to fail, got %+v", res)
	}
	if applyReq.ResourceID != "" {
		t.Fatalf("expected provider ApplyChange not to run, got %#v", applyReq)
	}
}

func TestJVMApplyChangeRejectsReplayedPreviewConfirmationToken(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	app.configDir = t.TempDir()
	readOnly := false
	applyCalls := 0

	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{
				ResourceID: "/cache/orders",
				Kind:       "entry",
				Format:     "json",
				Value: map[string]any{
					"status": "stale",
				},
			},
			previewSet: true,
			preview: jvm.ChangePreview{
				Allowed:              true,
				RequiresConfirmation: true,
				Summary:              "risky change",
				RiskLevel:            "high",
			},
			applyFn: func(context.Context, connection.ConnectionConfig, jvm.ChangeRequest) (jvm.ApplyResult, error) {
				applyCalls++
				return jvm.ApplyResult{Status: "applied"}, nil
			},
		}, nil
	})
	defer restore()

	cfg := connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			Environment:   jvm.EnvPROD,
			ReadOnly:      &readOnly,
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	}
	req := jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload: map[string]any{
			"status": "ready",
		},
	}

	previewRes := app.JVMPreviewChange(cfg, req)
	if !previewRes.Success {
		t.Fatalf("expected preview success, got %+v", previewRes)
	}
	preview, ok := previewRes.Data.(jvm.ChangePreview)
	if !ok {
		t.Fatalf("expected preview payload, got %#v", previewRes.Data)
	}
	req.ConfirmationToken = preview.ConfirmationToken

	firstRes := app.JVMApplyChange(cfg, req)
	if !firstRes.Success {
		t.Fatalf("expected first apply success, got %+v", firstRes)
	}
	secondRes := app.JVMApplyChange(cfg, req)
	if secondRes.Success {
		t.Fatalf("expected replayed confirmation token to fail, got %+v", secondRes)
	}
	if applyCalls != 1 {
		t.Fatalf("expected exactly one provider ApplyChange call, got %d", applyCalls)
	}
}

func TestJVMApplyChangeRejectsExpiredPreviewConfirmationToken(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	app.configDir = t.TempDir()
	app.jvmPreviewTokenTTL = time.Nanosecond
	readOnly := false
	applyCalls := 0

	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{
				ResourceID: "/cache/orders",
				Kind:       "entry",
				Format:     "json",
				Value: map[string]any{
					"status": "stale",
				},
			},
			previewSet: true,
			preview: jvm.ChangePreview{
				Allowed:              true,
				RequiresConfirmation: true,
				Summary:              "risky change",
				RiskLevel:            "high",
			},
			applyFn: func(context.Context, connection.ConnectionConfig, jvm.ChangeRequest) (jvm.ApplyResult, error) {
				applyCalls++
				return jvm.ApplyResult{Status: "applied"}, nil
			},
		}, nil
	})
	defer restore()

	cfg := connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			Environment:   jvm.EnvPROD,
			ReadOnly:      &readOnly,
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	}
	req := jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload: map[string]any{
			"status": "ready",
		},
	}

	previewRes := app.JVMPreviewChange(cfg, req)
	if !previewRes.Success {
		t.Fatalf("expected preview success, got %+v", previewRes)
	}
	preview, ok := previewRes.Data.(jvm.ChangePreview)
	if !ok {
		t.Fatalf("expected preview payload, got %#v", previewRes.Data)
	}
	time.Sleep(time.Millisecond)
	req.ConfirmationToken = preview.ConfirmationToken

	res := app.JVMApplyChange(cfg, req)
	if res.Success {
		t.Fatalf("expected expired confirmation token to fail, got %+v", res)
	}
	if applyCalls != 0 {
		t.Fatalf("expected provider ApplyChange not to run, got %d calls", applyCalls)
	}
}

func TestJVMApplyChangePersistsAuditSource(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	app.configDir = t.TempDir()
	readOnly := false
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{
				ResourceID: "/cache/orders",
				Kind:       "entry",
				Format:     "json",
				Value: map[string]any{
					"status": "stale",
				},
			},
			apply: jvm.ApplyResult{
				Status: "applied",
				UpdatedValue: jvm.ValueSnapshot{
					ResourceID: "/cache/orders",
					Kind:       "entry",
					Format:     "json",
					Value: map[string]any{
						"status": "ready",
					},
				},
			},
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			ReadOnly:      &readOnly,
			PreferredMode: "endpoint",
			AllowedModes:  []string{"endpoint"},
		},
	}, jvm.ChangeRequest{
		ProviderMode: "endpoint",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Source:       "ai-plan",
		Payload: map[string]any{
			"status": "ready",
		},
	})
	if !res.Success {
		t.Fatalf("expected success, got %+v", res)
	}

	listRes := app.JVMListAuditRecords("conn-orders", 10)
	if !listRes.Success {
		t.Fatalf("expected audit list success, got %+v", listRes)
	}
	records, ok := listRes.Data.([]jvm.AuditRecord)
	if !ok || len(records) < 2 {
		t.Fatalf("expected at least two audit records (pending+terminal), got %#v", listRes.Data)
	}
	var hasPending, hasApplied bool
	for _, record := range records {
		if record.Source != "ai-plan" {
			t.Fatalf("expected audit source %q, got %#v", "ai-plan", record)
		}
		switch record.Result {
		case "pending":
			hasPending = true
		case "applied":
			hasApplied = true
		}
	}
	if !hasPending || !hasApplied {
		t.Fatalf("expected pending and applied audit records, got %#v", records)
	}
}

func TestJVMApplyChangeNormalizesRequestBeforeProviderAndAudit(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	app.configDir = t.TempDir()
	readOnly := false
	var previewReq jvm.ChangeRequest
	var applyReq jvm.ChangeRequest
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{
				ResourceID: "/cache/orders",
				Kind:       "entry",
				Format:     "json",
			},
			previewReq: &previewReq,
			applyReq:   &applyReq,
			apply: jvm.ApplyResult{
				Status: "applied",
				UpdatedValue: jvm.ValueSnapshot{
					ResourceID: "/cache/orders",
					Kind:       "entry",
					Format:     "json",
				},
			},
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			ReadOnly:      &readOnly,
			PreferredMode: "endpoint",
			AllowedModes:  []string{"endpoint"},
		},
	}, jvm.ChangeRequest{
		ProviderMode: " endpoint ",
		ResourceID:   " /cache/orders ",
		Action:       " put ",
		Reason:       " repair cache ",
		Source:       " manual ",
		Payload: map[string]any{
			"status": "ready",
		},
	})
	if !res.Success {
		t.Fatalf("expected success, got %+v", res)
	}
	if previewReq.ProviderMode != "endpoint" || previewReq.ResourceID != "/cache/orders" || previewReq.Action != "put" || previewReq.Reason != "repair cache" {
		t.Fatalf("expected normalized preview request, got %#v", previewReq)
	}
	if applyReq.ProviderMode != "endpoint" || applyReq.ResourceID != "/cache/orders" || applyReq.Action != "put" || applyReq.Reason != "repair cache" || applyReq.Source != "manual" {
		t.Fatalf("expected normalized apply request, got %#v", applyReq)
	}

	listRes := app.JVMListAuditRecords("conn-orders", 10)
	if !listRes.Success {
		t.Fatalf("expected audit list success, got %+v", listRes)
	}
	records, ok := listRes.Data.([]jvm.AuditRecord)
	if !ok || len(records) < 2 {
		t.Fatalf("expected at least two audit records (pending+terminal), got %#v", listRes.Data)
	}
	var matchedTerminal bool
	for _, record := range records {
		if record.ProviderMode != "endpoint" || record.ResourceID != "/cache/orders" || record.Action != "put" || record.Reason != "repair cache" || record.Source != "manual" {
			t.Fatalf("expected normalized audit record, got %#v", record)
		}
		if record.Result == "applied" {
			matchedTerminal = true
		}
	}
	if !matchedTerminal {
		t.Fatalf("expected applied terminal audit record, got %#v", records)
	}
}

func TestJVMPreviewChangeRejectsModeOutsideAllowedModes(t *testing.T) {
	app := NewAppWithSecretStore(nil)

	res := app.JVMPreviewChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			PreferredMode: "endpoint",
			AllowedModes:  []string{"endpoint"},
		},
	}, jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
	})

	if res.Success {
		t.Fatalf("expected preview request to be rejected, got %+v", res)
	}
	if !strings.Contains(res.Message, "不允许使用") {
		t.Fatalf("expected disallowed mode error, got %+v", res)
	}
}

func TestJVMListAuditRecordsReturnsLatestRecords(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	app.configDir = t.TempDir()
	store := jvm.NewAuditStore(filepath.Join(app.configDir, "jvm_audit.jsonl"))
	for _, record := range []jvm.AuditRecord{
		{Timestamp: 100, ConnectionID: "conn-orders", ProviderMode: "jmx", ResourceID: "/cache/orders", Action: "put", Reason: "first", Result: "applied"},
		{Timestamp: 200, ConnectionID: "conn-other", ProviderMode: "jmx", ResourceID: "/cache/other", Action: "put", Reason: "other", Result: "applied"},
		{Timestamp: 300, ConnectionID: "conn-orders", ProviderMode: "jmx", ResourceID: "/cache/orders", Action: "put", Reason: "latest", Result: "applied"},
	} {
		if err := store.Append(record); err != nil {
			t.Fatalf("Append returned error: %v", err)
		}
	}

	res := app.JVMListAuditRecords("conn-orders", 1)
	if !res.Success {
		t.Fatalf("expected success, got %+v", res)
	}
	records, ok := res.Data.([]jvm.AuditRecord)
	if !ok {
		t.Fatalf("expected audit record slice, got %#v", res.Data)
	}
	if len(records) != 1 {
		t.Fatalf("expected one audit record, got %#v", records)
	}
	if records[0].Timestamp != 300 {
		t.Fatalf("expected latest timestamp %d, got %#v", 300, records[0])
	}
}

func TestJVMApplyChangeFailsClosedWhenInitialAuditWriteFails(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	tempDir := t.TempDir()
	blockerPath := filepath.Join(tempDir, "audit-blocker")
	if err := os.WriteFile(blockerPath, []byte("blocker"), 0o600); err != nil {
		t.Fatalf("WriteFile returned error: %v", err)
	}
	app.configDir = blockerPath

	readOnly := false
	var applyReq jvm.ChangeRequest
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{
				ResourceID: "/cache/orders",
				Kind:       "entry",
				Format:     "json",
				Value: map[string]any{
					"status": "stale",
				},
			},
			applyReq: &applyReq,
			apply: jvm.ApplyResult{
				Status: "applied",
			},
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			ReadOnly:      &readOnly,
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	}, jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload: map[string]any{
			"status": "ready",
		},
	})

	if res.Success {
		t.Fatalf("expected fail-closed when initial audit write fails, got %+v", res)
	}
	if !strings.Contains(res.Message, "审计") {
		t.Fatalf("expected audit failure message, got %q", res.Message)
	}
	if applyReq.ResourceID != "" {
		t.Fatalf("expected provider ApplyChange not to run, got %#v", applyReq)
	}
}

func TestJVMApplyChangeLatestAuditRecordIsTerminal(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	app.configDir = t.TempDir()
	readOnly := false

	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{
				ResourceID: "/cache/orders",
				Kind:       "entry",
				Format:     "json",
				Value: map[string]any{
					"status": "stale",
				},
			},
			apply: jvm.ApplyResult{Status: "applied"},
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			ReadOnly:      &readOnly,
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	}, jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload: map[string]any{
			"status": "ready",
		},
	})
	if !res.Success {
		t.Fatalf("expected apply success, got %+v", res)
	}

	latestRes := app.JVMListAuditRecords("conn-orders", 1)
	if !latestRes.Success {
		t.Fatalf("expected list success, got %+v", latestRes)
	}
	latestRecords, ok := latestRes.Data.([]jvm.AuditRecord)
	if !ok || len(latestRecords) != 1 {
		t.Fatalf("expected one latest audit record, got %#v", latestRes.Data)
	}
	if latestRecords[0].Result != "applied" {
		t.Fatalf("expected latest record applied, got %#v", latestRecords[0])
	}
}

func TestJVMApplyChangeApplySuccessKeepsSuccessWhenTerminalAuditFails(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	tempDir := t.TempDir()
	auditDir := filepath.Join(tempDir, "audit")
	if err := os.MkdirAll(auditDir, 0o755); err != nil {
		t.Fatalf("MkdirAll returned error: %v", err)
	}
	app.configDir = auditDir

	readOnly := false
	terminalAuditFailed := false
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{
				ResourceID: "/cache/orders",
				Kind:       "entry",
				Format:     "json",
			},
			applyFn: func(_ context.Context, _ connection.ConnectionConfig, _ jvm.ChangeRequest) (jvm.ApplyResult, error) {
				if !terminalAuditFailed {
					terminalAuditFailed = true
					forceAuditAppendFailureAfterPending(t, auditDir)
				}
				return jvm.ApplyResult{Status: "applied", Message: "ok"}, nil
			},
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			ReadOnly:      &readOnly,
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	}, jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload: map[string]any{
			"status": "ready",
		},
	})

	if !res.Success {
		t.Fatalf("expected success when apply succeeded, got %+v", res)
	}
	result, ok := res.Data.(jvm.ApplyResult)
	if !ok {
		t.Fatalf("expected apply result data, got %#v", res.Data)
	}
	if result.Status != "applied" {
		t.Fatalf("expected applied status, got %#v", result)
	}
	if !strings.Contains(result.Message, "终态审计写入失败") {
		t.Fatalf("expected terminal audit warning in result message, got %#v", result)
	}
}

func TestJVMApplyChangeApplyFailureReportsFailedAuditWriteError(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	tempDir := t.TempDir()
	auditDir := filepath.Join(tempDir, "audit")
	if err := os.MkdirAll(auditDir, 0o755); err != nil {
		t.Fatalf("MkdirAll returned error: %v", err)
	}
	app.configDir = auditDir

	readOnly := false
	failedAuditBlocked := false
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{
				ResourceID: "/cache/orders",
				Kind:       "entry",
				Format:     "json",
			},
			applyFn: func(_ context.Context, _ connection.ConnectionConfig, _ jvm.ChangeRequest) (jvm.ApplyResult, error) {
				if !failedAuditBlocked {
					failedAuditBlocked = true
					forceAuditAppendFailureAfterPending(t, auditDir)
				}
				return jvm.ApplyResult{}, errors.New("provider apply failed")
			},
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			ReadOnly:      &readOnly,
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	}, jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload: map[string]any{
			"status": "ready",
		},
	})

	if res.Success {
		t.Fatalf("expected failure when apply fails, got %+v", res)
	}
	if !strings.Contains(res.Message, "provider apply failed") {
		t.Fatalf("expected provider failure in message, got %q", res.Message)
	}
	if !strings.Contains(res.Message, "失败审计写入失败") {
		t.Fatalf("expected failed audit write failure in message, got %q", res.Message)
	}
}

func TestJVMApplyChangeApplyFailureKeepsProviderErrorWhenFailedAuditSucceeds(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	app.configDir = t.TempDir()
	readOnly := false

	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{
				ResourceID: "/cache/orders",
				Kind:       "entry",
				Format:     "json",
			},
			applyErr: errors.New("provider apply failed"),
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			ReadOnly:      &readOnly,
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	}, jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload: map[string]any{
			"status": "ready",
		},
	})

	if res.Success {
		t.Fatalf("expected failure when provider apply fails, got %+v", res)
	}
	if res.Message != "provider apply failed" {
		t.Fatalf("expected provider failure only when failed audit succeeds, got %q", res.Message)
	}
}

func TestJVMApplyChangeUsesProviderErrorWhenFailedAuditAlsoFails(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	tempDir := t.TempDir()
	auditDir := filepath.Join(tempDir, "audit")
	if err := os.MkdirAll(auditDir, 0o755); err != nil {
		t.Fatalf("MkdirAll returned error: %v", err)
	}
	app.configDir = auditDir

	readOnly := false
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{
				ResourceID: "/cache/orders",
				Kind:       "entry",
				Format:     "json",
			},
			applyFn: func(_ context.Context, _ connection.ConnectionConfig, _ jvm.ChangeRequest) (jvm.ApplyResult, error) {
				forceAuditAppendFailureAfterPending(t, auditDir)
				return jvm.ApplyResult{}, errors.New("provider apply failed")
			},
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM: connection.JVMConfig{
			ReadOnly:      &readOnly,
			PreferredMode: "jmx",
			AllowedModes:  []string{"jmx"},
		},
	}, jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload: map[string]any{
			"status": "ready",
		},
	})

	if res.Success {
		t.Fatalf("expected failure when provider apply fails, got %+v", res)
	}
	if !strings.HasPrefix(res.Message, "provider apply failed") {
		t.Fatalf("expected provider error prefix, got %q", res.Message)
	}
	if !strings.Contains(res.Message, "失败审计写入失败") {
		t.Fatalf("expected failed audit write failure in message, got %q", res.Message)
	}
}

func TestJVMApplyChangeTerminalAuditWarningAppendsToExistingResultMessage(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	tempDir := t.TempDir()
	auditDir := filepath.Join(tempDir, "audit")
	if err := os.MkdirAll(auditDir, 0o755); err != nil {
		t.Fatalf("MkdirAll returned error: %v", err)
	}
	app.configDir = auditDir

	readOnly := false
	terminalAuditFailed := false
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{ResourceID: "/cache/orders", Kind: "entry", Format: "json"},
			applyFn: func(_ context.Context, _ connection.ConnectionConfig, _ jvm.ChangeRequest) (jvm.ApplyResult, error) {
				if !terminalAuditFailed {
					terminalAuditFailed = true
					forceAuditAppendFailureAfterPending(t, auditDir)
				}
				return jvm.ApplyResult{Status: "applied", Message: "provider message"}, nil
			},
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM:  connection.JVMConfig{ReadOnly: &readOnly, PreferredMode: "jmx", AllowedModes: []string{"jmx"}},
	}, jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload:      map[string]any{"status": "ready"},
	})
	if !res.Success {
		t.Fatalf("expected success when apply succeeded, got %+v", res)
	}
	result, ok := res.Data.(jvm.ApplyResult)
	if !ok {
		t.Fatalf("expected apply result data, got %#v", res.Data)
	}
	if !strings.Contains(result.Message, "provider message") || !strings.Contains(result.Message, "终态审计写入失败") {
		t.Fatalf("expected provider message with terminal audit warning, got %#v", result)
	}
}

func TestJVMApplyChangeTerminalAuditWarningUsesStandaloneMessageWhenResultMessageEmpty(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	tempDir := t.TempDir()
	auditDir := filepath.Join(tempDir, "audit")
	if err := os.MkdirAll(auditDir, 0o755); err != nil {
		t.Fatalf("MkdirAll returned error: %v", err)
	}
	app.configDir = auditDir

	readOnly := false
	terminalAuditFailed := false
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{ResourceID: "/cache/orders", Kind: "entry", Format: "json"},
			applyFn: func(_ context.Context, _ connection.ConnectionConfig, _ jvm.ChangeRequest) (jvm.ApplyResult, error) {
				if !terminalAuditFailed {
					terminalAuditFailed = true
					forceAuditAppendFailureAfterPending(t, auditDir)
				}
				return jvm.ApplyResult{Status: "applied"}, nil
			},
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM:  connection.JVMConfig{ReadOnly: &readOnly, PreferredMode: "jmx", AllowedModes: []string{"jmx"}},
	}, jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload:      map[string]any{"status": "ready"},
	})
	if !res.Success {
		t.Fatalf("expected success when apply succeeded, got %+v", res)
	}
	result, ok := res.Data.(jvm.ApplyResult)
	if !ok {
		t.Fatalf("expected apply result data, got %#v", res.Data)
	}
	if !strings.Contains(result.Message, "终态审计写入失败") {
		t.Fatalf("expected standalone terminal audit warning, got %#v", result)
	}
}

func TestJVMApplyChangeFailedAuditFailureMessageIncludesUnderlyingError(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	tempDir := t.TempDir()
	auditDir := filepath.Join(tempDir, "audit")
	if err := os.MkdirAll(auditDir, 0o755); err != nil {
		t.Fatalf("MkdirAll returned error: %v", err)
	}
	app.configDir = auditDir

	readOnly := false
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{ResourceID: "/cache/orders", Kind: "entry", Format: "json"},
			applyFn: func(_ context.Context, _ connection.ConnectionConfig, _ jvm.ChangeRequest) (jvm.ApplyResult, error) {
				forceAuditAppendFailureAfterPending(t, auditDir)
				return jvm.ApplyResult{}, errors.New("provider apply failed")
			},
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM:  connection.JVMConfig{ReadOnly: &readOnly, PreferredMode: "jmx", AllowedModes: []string{"jmx"}},
	}, jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload:      map[string]any{"status": "ready"},
	})
	if res.Success {
		t.Fatalf("expected failure when apply fails, got %+v", res)
	}
	if !strings.Contains(res.Message, "失败审计写入失败") {
		t.Fatalf("expected failed audit failure marker, got %q", res.Message)
	}
	lowerMessage := strings.ToLower(res.Message)
	if !strings.Contains(lowerMessage, "not a directory") && !strings.Contains(lowerMessage, "system cannot find the path specified") {
		t.Fatalf("expected underlying audit failure detail in message, got %q", res.Message)
	}
}

func TestJVMApplyChangeFailureMessageSeparatorUsesChineseSemicolon(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	tempDir := t.TempDir()
	auditDir := filepath.Join(tempDir, "audit")
	if err := os.MkdirAll(auditDir, 0o755); err != nil {
		t.Fatalf("MkdirAll returned error: %v", err)
	}
	app.configDir = auditDir

	readOnly := false
	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{ResourceID: "/cache/orders", Kind: "entry", Format: "json"},
			applyFn: func(_ context.Context, _ connection.ConnectionConfig, _ jvm.ChangeRequest) (jvm.ApplyResult, error) {
				forceAuditAppendFailureAfterPending(t, auditDir)
				return jvm.ApplyResult{}, errors.New("provider apply failed")
			},
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM:  connection.JVMConfig{ReadOnly: &readOnly, PreferredMode: "jmx", AllowedModes: []string{"jmx"}},
	}, jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload:      map[string]any{"status": "ready"},
	})
	if res.Success {
		t.Fatalf("expected failure when apply fails, got %+v", res)
	}
	if !strings.Contains(res.Message, "；失败审计写入失败") {
		t.Fatalf("expected chinese semicolon separator in failure message, got %q", res.Message)
	}
}

func TestJVMApplyChangeLatestAuditRecordIsFailedWhenApplyFails(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	app.configDir = t.TempDir()
	readOnly := false

	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value:    jvm.ValueSnapshot{ResourceID: "/cache/orders", Kind: "entry", Format: "json"},
			applyErr: errors.New("provider apply failed"),
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM:  connection.JVMConfig{ReadOnly: &readOnly, PreferredMode: "jmx", AllowedModes: []string{"jmx"}},
	}, jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload:      map[string]any{"status": "ready"},
	})
	if res.Success {
		t.Fatalf("expected apply failure, got %+v", res)
	}

	latestRes := app.JVMListAuditRecords("conn-orders", 10)
	if !latestRes.Success {
		t.Fatalf("expected list success, got %+v", latestRes)
	}
	records, ok := latestRes.Data.([]jvm.AuditRecord)
	if !ok {
		t.Fatalf("expected records slice, got %#v", latestRes.Data)
	}
	if len(records) < 2 {
		t.Fatalf("expected at least pending and failed records, got %#v", records)
	}
	if records[0].Result != "failed" {
		t.Fatalf("expected latest record failed, got %#v", records[0])
	}

	var pendingTs, failedTs int64
	for _, record := range records {
		switch record.Result {
		case "pending":
			pendingTs = record.Timestamp
		case "failed":
			failedTs = record.Timestamp
		}
	}
	if pendingTs == 0 || failedTs == 0 {
		t.Fatalf("expected pending and failed records, got %#v", records)
	}
	if failedTs <= pendingTs {
		t.Fatalf("expected failed timestamp > pending timestamp, pending=%d failed=%d records=%#v", pendingTs, failedTs, records)
	}
}

func TestJVMApplyChangePendingAndTerminalAuditTimestampsAreStrictlyIncreasing(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	app.configDir = t.TempDir()
	readOnly := false

	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{ResourceID: "/cache/orders", Kind: "entry", Format: "json"},
			apply: jvm.ApplyResult{Status: "applied"},
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM:  connection.JVMConfig{ReadOnly: &readOnly, PreferredMode: "jmx", AllowedModes: []string{"jmx"}},
	}, jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache",
		Payload:      map[string]any{"status": "ready"},
	})
	if !res.Success {
		t.Fatalf("expected apply success, got %+v", res)
	}

	listRes := app.JVMListAuditRecords("conn-orders", 10)
	if !listRes.Success {
		t.Fatalf("expected list success, got %+v", listRes)
	}
	records, ok := listRes.Data.([]jvm.AuditRecord)
	if !ok {
		t.Fatalf("expected records slice, got %#v", listRes.Data)
	}
	var pendingTs, terminalTs int64
	for _, record := range records {
		switch record.Result {
		case "pending":
			pendingTs = record.Timestamp
		case "applied":
			terminalTs = record.Timestamp
		}
	}
	if pendingTs == 0 || terminalTs == 0 {
		t.Fatalf("expected pending and applied records, got %#v", records)
	}
	if terminalTs <= pendingTs {
		t.Fatalf("expected terminal timestamp > pending timestamp, pending=%d terminal=%d records=%#v", pendingTs, terminalTs, records)
	}
}

func TestJVMApplyChangeTerminalAuditTimestampReflectsApplyCompletion(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	app.configDir = t.TempDir()
	readOnly := false

	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{ResourceID: "/cache/orders", Kind: "entry", Format: "json"},
			applyFn: func(_ context.Context, _ connection.ConnectionConfig, _ jvm.ChangeRequest) (jvm.ApplyResult, error) {
				time.Sleep(15 * time.Millisecond)
				return jvm.ApplyResult{Status: "applied"}, nil
			},
		}, nil
	})
	defer restore()

	res := app.JVMApplyChange(connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM:  connection.JVMConfig{ReadOnly: &readOnly, PreferredMode: "jmx", AllowedModes: []string{"jmx"}},
	}, jvm.ChangeRequest{
		ProviderMode: "jmx",
		ResourceID:   "/cache/orders",
		Action:       "put",
		Reason:       "repair cache delayed",
		Payload:      map[string]any{"status": "ready"},
	})
	if !res.Success {
		t.Fatalf("expected apply success, got %+v", res)
	}

	listRes := app.JVMListAuditRecords("conn-orders", 10)
	if !listRes.Success {
		t.Fatalf("expected list success, got %+v", listRes)
	}
	records, ok := listRes.Data.([]jvm.AuditRecord)
	if !ok {
		t.Fatalf("expected records slice, got %#v", listRes.Data)
	}
	var pendingTs, terminalTs int64
	for _, record := range records {
		if record.Reason != "repair cache delayed" {
			continue
		}
		switch record.Result {
		case "pending":
			pendingTs = record.Timestamp
		case "applied":
			terminalTs = record.Timestamp
		}
	}
	if pendingTs == 0 || terminalTs == 0 {
		t.Fatalf("expected pending and applied records for delayed apply, got %#v", records)
	}
	if terminalTs <= pendingTs+1 {
		t.Fatalf("expected delayed terminal timestamp to be strictly greater than pending+1, pending=%d terminal=%d records=%#v", pendingTs, terminalTs, records)
	}
}

func TestJVMApplyChangeTimestampGuaranteeHoldsAcrossMultipleApplies(t *testing.T) {
	app := NewAppWithSecretStore(nil)
	app.configDir = t.TempDir()
	readOnly := false

	restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
		return fakeJVMProvider{
			value: jvm.ValueSnapshot{ResourceID: "/cache/orders", Kind: "entry", Format: "json"},
			apply: jvm.ApplyResult{Status: "applied"},
		}, nil
	})
	defer restore()

	cfg := connection.ConnectionConfig{
		Type: "jvm",
		ID:   "conn-orders",
		Host: "orders.internal",
		JVM:  connection.JVMConfig{ReadOnly: &readOnly, PreferredMode: "jmx", AllowedModes: []string{"jmx"}},
	}
	for i := 0; i < 3; i++ {
		res := app.JVMApplyChange(cfg, jvm.ChangeRequest{
			ProviderMode: "jmx",
			ResourceID:   "/cache/orders",
			Action:       "put",
			Reason:       fmt.Sprintf("repair cache %d", i),
			Payload:      map[string]any{"status": "ready"},
		})
		if !res.Success {
			t.Fatalf("apply %d expected success, got %+v", i, res)
		}
	}

	listRes := app.JVMListAuditRecords("conn-orders", 20)
	if !listRes.Success {
		t.Fatalf("expected list success, got %+v", listRes)
	}
	records, ok := listRes.Data.([]jvm.AuditRecord)
	if !ok {
		t.Fatalf("expected records slice, got %#v", listRes.Data)
	}
	reasonLatestTs := map[string]int64{}
	for _, record := range records {
		if strings.HasPrefix(record.Reason, "repair cache ") {
			reasonLatestTs[record.Reason+":"+record.Result] = record.Timestamp
		}
	}
	for i := 0; i < 3; i++ {
		reason := fmt.Sprintf("repair cache %d", i)
		pendingTs := reasonLatestTs[reason+":pending"]
		appliedTs := reasonLatestTs[reason+":applied"]
		if pendingTs == 0 || appliedTs == 0 {
			t.Fatalf("expected pending+applied for %s, got %#v", reason, records)
		}
		if appliedTs <= pendingTs {
			t.Fatalf("expected applied ts > pending ts for %s, pending=%d applied=%d", reason, pendingTs, appliedTs)
		}
	}
}
