package identity

import (
	"encoding/json"
	"strings"
	"testing"
)

func TestRevisionStateTransitions(t *testing.T) {
	tests := []struct {
		from, to RevisionState
		allowed  bool
	}{
		{RevisionDraft, RevisionValidated, true},
		{RevisionDraft, RevisionFailed, true},
		{RevisionValidated, RevisionActive, true},
		{RevisionValidated, RevisionFailed, true},
		{RevisionActive, RevisionSuperseded, true},
		{RevisionSuperseded, RevisionValidated, true},
		{RevisionSuperseded, RevisionActive, false},
		{RevisionDraft, RevisionActive, false},
		{RevisionFailed, RevisionActive, false},
		{RevisionActive, RevisionValidated, false},
	}
	for _, test := range tests {
		if got := CanTransition(test.from, test.to); got != test.allowed {
			t.Errorf("CanTransition(%q, %q)=%v, want %v", test.from, test.to, got, test.allowed)
		}
	}
}

func TestRevisionNeverSerializesSecretValues(t *testing.T) {
	type secretFields interface {
		MachineSecret() string
	}
	var _ = any((*Revision)(nil))
	if _, exposesSecret := any((*Revision)(nil)).(secretFields); exposesSecret {
		t.Fatal("Revision unexpectedly exposes a machine secret value")
	}
	revision := Revision{MachineCredentialRef: "identity-machine-example", SessionEncryptionKeyRef: "identity-session-example"}
	if revision.MachineCredentialRef == "" || revision.SessionEncryptionKeyRef == "" {
		t.Fatal("Revision must retain SecretStore references")
	}
}

func TestValidatePairingInputDerivesExactGatewayURIs(t *testing.T) {
	input := PairingInput{
		AuthCenterURL: "https://auth.example.com", PublicBaseURL: "https://api.example.com",
		WebBaseURL: "https://gateway.example.com", LocalTenantKey: "default",
	}
	metadata, err := input.ConsumerMetadata(true, "production")
	if err != nil {
		t.Fatal(err)
	}
	if metadata.RedirectURIs[0] != "https://api.example.com/api/v1/auth/oidc/callback" ||
		metadata.LogoutURIs[0] != "https://gateway.example.com/" ||
		metadata.ReceiverEndpoint != "https://api.example.com/api/v1/security-events/ssf" {
		t.Fatalf("unexpected derived metadata: %#v", metadata)
	}
}

func TestValidatePairingInputRejectsRemoteHTTPAndURLCredentials(t *testing.T) {
	for _, input := range []PairingInput{
		{AuthCenterURL: "http://auth.example.com", PublicBaseURL: "https://api.example.com", WebBaseURL: "https://gateway.example.com", LocalTenantKey: "default"},
		{AuthCenterURL: "https://user:password@auth.example.com", PublicBaseURL: "https://api.example.com", WebBaseURL: "https://gateway.example.com", LocalTenantKey: "default"},
	} {
		if _, err := input.ConsumerMetadata(false, "production"); err == nil {
			t.Fatalf("unsafe pairing input accepted: %#v", input)
		}
	}
}

func TestValidatePairingInputRejectsCrossSiteBrowserEndpoints(t *testing.T) {
	for _, test := range []struct {
		name   string
		appEnv string
		input  PairingInput
	}{
		{
			name:   "different registrable domains",
			appEnv: "production",
			input: PairingInput{
				AuthCenterURL: "https://auth.example.com", PublicBaseURL: "https://api.example.com",
				WebBaseURL: "https://gateway.example.net", LocalTenantKey: "default",
			},
		},
		{
			name:   "different loopback sites",
			appEnv: "test",
			input: PairingInput{
				AuthCenterURL: "http://127.0.0.1:18000", PublicBaseURL: "http://127.0.0.1:18088",
				WebBaseURL: "http://localhost:15178", LocalTenantKey: "default",
			},
		},
		{
			name:   "different schemes",
			appEnv: "test",
			input: PairingInput{
				AuthCenterURL: "http://127.0.0.1:18000", PublicBaseURL: "http://localhost:18088",
				WebBaseURL: "https://localhost:15178", LocalTenantKey: "default",
			},
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			if _, err := test.input.ConsumerMetadata(false, test.appEnv); err == nil {
				t.Fatalf("cross-site browser endpoints were accepted: %#v", test.input)
			}
		})
	}
}

func TestValidateRevisionURLsRejectsCrossSiteBrowserEndpoints(t *testing.T) {
	revision := Revision{
		AuthCenterURL: "https://auth.example.com",
		Issuer:        "https://auth.example.com/issuer/shared",
		PublicBaseURL: "https://api.example.com",
		WebBaseURL:    "https://gateway.example.net",
	}
	if err := ValidateRevisionURLs(revision, "production"); err == nil {
		t.Fatal("cross-site persisted browser endpoints were accepted")
	}
}

func TestNewDraftAppliesSessionDefaultsWithoutPersistingOnboardingCode(t *testing.T) {
	draft, err := NewDraft(PairingInput{
		AuthCenterURL: "https://auth.example.com", OnboardingCode: "onb1.must-never-be-persisted",
		PublicBaseURL: "https://api.example.com", WebBaseURL: "https://gateway.example.com",
		LocalTenantKey: "default", LegacyJWTEnabled: true,
	}, "production")
	if err != nil {
		t.Fatal(err)
	}
	if draft.State != RevisionDraft || draft.SessionIdleSeconds != 1800 || draft.SessionAbsoluteSeconds != 28800 || draft.SessionRefreshSeconds != 60 {
		t.Fatalf("unexpected draft defaults: %#v", draft)
	}
	payload, _ := json.Marshal(draft)
	if strings.Contains(string(payload), "must-never-be-persisted") || strings.Contains(string(payload), "onboardingCode") {
		t.Fatalf("draft exposed onboarding code: %s", payload)
	}
}

