package license

import (
	"context"
	"crypto/ed25519"
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"strings"
	"testing"
	"time"
)

const (
	testInstanceID = "550e8400-e29b-41d4-a716-446655440000"
	testBackendURL = "https://finance.example.com"
)

type fakeRemote struct {
	calls           []string
	detail          Detail
	detailErr       error
	registration    Registration
	query           Query
	queryRaw        []byte
	querySignature  string
	queryErr        error
	heartbeatResult *HeartbeatResult
	heartbeatErr    error
	heartbeatErrs   []error
	publicKey       []byte
	publicKeyErr    error
}

func (f *fakeRemote) Detail(context.Context, string) (Detail, error) {
	f.calls = append(f.calls, "detail")
	return f.detail, f.detailErr
}

func (f *fakeRemote) Register(_ context.Context, input RegisterRequest) (Registration, error) {
	f.calls = append(f.calls, "register")
	if input.InstanceID != testInstanceID || input.InstanceName == "" || input.NodeRole != "master" || input.SystemName == "" || input.SystemArch == "" {
		return Registration{}, errors.New("invalid registration metadata")
	}
	return f.registration, nil
}

func (f *fakeRemote) Query(context.Context, string) (Query, error) {
	f.calls = append(f.calls, "query")
	return f.query, nil
}

func (f *fakeRemote) QueryRaw(_ context.Context, _ string, instanceID, token, backendURL string) (Query, []byte, string, error) {
	f.calls = append(f.calls, "query")
	if instanceID != testInstanceID {
		return Query{}, nil, "", errors.New("invalid query instance id")
	}
	if strings.TrimSpace(token) == "" {
		return Query{}, nil, "", errors.New("missing query instance token")
	}
	_ = backendURL
	query := f.query
	if query.Status == "" {
		query.Status = "active"
	}
	if query.ExpiresAt.IsZero() {
		query.ExpiresAt = time.Now().UTC().Add(24 * time.Hour)
	}
	if query.LicenseNo == "" {
		query.LicenseNo = "LIC-TEST"
	}
	if query.InstanceID == "" {
		query.InstanceID = testInstanceID
	}
	if query.Sequence == 0 {
		query.Sequence = 1
	}
	if len(query.Features) == 0 {
		query.Features = []string{FeatureHTTP, FeatureWorker, FeaturePlugins}
	}
	return query, f.queryRaw, f.querySignature, f.queryErr
}

func (f *fakeRemote) Heartbeat(context.Context, string, HeartbeatRequest) (*HeartbeatResult, error) {
	f.calls = append(f.calls, "heartbeat")
	if len(f.heartbeatErrs) > 0 {
		err := f.heartbeatErrs[0]
		f.heartbeatErrs = f.heartbeatErrs[1:]
		if err != nil {
			return nil, err
		}
	}
	if f.heartbeatResult == nil && f.heartbeatErr == nil {
		return &HeartbeatResult{
			Status:     "active",
			LicenseNo:  "LIC-TEST",
			InstanceID: testInstanceID,
			Sequence:   2,
			Features:   []string{FeatureHTTP, FeatureWorker, FeaturePlugins},
			RawBody:    []byte(`{"status":"active"}`),
			Signature:  "SIGN-test",
		}, nil
	}
	return f.heartbeatResult, f.heartbeatErr
}

func (f *fakeRemote) GetPublicKey(context.Context) ([]byte, error) {
	f.calls = append(f.calls, "getPublicKey")
	return f.publicKey, f.publicKeyErr
}

type fakeProber struct {
	result ProbeResult
}

func (f fakeProber) Probe(_ context.Context) ProbeResult {
	return f.result
}

type fakeStateStore struct {
	state               State
	saved               State
	markHeartbeat       time.Time
	beforeHeartbeatSave func(State)
	saveHeartbeatCalls  int
	saveActivationCalls int
}

func (f *fakeStateStore) Load(context.Context) (State, error) {
	return f.state, nil
}

func (f *fakeStateStore) EnsureIdentity(_ context.Context, identity Identity) (Identity, error) {
	identity.InstanceID = testInstanceID
	return identity, nil
}

func (f *fakeStateStore) SaveActivation(_ context.Context, state State) error {
	f.saveActivationCalls++
	f.saved = state
	f.state = state
	return nil
}

