package runtimeidentity

import (
	"context"
	"crypto/ed25519"
	"encoding/base64"
	"encoding/json"
	"errors"
	"os"
	"path/filepath"
	"reflect"
	"testing"
	"time"

	"github.com/nexuscrm/backend/internal/runtimeapi"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-api-host/runtimegrpc"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordsv1"
	"google.golang.org/grpc/metadata"
)

func TestBearerWorkloadAuthorityAuthenticatesOneOperatorBinding(t *testing.T) {
	authority, bindings, err := NewBearerWorkloadAuthority("sfcrm-app-service", "secret-token")
	if err != nil {
		t.Fatal(err)
	}
	valid := metadata.NewIncomingContext(t.Context(), metadata.Pairs("authorization", "Bearer secret-token"))
	caller, err := authority(valid, "/runtime.v1.IdentityContextService/Exchange")
	if err != nil || caller != runtimegrpc.VerifiedCaller("sfcrm-app-service") {
		t.Fatalf("authority = %q, %v", caller, err)
	}
	binding, found, err := bindings(t.Context(), caller)
	if err != nil || !found || binding.CallerWorkloadID != "sfcrm-app-service" {
		t.Fatalf("binding = %+v, %v, %v", binding, found, err)
	}
	for _, values := range []metadata.MD{
		{}, metadata.Pairs("authorization", "Bearer wrong"),
		metadata.Pairs("authorization", "Bearer secret-token", "authorization", "Bearer secret-token"),
	} {
		ctx := metadata.NewIncomingContext(context.Background(), values)
		_, err := authority(ctx, "/runtime.v1.RecordsService/EvaluateRecordAccess")
		var failure *semantic.Failure
		if !errors.As(err, &failure) || failure.Category != semantic.FailureUnauthenticated {
			t.Fatalf("metadata %v error = %v", values, err)
		}
	}
}

func TestBearerWorkloadAuthoritiesAuthenticateClosedDistinctSet(t *testing.T) {
	path := filepath.Join(t.TempDir(), "workloads.json")
	raw := `{"schema":"nexuscrm.runtime-workload-identities/v1","identities":[{"workloadId":"integration-service","token":"integration-secret"},{"workloadId":"sfcrm-app-service","token":"sfcrm-secret"}]}`
	if err := os.WriteFile(path, []byte(raw), 0o600); err != nil {
		t.Fatal(err)
	}
	authority, bindings, err := LoadBearerWorkloadAuthorities(
		path,
		[]string{"integration-service", "sfcrm-app-service"},
	)
	if err != nil {
		t.Fatal(err)
	}
	for token, wantWorkload := range map[string]string{
		"integration-secret": "integration-service",
		"sfcrm-secret":       "sfcrm-app-service",
	} {
		ctx := metadata.NewIncomingContext(
			t.Context(),
			metadata.Pairs("authorization", "Bearer "+token),
		)
		caller, err := authority(
			ctx,
			"/runtime.v1.IdentityContextService/Exchange",
		)
		if err != nil || string(caller) != wantWorkload {
			t.Fatalf("token %q caller = %q, %v", token, caller, err)
		}
		binding, found, err := bindings(t.Context(), caller)
		if err != nil || !found ||
			binding.CallerWorkloadID != wantWorkload {
			t.Fatalf("token %q binding = %#v, %v, %v", token, binding, found, err)
		}
	}
	if _, _, err := NewBearerWorkloadAuthorities(map[string]string{
		"integration-service": "shared",
		"sfcrm-app-service":   "shared",
	}); err == nil {
		t.Fatal("duplicate bearer credential was accepted")
	}
	if _, _, err := LoadBearerWorkloadAuthorities(
		path,
		[]string{"sfcrm-app-service"},
	); err == nil {
		t.Fatal("partial deployment identity set was accepted")
	}
}

