package license

import (
	"context"
	"encoding/base64"
	"errors"
	"os"
	"path/filepath"
	"reflect"
	"sync"
	"testing"
	"time"
)

func testCredentialState() State {
	now := time.Date(2026, 7, 12, 10, 0, 0, 0, time.UTC)
	signature := "SIGN-" + base64.RawURLEncoding.EncodeToString(make([]byte, 64))
	payload := []byte(`{"status":"active","validUntil":"2026-07-12T11:00:00Z","expiresAt":"2027-01-01T00:00:00Z","license":"AUTH-TEST","licenseNo":"LIC-TEST","keyId":"key-2026","instanceId":"550e8400-e29b-41d4-a716-446655440000","sequence":2,"features":["http","worker","plugins"],"issuedAt":1783831200}`)
	raw := append(append([]byte(nil), payload[:len(payload)-1]...), []byte(`,"signature":"`+signature+`"}`)...)
	return State{
		Code:            "AUTH-TEST",
		Type:            "standard",
		Version:         "1.0",
		VerifiedAt:      now,
		Identity:        Identity{InstanceID: testInstanceID, InstanceName: "node-01", NodeRole: "master", SystemName: "linux", SystemArch: "amd64", BackendURL: "https://example.com"},
		InstanceToken:   "inst_secret",
		TokenExpiresAt:  now.Add(24 * time.Hour),
		LastHeartbeatAt: now,
		LeaseRaw:        raw,
		LeaseSignature:  signature,
		LeaseScope:      "instance:LIC-TEST:" + testInstanceID,
		LeaseSequence:   2,
		Features:        []string{FeatureHTTP, FeatureWorker, FeaturePlugins},
	}
}

func TestCredentialSealOpenRoundTrip(t *testing.T) {
	state := testCredentialState()
	key := keyMaterial(make([]byte, credentialKeyBytes))
	record, err := sealCredential(state, key)
	if err != nil {
		t.Fatalf("sealCredential: %v", err)
	}
	opened, err := openCredential(record, key)
	if err != nil {
		t.Fatalf("openCredential: %v", err)
	}
	if !reflect.DeepEqual(opened, state) {
		t.Fatalf("opened state = %#v, want %#v", opened, state)
	}
}

func TestCredentialRejectsCiphertextTampering(t *testing.T) {
	key := keyMaterial(make([]byte, credentialKeyBytes))
	record, err := sealCredential(testCredentialState(), key)
	if err != nil {
		t.Fatalf("sealCredential: %v", err)
	}
	record.Ciphertext[len(record.Ciphertext)-1] ^= 0xff
	if _, err := openCredential(record, key); !errors.Is(err, ErrCredentialCorrupt) {
		t.Fatalf("error = %v", err)
	}
}

func TestCredentialRejectsInstanceBindingTampering(t *testing.T) {
	key := keyMaterial(make([]byte, credentialKeyBytes))
	record, err := sealCredential(testCredentialState(), key)
	if err != nil {
		t.Fatalf("sealCredential: %v", err)
	}
	record.InstanceID = "f47ac10b-58cc-4372-a567-0e02b2c3d479"
	if _, err := openCredential(record, key); !errors.Is(err, ErrCredentialCorrupt) {
		t.Fatalf("error = %v", err)
	}
}

func TestCredentialRejectsSequenceRollbackMetadataTampering(t *testing.T) {
	key := keyMaterial(make([]byte, credentialKeyBytes))
	record, err := sealCredential(testCredentialState(), key)
	if err != nil {
		t.Fatalf("sealCredential: %v", err)
	}
	record.LeaseSequence--
	if _, err := openCredential(record, key); !errors.Is(err, ErrCredentialCorrupt) {
		t.Fatalf("error = %v", err)
	}
}

func TestFileKeyProviderCreatesStableMachineKey(t *testing.T) {
	path := filepath.Join(t.TempDir(), "license", "master.key")
	provider := NewFileKeyProvider(path)
	first, err := provider.LoadOrCreate(context.Background())
	if err != nil {
		t.Fatalf("first LoadOrCreate: %v", err)
	}
	second, err := provider.LoadOrCreate(context.Background())
	if err != nil {
		t.Fatalf("second LoadOrCreate: %v", err)
	}
	if first.ID != second.ID || !reflect.DeepEqual(first.Key, second.Key) || len(first.Key) != credentialKeyBytes {
		t.Fatalf("key material is not stable")
	}
	if info, err := os.Stat(path); err != nil || info.Size() != credentialKeyBytes {
		t.Fatalf("key file: info=%v err=%v", info, err)
	}
}