func (f *fakeStateStore) SaveHeartbeat(_ context.Context, state State) error {
	f.saveHeartbeatCalls++
	if f.beforeHeartbeatSave != nil {
		f.beforeHeartbeatSave(state)
	}
	f.markHeartbeat = state.LastHeartbeatAt
	f.state = state
	return nil
}

func TestManagerReportAppliesFeatureRemovalBeforePersistence(t *testing.T) {
	now := time.Now().UTC()
	publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	sign := func(sequence uint64, features []string) ([]byte, string) {
		payload, marshalErr := json.Marshal(evaluatorLeasePayload{
			Status:     "active",
			ValidUntil: now.Add(time.Hour).Format(time.RFC3339),
			ExpiresAt:  now.Add(24 * time.Hour).Format(time.RFC3339),
			License:    "AUTH-TEST",
			LicenseNo:  "LIC-TEST",
			KeyID:      "test-key",
			InstanceID: testInstanceID,
			Sequence:   sequence,
			Features:   features,
			IssuedAt:   now.Unix(),
		})
		if marshalErr != nil {
			t.Fatal(marshalErr)
		}
		signature := "SIGN-" + base64.RawURLEncoding.EncodeToString(ed25519.Sign(privateKey, payload))
		raw := append(append([]byte(nil), payload[:len(payload)-1]...), []byte(`,"signature":"`+signature+`"}`)...)
		return raw, signature
	}
	oldRaw, oldSignature := sign(10, []string{FeatureHTTP, FeatureWorker, FeaturePlugins})
	newRaw, newSignature := sign(11, []string{FeatureHTTP, FeatureWorker})
	eval := NewEvaluator(publicKey)
	if err := eval.LoadFromQuery(oldRaw, oldSignature); err != nil {
		t.Fatal(err)
	}
	state := testCredentialState()
	state.LeaseRaw = oldRaw
	state.LeaseSignature = oldSignature
	state.LeaseSequence = 10
	state.LeaseScope = "instance:LIC-TEST:" + testInstanceID
	state.Features = []string{FeatureHTTP, FeatureWorker, FeaturePlugins}
	store := &fakeStateStore{state: state}
	store.beforeHeartbeatSave = func(State) {
		if err := eval.CheckFeature(FeaturePlugins); !errors.Is(err, ErrLicenseFeatureMissing) {
			t.Fatalf("plugins remained enabled while persisting restricted lease: %v", err)
		}
	}
	remote := &fakeRemote{heartbeatResult: &HeartbeatResult{
		Status:     "active",
		ValidUntil: now.Add(time.Hour),
		ExpiresAt:  now.Add(24 * time.Hour),
		LicenseNo:  "LIC-TEST",
		KeyID:      "test-key",
		InstanceID: testInstanceID,
		Sequence:   11,
		Features:   []string{FeatureHTTP, FeatureWorker},
		IssuedAt:   now,
		RawBody:    newRaw,
		Signature:  newSignature,
	}}
	manager := NewManager(remote, store, "1.0").WithEvaluator(eval)
	manager.now = func() time.Time { return now }
	manager.hostname = func() (string, error) { return "production-01", nil }
	if err := manager.Report(context.Background()); err != nil {
		t.Fatalf("Report: %v", err)
	}
}

func TestManagerActivateSupportsServerWithoutDetailEndpoint(t *testing.T) {
	now := time.Date(2026, 7, 10, 12, 0, 0, 0, time.UTC)
	remote := &fakeRemote{
		detailErr:    &HTTPError{StatusCode: 404},
		registration: Registration{Type: "standard", Version: "0.10", InstanceToken: "inst_test", ExpiresAt: now.Add(time.Hour)},
		query:        Query{Authorized: true, Version: "0.10", LicenseNo: "LIC-TEST"},
	}
	store := &fakeStateStore{}
	manager := NewManager(remote, store, "0.1.0")
	manager.now = func() time.Time { return now }
	manager.hostname = func() (string, error) { return "production-01", nil }

	if err := manager.Activate(context.Background(), "AUTH-TEST", ""); err != nil {
		t.Fatalf("Activate: %v", err)
	}
	if !store.saved.Configured() {
		t.Fatalf("saved state = %#v", store.saved)
	}
}