func TestNewDraftAllowsLoopbackHTTPOnlyInLocalEnvironments(t *testing.T) {
	localInput := PairingInput{
		AuthCenterURL: "http://localhost:18000", PublicBaseURL: "http://127.0.0.1:18089",
		WebBaseURL: "http://127.0.0.1:5178", LocalTenantKey: "default",
	}
	secureInput := PairingInput{
		AuthCenterURL: "https://auth.example.com", PublicBaseURL: "https://api.example.com",
		WebBaseURL: "https://gateway.example.com", LocalTenantKey: "default",
	}
	for _, test := range []struct {
		name   string
		mutate func(*PairingInput)
	}{
		{name: "auth center", mutate: func(input *PairingInput) { input.AuthCenterURL = localInput.AuthCenterURL }},
		{name: "public base", mutate: func(input *PairingInput) { input.PublicBaseURL = localInput.PublicBaseURL }},
		{name: "web base", mutate: func(input *PairingInput) { input.WebBaseURL = localInput.WebBaseURL }},
	} {
		t.Run(test.name, func(t *testing.T) {
			input := secureInput
			test.mutate(&input)
			for _, appEnv := range []string{"", "production", "staging"} {
				if _, err := NewDraft(input, appEnv); err == nil {
					t.Fatalf("%s accepted loopback HTTP %s URL", appEnv, test.name)
				}
			}
		})
	}
	for _, appEnv := range []string{"local", "development", "dev", "test"} {
		draft, err := NewDraft(localInput, appEnv)
		if err != nil {
			t.Fatalf("%s rejected loopback HTTP pairing URLs: %v", appEnv, err)
		}
		if draft.AuthCenterURL != localInput.AuthCenterURL || draft.PublicBaseURL != localInput.PublicBaseURL || draft.WebBaseURL != localInput.WebBaseURL {
			t.Fatalf("%s changed normalized loopback URLs: %#v", appEnv, draft)
		}
	}
}

func TestApplyManifestV2RemovesFixedTenantMappingWhileV1StillRequiresIt(t *testing.T) {
	draft, err := NewDraft(PairingInput{
		AuthCenterURL: "https://auth.example.com", PublicBaseURL: "https://api.example.com",
		WebBaseURL: "https://gateway.example.com",
	}, "production")
	if err != nil {
		t.Fatalf("multi-tenant pairing draft rejected: %v", err)
	}
	applied, err := ApplyManifest(draft, ManifestApplication{
		AppEnv: "production", MachineCredentialRef: "identity-machine-example",
		SessionEncryptionKeyRef: "identity-session-example",
		Manifest: ManifestV2{
			SchemaVersion: 2, TenantMode: "multi_tenant", Issuer: "https://auth.example.com/issuer/shared",
			ApplicationID: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
			Audience:      "urn:easyai:resource:bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
			Capabilities:  []string{"oidc_login", "api_access", "machine_to_machine", "token_introspection", "session_revocation"},
			Scopes:        []string{"openid", "gateway.access"},
			Clients: ManifestClients{
				BrowserLogin: &ManifestClient{ClientID: "browser"}, MachineToMachine: &ManifestClient{ClientID: "service"},
			},
			TenantContext: &ManifestTenantContext{
				Endpoint: "https://auth.example.com/api/v1/runtime/tenants/{tenantId}",
				Audience: "urn:easyai:auth-center:tenant-context", Scope: "tenant.context.read",
			},
			SecurityEvents: &ManifestSecurityEvents{
				TransmitterIssuer:     "https://auth.example.com/ssf",
				ConfigurationEndpoint: "https://auth.example.com/.well-known/ssf-configuration/ssf",
				Audience:              "urn:easyai:ssf:receiver:bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
			},
		},
	})
	if err != nil {
		t.Fatal(err)
	}
	if applied.SchemaVersion != 2 || applied.TenantMode != "multi_tenant" || applied.TenantID != "" ||
		applied.LocalTenantKey != "" || applied.TenantContextEndpoint == "" {
		t.Fatalf("unexpected multi-tenant revision: %#v", applied)
	}

	v1Draft := draft
	v1Draft.ID = "v1-draft"
	if _, err := ApplyManifest(v1Draft, ManifestApplication{AppEnv: "production", Manifest: ManifestV1{
		SchemaVersion: 1, Issuer: "https://auth.example.com/issuer/shared",
		TenantID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", ApplicationID: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
		Capabilities: []string{}, Scopes: []string{}, Clients: ManifestClients{},
	}}); err == nil {
		t.Fatal("Manifest V1 without a fixed local tenant mapping was accepted")
	}
}

func TestApplyManifestV1PersistsSingleTenantMode(t *testing.T) {
	draft, err := NewDraft(PairingInput{
		AuthCenterURL: "https://auth.example.com", PublicBaseURL: "https://api.example.com",
		WebBaseURL: "https://gateway.example.com", LocalTenantKey: "default",
	}, "production")
	if err != nil {
		t.Fatal(err)
	}
	applied, err := ApplyManifest(draft, ManifestApplication{
		AppEnv: "production",
		Manifest: ManifestV1{
			SchemaVersion: 1, Issuer: "https://auth.example.com/issuer/default",
			TenantID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", ApplicationID: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
			Capabilities: []string{}, Scopes: []string{}, Clients: ManifestClients{},
		},
	})
	if err != nil {
		t.Fatal(err)
	}
	if applied.TenantMode != "single_tenant" {
		t.Fatalf("Manifest V1 should persist the explicit single-tenant discriminator: %#v", applied)
	}
}
