package license

import (
	"crypto/ed25519"
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"errors"
	"math"
	"testing"
	"time"
)

func TestValidateLeaseRejectsSequenceOutsideSignedBigInt(t *testing.T) {
	lease := Lease{
		Status:     "active",
		ValidUntil: time.Now().Add(time.Hour),
		ExpiresAt:  time.Now().Add(24 * time.Hour),
		LicenseNo:  "LIC-TEST",
		KeyID:      "key-1",
		InstanceID: testInstanceID,
		Sequence:   uint64(math.MaxInt64) + 1,
		Features:   []string{FeatureHTTP},
		IssuedAt:   time.Now(),
	}
	if err := validateLease(lease, time.Now(), testInstanceID, true); !errors.Is(err, ErrLicenseProtocol) {
		t.Fatalf("validateLease() error = %v", err)
	}
}

type evaluatorLeasePayload struct {
	Status     string   `json:"status"`
	ValidUntil string   `json:"validUntil"`
	ExpiresAt  string   `json:"expiresAt"`
	License    string   `json:"license"`
	LicenseNo  string   `json:"licenseNo"`
	KeyID      string   `json:"keyId"`
	InstanceID string   `json:"instanceId"`
	Sequence   uint64   `json:"sequence"`
	Features   []string `json:"features"`
	IssuedAt   int64    `json:"issuedAt"`
}

func signedEvaluatorLease(t *testing.T, status string, features []string, validUntil, expiresAt time.Time) (ed25519.PublicKey, []byte, string) {
	t.Helper()
	publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatalf("GenerateKey: %v", err)
	}
	payload, err := json.Marshal(evaluatorLeasePayload{
		Status:     status,
		ValidUntil: validUntil.UTC().Format(time.RFC3339),
		ExpiresAt:  expiresAt.UTC().Format(time.RFC3339),
		License:    "AUTH-TEST",
		LicenseNo:  "LIC-TEST",
		KeyID:      "test-key",
		InstanceID: testInstanceID,
		Sequence:   7,
		Features:   features,
		IssuedAt:   time.Now().UTC().Unix(),
	})
	if err != nil {
		t.Fatalf("Marshal: %v", err)
	}
	signature := "SIGN-" + base64.RawURLEncoding.EncodeToString(ed25519.Sign(privateKey, payload))
	raw := append(append([]byte(nil), payload[:len(payload)-1]...), []byte(`,"signature":"`+signature+`"}`)...)
	return publicKey, raw, signature
}

func TestEvaluatorEnforcesFeatures(t *testing.T) {
	publicKey, raw, signature := signedEvaluatorLease(t, "active", []string{FeatureHTTP}, time.Now().Add(time.Hour), time.Now().Add(24*time.Hour))
	eval := NewEvaluator(publicKey)
	if err := eval.LoadFromQuery(raw, signature); err != nil {
		t.Fatalf("LoadFromQuery: %v", err)
	}
	if err := eval.CheckFeature(FeatureHTTP); err != nil {
		t.Fatalf("HTTP feature: %v", err)
	}
	if err := eval.CheckFeature(FeatureWorker); !errors.Is(err, ErrLicenseFeatureMissing) {
		t.Fatalf("worker feature error = %v", err)
	}
}

func TestEvaluatorFailsClosedWhenOnlineLeaseExpires(t *testing.T) {
	publicKey, raw, signature := signedEvaluatorLease(t, "active", []string{FeatureHTTP}, time.Now().Add(-time.Minute), time.Now().Add(time.Hour))
	eval := NewEvaluator(publicKey)
	if err := eval.LoadFromQuery(raw, signature); err != nil {
		t.Fatalf("LoadFromQuery: %v", err)
	}
	if err := eval.CheckFeature(FeatureHTTP); !errors.Is(err, ErrLicenseUnavailable) {
		t.Fatalf("error = %v", err)
	}
}

func TestEvaluatorRejectsTamperedLease(t *testing.T) {
	publicKey, raw, signature := signedEvaluatorLease(t, "active", []string{FeatureHTTP}, time.Now().Add(time.Hour), time.Now().Add(24*time.Hour))
	raw[10] ^= 1
	eval := NewEvaluator(publicKey)
	if err := eval.LoadFromQuery(raw, signature); !errors.Is(err, ErrLicenseTampered) {
		t.Fatalf("error = %v", err)
	}
}

func TestEvaluatorUsesSignedTimeAnchorAgainstClockRollback(t *testing.T) {
	base := time.Date(2026, 7, 12, 12, 0, 0, 0, time.UTC)
	publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	payload, err := json.Marshal(evaluatorLeasePayload{
		Status:     "active",
		ValidUntil: base.Add(time.Minute).Format(time.RFC3339),
		ExpiresAt:  base.Add(time.Hour).Format(time.RFC3339),
		License:    "AUTH-TEST",
		LicenseNo:  "LIC-TEST",
		KeyID:      "test-key",
		InstanceID: testInstanceID,
		Sequence:   7,
		Features:   []string{FeatureHTTP},
		IssuedAt:   base.Unix(),
	})
	if err != nil {
		t.Fatal(err)
	}
	signature := "SIGN-" + base64.RawURLEncoding.EncodeToString(ed25519.Sign(privateKey, payload))
	raw := append(append([]byte(nil), payload[:len(payload)-1]...), []byte(`,"signature":"`+signature+`"}`)...)

	wallNow := base
	elapsed := time.Duration(0)
	eval := NewEvaluator(publicKey)
	eval.now = func() time.Time { return wallNow }
	eval.since = func(time.Time) time.Duration { return elapsed }
	if err := eval.LoadFromQuery(raw, signature); err != nil {
		t.Fatal(err)
	}

	wallNow = base.Add(-2 * time.Minute)
	elapsed = 2 * time.Minute
	if err := eval.CheckFeature(FeatureHTTP); !errors.Is(err, ErrLicenseUnavailable) {
		t.Fatalf("clock rollback extended lease: %v", err)
	}
}