func TestManagerActivateSupportsLegacyRegistrationWithoutExpiry(t *testing.T) {
	now := time.Date(2026, 7, 10, 12, 0, 0, 0, time.UTC)
	remote := &fakeRemote{
		detail:       Detail{Valid: true, Type: "standard", Version: "0.10"},
		registration: Registration{InstanceToken: "inst_test"},
		query:        Query{Authorized: true, Version: "0.10", LicenseNo: "LIC-TEST"},
	}
	store := &fakeStateStore{}
	manager := NewManager(remote, store, "0.1.0")
	manager.now = func() time.Time { return now }
	manager.hostname = func() (string, error) { return "production-01", nil }

	if err := manager.Activate(context.Background(), "AUTH-TEST", ""); err != nil {
		t.Fatalf("Activate: %v", err)
	}
	if !store.saved.TokenExpiresAt.Equal(now.Add(legacyTokenRefreshInterval)) {
		t.Fatalf("token expiry = %v", store.saved.TokenExpiresAt)
	}
}

func TestManagerActivateCompletesBindingBeforePersisting(t *testing.T) {
	now := time.Date(2026, 7, 10, 12, 0, 0, 0, time.UTC)
	remote := &fakeRemote{
		detail: Detail{Valid: true, Type: "standard", Version: "0.10"},
		registration: Registration{
			Type:          "standard",
			Version:       "0.10",
			InstanceToken: "inst_test",
			ExpiresAt:     now.Add(24 * time.Hour),
		},
		query: Query{Authorized: true, Version: "0.10", LicenseNo: "LIC-TEST"},
	}
	store := &fakeStateStore{}
	manager := NewManager(remote, store, "0.1.0")
	manager.now = func() time.Time { return now }
	manager.hostname = func() (string, error) { return "production-01", nil }

	if err := manager.Activate(context.Background(), " AUTH-TEST ", "example.com:8080"); err != nil {
		t.Fatalf("Activate: %v", err)
	}
	if want := []string{"detail", "register", "query", "heartbeat"}; !reflect.DeepEqual(remote.calls, want) {
		t.Fatalf("calls = %#v, want %#v", remote.calls, want)
	}
	if !store.saved.Configured() || store.saved.Code != "AUTH-TEST" {
		t.Fatalf("saved state = %#v", store.saved)
	}
}

func TestManagerActivateDoesNotPersistWhenQueryRejects(t *testing.T) {
	now := time.Date(2026, 7, 10, 12, 0, 0, 0, time.UTC)
	remote := &fakeRemote{
		detail:       Detail{Valid: true, Type: "standard", Version: "0.10"},
		registration: Registration{Type: "standard", Version: "0.10", InstanceToken: "inst_test", ExpiresAt: now.Add(time.Hour)},
		query:        Query{Authorized: false},
	}
	store := &fakeStateStore{}
	manager := NewManager(remote, store, "0.1.0")
	manager.now = func() time.Time { return now }
	manager.hostname = func() (string, error) { return "production-01", nil }

	err := manager.Activate(context.Background(), "AUTH-TEST", "")
	if !errors.Is(err, ErrUnavailable) {
		t.Fatalf("error = %v", err)
	}
	if store.saved.Code != "" {
		t.Fatalf("unexpected saved state = %#v", store.saved)
	}
}

func TestManagerReportDoesNotReregisterRejectedToken(t *testing.T) {
	now := time.Date(2026, 7, 10, 12, 0, 0, 0, time.UTC)
	remote := &fakeRemote{
		heartbeatErrs: []error{&HTTPError{StatusCode: 401}},
	}
	store := &fakeStateStore{state: State{
		Code:           "AUTH-TEST",
		VerifiedAt:     now.Add(-time.Hour),
		Identity:       Identity{InstanceID: testInstanceID, InstanceName: "production-01", NodeRole: "master", SystemName: "linux", SystemArch: "amd64"},
		InstanceToken:  "inst_old",
		TokenExpiresAt: now.Add(24 * time.Hour),
	}}
	manager := NewManager(remote, store, "0.1.0")
	manager.now = func() time.Time { return now }
	manager.hostname = func() (string, error) { return "production-01", nil }

	if err := manager.Report(context.Background()); !errors.Is(err, ErrReactivationRequired) {
		t.Fatalf("Report error = %v", err)
	}
	if want := []string{"heartbeat"}; !reflect.DeepEqual(remote.calls, want) {
		t.Fatalf("calls = %#v, want %#v", remote.calls, want)
	}
}