func TestFileKeyProviderHandlesConcurrentFirstStart(t *testing.T) {
	path := filepath.Join(t.TempDir(), "license", "master.key")
	const workers = 8
	results := make(chan KeyMaterial, workers)
	errorsCh := make(chan error, workers)
	var wg sync.WaitGroup
	for range workers {
		wg.Add(1)
		go func() {
			defer wg.Done()
			key, err := NewFileKeyProvider(path).LoadOrCreate(context.Background())
			if err != nil {
				errorsCh <- err
				return
			}
			results <- key
		}()
	}
	wg.Wait()
	close(results)
	close(errorsCh)
	for err := range errorsCh {
		t.Fatalf("LoadOrCreate: %v", err)
	}
	var first KeyMaterial
	for result := range results {
		if len(first.Key) == 0 {
			first = result
			continue
		}
		if result.ID != first.ID || !reflect.DeepEqual(result.Key, first.Key) {
			t.Fatal("concurrent providers returned different machine keys")
		}
	}
}

func TestStaticKeyProviderRoundTrip(t *testing.T) {
	hexKey := "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"
	p, err := NewStaticKeyProvider(hexKey)
	if err != nil {
		t.Fatalf("NewStaticKeyProvider: %v", err)
	}
	key, err := p.LoadOrCreate(context.Background())
	if err != nil {
		t.Fatalf("LoadOrCreate: %v", err)
	}
	if len(key.Key) != credentialKeyBytes {
		t.Fatalf("key length %d, want %d", len(key.Key), credentialKeyBytes)
	}
	// second call returns same material
	key2, _ := p.LoadOrCreate(context.Background())
	if key.ID != key2.ID {
		t.Fatal("ID changed between calls")
	}
}

func TestStaticKeyProviderRejectsShortKey(t *testing.T) {
	if _, err := NewStaticKeyProvider("0102"); err == nil {
		t.Fatal("expected error for short hex key")
	}
}

func TestStaticKeyProviderRejectsNonHex(t *testing.T) {
	if _, err := NewStaticKeyProvider("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"); err == nil {
		t.Fatal("expected error for non-hex")
	}
}

func TestCredentialEnvelopePreservesHighWaterMark(t *testing.T) {
	state := testCredentialState()
	state.HighWaterMark = time.Date(2026, 7, 12, 10, 0, 0, 0, time.UTC)

	key := keyMaterial(make([]byte, credentialKeyBytes))
	record, err := sealCredential(state, key)
	if err != nil {
		t.Fatalf("sealCredential: %v", err)
	}
	opened, err := openCredential(record, key)
	if err != nil {
		t.Fatalf("openCredential: %v", err)
	}
	if !opened.HighWaterMark.Equal(state.HighWaterMark) {
		t.Fatalf("HighWaterMark = %v, want %v", opened.HighWaterMark, state.HighWaterMark)
	}
}

func TestCredentialEnvelopePreservesSelectiveBlock(t *testing.T) {
	state := testCredentialState()
	state.SelectiveBlockDetected = true

	key := keyMaterial(make([]byte, credentialKeyBytes))
	record, err := sealCredential(state, key)
	if err != nil {
		t.Fatalf("sealCredential: %v", err)
	}
	opened, err := openCredential(record, key)
	if err != nil {
		t.Fatalf("openCredential: %v", err)
	}
	if !opened.SelectiveBlockDetected {
		t.Fatal("SelectiveBlockDetected should be true after round-trip")
	}
}

func TestCredentialEnvelopeZeroFieldsDefaultToFalse(t *testing.T) {
	state := testCredentialState()
	// HighWaterMark and SelectiveBlockDetected are zero/false by default
	key := keyMaterial(make([]byte, credentialKeyBytes))
	record, err := sealCredential(state, key)
	if err != nil {
		t.Fatalf("sealCredential: %v", err)
	}
	opened, err := openCredential(record, key)
	if err != nil {
		t.Fatalf("openCredential: %v", err)
	}
	if !opened.HighWaterMark.IsZero() {
		t.Fatalf("HighWaterMark should be zero, got %v", opened.HighWaterMark)
	}
	if opened.SelectiveBlockDetected {
		t.Fatal("SelectiveBlockDetected should be false by default")
	}
}