func TestEvaluatorRejectsLargeClockDrift(t *testing.T) {
	base := time.Now().UTC()
	publicKey, raw, signature := signedEvaluatorLease(t, "active", []string{FeatureHTTP}, base.Add(time.Hour), base.Add(24*time.Hour))
	eval := NewEvaluator(publicKey)
	if err := eval.LoadFromQuery(raw, signature); err != nil {
		t.Fatal(err)
	}
	snapshot := eval.snapshot.Load()
	eval.now = func() time.Time { return snapshot.serverIssuedAt.Add(-time.Hour) }
	eval.since = func(time.Time) time.Duration { return time.Minute }
	if err := eval.CheckFeature(FeatureHTTP); !errors.Is(err, ErrLicenseClockInvalid) {
		t.Fatalf("large clock drift error = %v", err)
	}
}

func TestEvaluatorSetGraceAllowsCallsWithinWindow(t *testing.T) {
	eval := NewEvaluator(nil)
	now := time.Now()
	eval.now = func() time.Time { return now }
	expiresAt := now.Add(24 * time.Hour)
	graceUntil := now.Add(3 * time.Hour)
	eval.SetGrace(expiresAt, graceUntil)

	if err := eval.CheckFeature(""); err != nil {
		t.Fatalf("CheckFeature in grace window: %v", err)
	}
	if eval.Status() != "grace" {
		t.Fatalf("Status() = %q, want 'grace'", eval.Status())
	}
	if !eval.GraceUntil().Equal(graceUntil) {
		t.Fatalf("GraceUntil() = %v, want %v", eval.GraceUntil(), graceUntil)
	}
}

func TestEvaluatorSetGraceBlocksAfterGraceExpiry(t *testing.T) {
	eval := NewEvaluator(nil)
	now := time.Now()
	expiresAt := now.Add(24 * time.Hour)
	graceUntil := now.Add(-time.Minute) // already expired
	eval.now = func() time.Time { return now }
	eval.SetGrace(expiresAt, graceUntil)

	if err := eval.CheckFeature(""); !errors.Is(err, ErrLicenseGracePeriodExceeded) {
		t.Fatalf("expected ErrLicenseGracePeriodExceeded, got %v", err)
	}
}

func TestEvaluatorSetGraceBlocksAfterLicenseExpiry(t *testing.T) {
	eval := NewEvaluator(nil)
	now := time.Now()
	expiresAt := now.Add(-time.Minute) // license expired
	graceUntil := now.Add(3 * time.Hour)
	eval.now = func() time.Time { return now }
	eval.SetGrace(expiresAt, graceUntil)

	if err := eval.CheckFeature(""); !errors.Is(err, ErrLicenseExpired) {
		t.Fatalf("expected ErrLicenseExpired, got %v", err)
	}
}

func TestEvaluatorGraceUntilZeroWhenNotInGrace(t *testing.T) {
	eval := NewEvaluator(nil)
	if !eval.GraceUntil().IsZero() {
		t.Fatalf("GraceUntil() should be zero when not in grace, got %v", eval.GraceUntil())
	}
}

func TestEvaluatorSetGraceClearsForceBlock(t *testing.T) {
	eval := NewEvaluator(nil)
	eval.SetBlocked("suspended")
	if err := eval.CheckFeature(""); !errors.Is(err, ErrLicenseBlocked) {
		t.Fatalf("expected blocked before SetGrace, got %v", err)
	}
	now := time.Now()
	eval.now = func() time.Time { return now }
	eval.SetGrace(now.Add(24*time.Hour), now.Add(3*time.Hour))
	if err := eval.CheckFeature(""); err != nil {
		t.Fatalf("expected grace to override forceBlock, got %v", err)
	}
}

func TestEvaluatorLoadFromQueryClearsGrace(t *testing.T) {
	eval := NewEvaluator(nil)
	now := time.Now()
	eval.now = func() time.Time { return now }
	eval.SetGrace(now.Add(24*time.Hour), now.Add(3*time.Hour))
	if eval.Status() != "grace" {
		t.Fatalf("expected grace before LoadFromQuery, got %q", eval.Status())
	}

	publicKey, raw, signature := signedEvaluatorLease(t, "active", []string{FeatureHTTP}, now.Add(time.Hour), now.Add(24*time.Hour))
	eval.publicKey = publicKey
	if err := eval.LoadFromQuery(raw, signature); err != nil {
		t.Fatalf("LoadFromQuery: %v", err)
	}
	if eval.Status() == "grace" {
		t.Fatalf("expected LoadFromQuery to clear grace state, got Status() = %q", eval.Status())
	}
	if !eval.GraceUntil().IsZero() {
		t.Fatalf("expected GraceUntil() to be zero after LoadFromQuery, got %v", eval.GraceUntil())
	}
}

func TestCompareLeaseSequenceIsScoped(t *testing.T) {
	next := Lease{LicenseNo: "LIC-TEST", InstanceID: testInstanceID, Sequence: 10}
	if err := compareLeaseSequence(next.Scope(), 10, next); !errors.Is(err, ErrLicenseSequenceStale) {
		t.Fatalf("same scope error = %v", err)
	}
	if err := compareLeaseSequence("license:LIC-TEST", 100, next); err != nil {
		t.Fatalf("different scope error = %v", err)
	}
}