func TestManagerReportBlocksOnQuorumRejection(t *testing.T) {
	now := time.Date(2026, 7, 10, 12, 0, 0, 0, time.UTC)
	store := &fakeStateStore{state: State{
		Code:            "AUTH-TEST",
		VerifiedAt:      now.Add(-time.Hour),
		Identity:        Identity{InstanceID: testInstanceID, InstanceName: "production-01", NodeRole: "master", SystemName: "linux", SystemArch: "amd64", BackendURL: testBackendURL},
		InstanceToken:   "inst_test",
		TokenExpiresAt:  now.Add(24 * time.Hour),
		LastHeartbeatAt: now.Add(-time.Hour),
	}}
	remote := &fakeRemote{heartbeatErr: &QuorumRejectionError{
		Rejections: 2,
		Required:   2,
		Total:      3,
		Last:       &HTTPError{StatusCode: 403, Code: "DOMAIN_MISMATCH"},
	}}
	eval := NewEvaluator(nil)
	manager := NewManager(remote, store, "1.0").WithEvaluator(eval)
	manager.now = func() time.Time { return now }
	manager.hostname = func() (string, error) { return "production-01", nil }

	reportErr := manager.Report(context.Background())
	if reportErr == nil {
		t.Fatal("expected quorum rejection error")
	}
	snapshot := eval.snapshot.Load()
	if snapshot == nil || !snapshot.forceBlock || snapshot.status != "domain_mismatch" {
		t.Fatalf("expected domain mismatch force block; snapshot = %#v; report error = %v; calls = %#v", snapshot, reportErr, remote.calls)
	}
}

func TestManagerReportMarksSuccessfulHeartbeat(t *testing.T) {
	now := time.Date(2026, 7, 10, 12, 0, 0, 0, time.UTC)
	remote := &fakeRemote{}
	store := &fakeStateStore{state: State{
		Code:            "AUTH-TEST",
		VerifiedAt:      now.Add(-time.Hour),
		Identity:        Identity{InstanceID: testInstanceID, InstanceName: "production-01", NodeRole: "master", SystemName: "linux", SystemArch: "amd64"},
		InstanceToken:   "inst_test",
		TokenExpiresAt:  now.Add(24 * time.Hour),
		LastHeartbeatAt: now.Add(-time.Hour),
	}}
	manager := NewManager(remote, store, "0.1.0")
	manager.now = func() time.Time { return now }
	manager.hostname = func() (string, error) { return "production-01", nil }

	if err := manager.Report(context.Background()); err != nil {
		t.Fatalf("Report: %v", err)
	}
	if want := []string{"heartbeat"}; !reflect.DeepEqual(remote.calls, want) {
		t.Fatalf("calls = %#v, want %#v", remote.calls, want)
	}
	if !store.markHeartbeat.Equal(now) {
		t.Fatalf("heartbeat time = %v", store.markHeartbeat)
	}
}

func TestManagerSuspendedHeartbeatKeepsInstanceToken(t *testing.T) {
	now := time.Now().UTC()
	_, raw, signature := signedEvaluatorLease(t, "suspended", []string{FeatureHTTP}, now.Add(time.Hour), now.Add(24*time.Hour))
	state := testCredentialState()
	state.TokenExpiresAt = now.Add(24 * time.Hour)
	store := &fakeStateStore{state: state}
	remote := &fakeRemote{heartbeatResult: &HeartbeatResult{
		Status:     "suspended",
		ValidUntil: now.Add(time.Hour),
		ExpiresAt:  now.Add(24 * time.Hour),
		LicenseNo:  "LIC-TEST",
		InstanceID: testInstanceID,
		Sequence:   7,
		Features:   []string{FeatureHTTP},
		RawBody:    raw,
		Signature:  signature,
	}}
	manager := NewManager(remote, store, "1.0")
	manager.now = func() time.Time { return now }
	manager.hostname = func() (string, error) { return "production-01", nil }
	if err := manager.Report(context.Background()); err != nil {
		t.Fatalf("Report: %v", err)
	}
	if store.state.InstanceToken != state.InstanceToken {
		t.Fatal("suspended heartbeat removed instance token")
	}
	if status := persistedLeaseStatus(store.state); status != "suspended" {
		t.Fatalf("stored status = %q", status)
	}
}