func TestSigningKeyFileLoadsCurrentAndOverlapWithoutGeneratingKeys(t *testing.T) {
	now := time.Now().UTC().Truncate(time.Second)
	current := ed25519.NewKeyFromSeed(make([]byte, ed25519.SeedSize))
	old := ed25519.NewKeyFromSeed(append([]byte{1}, make([]byte, ed25519.SeedSize-1)...))
	document := signingKeyFile{
		Schema: SigningKeyFileSchemaV1,
		Active: signingKeyFileActive{SigningKeyID: "current", PrivateKeyBase64: base64.StdEncoding.EncodeToString(current)},
		Overlap: []signingKeyFileOverlap{{
			SigningKeyID: "old", PublicKeyBase64: base64.StdEncoding.EncodeToString(old.Public().(ed25519.PublicKey)),
			SigningStoppedAt: now.Format(time.RFC3339), VerificationExpiresAt: now.Add(PlannedOldKeyOverlap).Format(time.RFC3339),
		}},
	}
	raw, err := json.Marshal(document)
	if err != nil {
		t.Fatal(err)
	}
	path := filepath.Join(t.TempDir(), "keys.json")
	if err := os.WriteFile(path, raw, 0o640); err != nil {
		t.Fatal(err)
	}
	snapshot, err := LoadSigningKeySnapshot(path, now)
	if err != nil {
		t.Fatal(err)
	}
	if snapshot.Active.SigningKeyID != "current" || len(snapshot.Overlap) != 1 || snapshot.Overlap[0].SigningKeyID != "old" {
		t.Fatalf("snapshot = %+v", snapshot)
	}
	if _, err := snapshot.Verifier(now, []string{semantic.CapabilityName}); err != nil {
		t.Fatalf("verify loaded snapshot: %v", err)
	}
	if err := os.WriteFile(path, append(raw, []byte(` {}`)...), 0o640); err != nil {
		t.Fatal(err)
	}
	if _, err := LoadSigningKeySnapshot(path, now); err == nil {
		t.Fatal("multiple JSON documents were accepted")
	}
}

func TestSigningKeyProviderKeepsOneImmutableStartupEpoch(t *testing.T) {
	private := ed25519.NewKeyFromSeed(make([]byte, ed25519.SeedSize))
	wantPrivate := append(ed25519.PrivateKey(nil), private...)
	snapshot := SigningKeySnapshot{Active: ActiveSigningKey{SigningKeyID: "startup", PrivateKey: private}}
	provider := SnapshotSigningKeyProvider(snapshot)
	snapshot.Active.SigningKeyID = "mutated-caller"
	snapshot.Active.PrivateKey[0] ^= 0xff

	first, err := provider(t.Context(), time.Now())
	if err != nil {
		t.Fatal(err)
	}
	first.Active.SigningKeyID = "mutated-result"
	first.Active.PrivateKey[0] ^= 0xff
	second, err := provider(t.Context(), time.Now())
	if err != nil {
		t.Fatal(err)
	}
	if second.Active.SigningKeyID != "startup" || !reflect.DeepEqual(second.Active.PrivateKey, wantPrivate) {
		t.Fatal("issuer key epoch was mutable after startup")
	}
}

func TestSessionCapabilityPolicyHasNoEmptyScopeFallback(t *testing.T) {
	policy, err := NewSessionCapabilityPolicy(map[string][]string{
		"api": []string{"runtime.records.evaluate-access.v1"},
	})
	if err != nil {
		t.Fatal(err)
	}
	for _, test := range []struct {
		scope string
		want  []string
	}{
		{scope: "", want: nil},
		{scope: "openid", want: nil},
		{scope: "openid api", want: []string{"runtime.records.evaluate-access.v1"}},
	} {
		got, err := policy(t.Context(), runtimeapi.RuntimeSessionState{User: modelsUserSession(test.scope)})
		if err != nil || !reflect.DeepEqual(got, test.want) {
			t.Fatalf("scope %q = %v, %v; want %v", test.scope, got, err, test.want)
		}
	}
}

func modelsUserSession(scope string) models.UserSession {
	return models.UserSession{ID: "user-1", Scope: scope}
}