func TestManagerRejectedRevokedTokenStaysBlocked(t *testing.T) {
	now := time.Now().UTC()
	publicKey, raw, signature := signedEvaluatorLease(t, "revoked", []string{FeatureHTTP}, now.Add(time.Hour), now.Add(24*time.Hour))
	eval := NewEvaluator(publicKey)
	if err := eval.LoadFromQuery(raw, signature); err != nil {
		t.Fatal(err)
	}
	state := testCredentialState()
	state.LeaseRaw = raw
	state.LeaseSignature = signature
	state.LeaseScope = "instance:LIC-TEST:" + testInstanceID
	state.LeaseSequence = 7
	state.Features = []string{FeatureHTTP}
	state.TokenExpiresAt = now.Add(24 * time.Hour)
	store := &fakeStateStore{state: state}
	remote := &fakeRemote{heartbeatErrs: []error{&HTTPError{StatusCode: 401}}}
	manager := NewManager(remote, store, "1.0").WithEvaluator(eval)
	manager.now = func() time.Time { return now }
	manager.hostname = func() (string, error) { return "production-01", nil }
	if err := manager.Report(context.Background()); !errors.Is(err, ErrReactivationRequired) {
		t.Fatalf("Report error = %v", err)
	}
	if err := eval.CheckFeature(FeatureHTTP); !errors.Is(err, ErrLicenseBlocked) {
		t.Fatalf("revoked L1 status changed after 401: %v", err)
	}
	if want := []string{"heartbeat"}; !reflect.DeepEqual(remote.calls, want) {
		t.Fatalf("calls = %#v, want %#v", remote.calls, want)
	}
}

func TestStateConfigured(t *testing.T) {
	state := State{
		Code:           "AUTH-TEST",
		VerifiedAt:     time.Now(),
		Identity:       Identity{InstanceID: testInstanceID},
		InstanceToken:  "inst_test",
		TokenExpiresAt: time.Now().Add(time.Hour),
	}
	if !state.Configured() {
		t.Fatal("expected state to be configured")
	}
	state.InstanceToken = ""
	if state.Configured() {
		t.Fatal("expected missing token to be unconfigured")
	}
}

func TestManagerInitEvaluatorRequiresOnlineLicenseServer(t *testing.T) {
	publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatalf("GenerateKey: %v", err)
	}
	validUntil := time.Now().UTC().Add(time.Hour).Format(time.RFC3339)
	payload := []byte(fmt.Sprintf(`{"status":"active","validUntil":%q,"expiresAt":%q}`, validUntil, validUntil))
	signature := "SIGN-" + base64.RawURLEncoding.EncodeToString(ed25519.Sign(privateKey, payload))
	raw := append(append([]byte(nil), payload[:len(payload)-1]...), []byte(`,"signature":"`+signature+`"}`)...)

	state := testCredentialState()
	state.LeaseRaw = raw
	state.LeaseSignature = signature
	store := &fakeStateStore{state: state}
	remote := &fakeRemote{publicKey: publicKey, queryErr: errors.New("license server offline")}
	eval := NewEvaluator(nil)
	manager := NewManager(remote, store, "1.0").WithEvaluator(eval)

	if err := manager.InitEvaluator(context.Background()); err == nil {
		t.Fatal("expected online validation failure")
	}
	if err := eval.Check(); !errors.Is(err, ErrLicenseUnavailable) {
		t.Fatalf("evaluator error = %v", err)
	}
}

func TestManagerInitEvaluatorAllowsExplicitReactivationAfterQuery401(t *testing.T) {
	publicKey, _, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	state := testCredentialState()
	store := &fakeStateStore{state: state}
	remote := &fakeRemote{publicKey: publicKey, queryErr: &HTTPError{StatusCode: 401}}
	eval := NewEvaluator(nil)
	manager := NewManager(remote, store, "1.0").WithEvaluator(eval)
	if err := manager.InitEvaluator(context.Background()); !errors.Is(err, ErrReactivationRequired) {
		t.Fatalf("InitEvaluator error = %v", err)
	}
	if err := eval.Check(); !errors.Is(err, ErrLicenseUnavailable) {
		t.Fatalf("evaluator error = %v", err)
	}
}

func TestManagerReportEntersGraceOnNetworkOutage(t *testing.T) {
	now := time.Date(2026, 7, 12, 12, 0, 0, 0, time.UTC)
	state := testCredentialState()
	state.LastHeartbeatAt = now.Add(-30 * time.Minute)
	store := &fakeStateStore{state: state}
	remote := &fakeRemote{heartbeatErr: errors.New("dial tcp: network unreachable")}
	eval := NewEvaluator(nil)
	manager := NewManager(remote, store, "1.0").
		WithEvaluator(eval).
		WithConnectivityProber(fakeProber{result: ProbeResultNetworkDown})
	manager.now = func() time.Time { return now }
	manager.hostname = func() (string, error) { return "production-01", nil }

	if err := manager.Report(context.Background()); err == nil {
		t.Fatal("expected Report to return the heartbeat error")
	}
	if status := eval.Status(); status != "grace" {
		t.Fatalf("eval status = %q, want grace", status)
	}
	if eval.GraceUntil().IsZero() {
		t.Fatal("expected non-zero grace until")
	}
}

func TestManagerReportBlocksAfterGraceWindowExceeded(t *testing.T) {
	now := time.Date(2026, 7, 12, 12, 0, 0, 0, time.UTC)
	state := testCredentialState()
	state.LastHeartbeatAt = now.Add(-4 * time.Hour)
	store := &fakeStateStore{state: state}
	remote := &fakeRemote{heartbeatErr: errors.New("dial tcp: network unreachable")}
	eval := NewEvaluator(nil)
	manager := NewManager(remote, store, "1.0").
		WithEvaluator(eval).
		WithConnectivityProber(fakeProber{result: ProbeResultNetworkDown})
	manager.now = func() time.Time { return now }
	manager.hostname = func() (string, error) { return "production-01", nil }

	if err := manager.Report(context.Background()); err == nil {
		t.Fatal("expected Report to return the heartbeat error")
	}
	if err := eval.CheckFeature(FeatureHTTP); !errors.Is(err, ErrLicenseUnavailable) {
		t.Fatalf("CheckFeature error = %v, want ErrLicenseUnavailable", err)
	}
}

// TestManagerReportDetectsSelectiveBlock verifies that a probe result of
// ProbeResultSelectiveBlock causes handleUnreachableHeartbeat to persist
// SelectiveBlockDetected and force the evaluator into a blocked state
// immediately (bypassing the grace period), rather than treating the outage
// as a transient network issue. evaluator.go's blockedError() maps the
// "selective_block" status to ErrLicenseBlocked.
func TestManagerReportDetectsSelectiveBlock(t *testing.T) {
	now := time.Date(2026, 7, 12, 12, 0, 0, 0, time.UTC)
	state := testCredentialState()
	state.LastHeartbeatAt = now.Add(-30 * time.Minute)
	store := &fakeStateStore{state: state}
	remote := &fakeRemote{heartbeatErr: errors.New("dial tcp: connection refused")}
	eval := NewEvaluator(nil)
	manager := NewManager(remote, store, "1.0").
		WithEvaluator(eval).
		WithConnectivityProber(fakeProber{result: ProbeResultSelectiveBlock})
	manager.now = func() time.Time { return now }
	manager.hostname = func() (string, error) { return "production-01", nil }

	if err := manager.Report(context.Background()); err == nil {
		t.Fatal("expected Report to return the selective-block error")
	}
	if status := eval.Status(); status == "grace" {
		t.Fatalf("eval status = %q, want not grace", status)
	}
	if err := eval.CheckFeature(FeatureHTTP); !errors.Is(err, ErrLicenseBlocked) {
		t.Fatalf("CheckFeature error = %v, want ErrLicenseBlocked", err)
	}
	if !store.state.SelectiveBlockDetected {
		t.Fatal("expected SelectiveBlockDetected to be persisted")
	}
}

func TestManagerInitEvaluatorStartupGraceWhenKeyFetchFails(t *testing.T) {
	originalDelays := publicKeyRetryDelays
	publicKeyRetryDelays = []time.Duration{time.Millisecond, time.Millisecond}
	defer func() { publicKeyRetryDelays = originalDelays }()

	state := testCredentialState()
	now := state.LastHeartbeatAt.Add(30 * time.Minute)
	store := &fakeStateStore{state: state}
	remote := &fakeRemote{publicKeyErr: errors.New("key server unreachable")}
	eval := NewEvaluator(nil)
	manager := NewManager(remote, store, "1.0").WithEvaluator(eval)
	manager.now = func() time.Time { return now }
	manager.hostname = func() (string, error) { return "production-01", nil }

	if err := manager.InitEvaluator(context.Background()); err != nil {
		t.Fatalf("InitEvaluator: %v", err)
	}
	if status := eval.Status(); status != "grace" {
		t.Fatalf("eval status = %q, want grace", status)
	}
}

func TestManagerInitEvaluatorRejectsStartupPastGraceWindow(t *testing.T) {
	originalDelays := publicKeyRetryDelays
	publicKeyRetryDelays = []time.Duration{time.Millisecond, time.Millisecond}
	defer func() { publicKeyRetryDelays = originalDelays }()

	state := testCredentialState()
	now := state.LastHeartbeatAt.Add(4 * time.Hour)
	store := &fakeStateStore{state: state}
	remote := &fakeRemote{publicKeyErr: errors.New("key server unreachable")}
	eval := NewEvaluator(nil)
	manager := NewManager(remote, store, "1.0").WithEvaluator(eval)
	manager.now = func() time.Time { return now }
	manager.hostname = func() (string, error) { return "production-01", nil }

	if err := manager.InitEvaluator(context.Background()); err == nil {
		t.Fatal("expected InitEvaluator to fail once the grace window is exceeded")
	}
}

func TestManagerInitEvaluatorRejectsClockRollback(t *testing.T) {
	state := testCredentialState()
	state.HighWaterMark = state.LastHeartbeatAt.Add(time.Hour)
	store := &fakeStateStore{state: state}
	remote := &fakeRemote{publicKey: make([]byte, 32)}
	eval := NewEvaluator(nil)
	manager := NewManager(remote, store, "1.0").WithEvaluator(eval)
	manager.now = func() time.Time { return state.LastHeartbeatAt }
	manager.hostname = func() (string, error) { return "production-01", nil }

	err := manager.InitEvaluator(context.Background())
	if !errors.Is(err, ErrLicenseClockRolledBack) {
		t.Fatalf("InitEvaluator error = %v, want ErrLicenseClockRolledBack", err)
	}
}

// TestManagerInitEvaluatorRejectsPersistedSelectiveBlock verifies startup
// refuses to proceed when a prior run detected a selective block.
// evaluator.go's blockedError() maps the "selective_block" status to
// ErrLicenseBlocked.
func TestManagerInitEvaluatorRejectsPersistedSelectiveBlock(t *testing.T) {
	state := testCredentialState()
	state.SelectiveBlockDetected = true
	store := &fakeStateStore{state: state}
	remote := &fakeRemote{publicKey: make([]byte, 32)}
	eval := NewEvaluator(nil)
	manager := NewManager(remote, store, "1.0").WithEvaluator(eval)
	manager.now = func() time.Time { return state.LastHeartbeatAt }
	manager.hostname = func() (string, error) { return "production-01", nil }

	if err := manager.InitEvaluator(context.Background()); err == nil {
		t.Fatal("expected InitEvaluator to reject a persisted selective block")
	}
	if err := eval.CheckFeature(FeatureHTTP); !errors.Is(err, ErrLicenseBlocked) {
		t.Fatalf("CheckFeature error = %v, want ErrLicenseBlocked", err)
	}
}

func TestManagerInitEvaluatorRequiresFreshOnlineQueryBeforeLoadingL1(t *testing.T) {
	publicKey, raw, signature := signedEvaluatorLease(
		t,
		"active",
		[]string{FeatureHTTP, FeatureWorker, FeaturePlugins},
		time.Now().Add(time.Hour),
		time.Now().Add(24*time.Hour),
	)
	state := testCredentialState()
	state.LeaseSequence = 6
	state.LeaseScope = "instance:LIC-TEST:" + testInstanceID
	store := &fakeStateStore{state: state}
	remote := &fakeRemote{
		publicKey:      publicKey,
		queryRaw:       raw,
		querySignature: signature,
		query: Query{
			Authorized: true,
			Status:     "active",
			Version:    "1.0",
			ExpiresAt:  time.Now().Add(24 * time.Hour),
			LicenseNo:  "LIC-TEST",
			InstanceID: testInstanceID,
			Sequence:   7,
			Features:   []string{FeatureHTTP, FeatureWorker, FeaturePlugins},
		},
	}
	eval := NewEvaluator(nil)
	manager := NewManager(remote, store, "1.0").WithEvaluator(eval)

	if err := manager.InitEvaluator(context.Background()); err != nil {
		t.Fatalf("InitEvaluator: %v", err)
	}
	if err := eval.CheckFeature(FeatureHTTP); err != nil {
		t.Fatalf("HTTP feature: %v", err)
	}
	if store.state.LeaseSequence != 7 {
		t.Fatalf("stored sequence = %d", store.state.LeaseSequence)
	}
}
