package oidcsession

import (
	"bytes"
	"strings"
	"testing"
	"time"
)

func TestLoginTransactionIsEncryptedAndBounded(t *testing.T) {
	now := time.Date(2026, 7, 13, 12, 0, 0, 0, time.UTC)
	cipher, _ := NewCipher(bytes.Repeat([]byte{9}, 32))
	transaction, err := NewLoginTransaction("/workspace?tab=wallet", now)
	if err != nil {
		t.Fatal(err)
	}
	if transaction.State == transaction.Nonce || len(transaction.PKCEVerifier) != 43 || transaction.State == transaction.PKCEVerifier {
		t.Fatal("state, nonce and PKCE verifier are not independent security material")
	}
	encoded, err := cipher.EncodeLoginTransaction(transaction)
	if err != nil {
		t.Fatal(err)
	}
	if strings.Contains(encoded, transaction.State) || strings.Contains(encoded, transaction.PKCEVerifier) {
		t.Fatal("login transaction cookie contains plaintext security material")
	}
	decoded, err := cipher.DecodeLoginTransaction(encoded, now.Add(9*time.Minute))
	if err != nil || decoded.ReturnTo != transaction.ReturnTo {
		t.Fatalf("decode transaction=%#v err=%v", decoded, err)
	}
	if _, err := cipher.DecodeLoginTransaction(encoded, now.Add(10*time.Minute)); err == nil {
		t.Fatal("10-minute login transaction was accepted")
	}
}

func TestLoginTransactionEncryptsTenantHint(t *testing.T) {
	now := time.Date(2026, 7, 28, 12, 0, 0, 0, time.UTC)
	cipher, _ := NewCipher(bytes.Repeat([]byte{9}, 32))
	tenantHint := "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
	transaction, err := NewLoginTransactionWithContext("/", "tenant", tenantHint, now)
	if err != nil {
		t.Fatal(err)
	}
	encoded, err := cipher.EncodeLoginTransaction(transaction)
	if err != nil {
		t.Fatal(err)
	}
	if strings.Contains(encoded, tenantHint) {
		t.Fatal("login transaction cookie contains plaintext tenant hint")
	}
	decoded, err := cipher.DecodeLoginTransaction(encoded, now)
	if err != nil || decoded.ContextType != "tenant" || decoded.TenantHint != tenantHint {
		t.Fatalf("decoded transaction=%+v err=%v", decoded, err)
	}
}

func TestLoginTransactionAllowsIssuerSelectedContextWithoutTenantHint(t *testing.T) {
	now := time.Date(2026, 7, 31, 12, 0, 0, 0, time.UTC)
	cipher, _ := NewCipher(bytes.Repeat([]byte{9}, 32))
	transaction, err := NewLoginTransactionWithContext("/", "", "", now)
	if err != nil {
		t.Fatal(err)
	}
	encoded, err := cipher.EncodeLoginTransaction(transaction)
	if err != nil {
		t.Fatal(err)
	}
	decoded, err := cipher.DecodeLoginTransaction(encoded, now)
	if err != nil || decoded.ContextType != "" || decoded.TenantHint != "" {
		t.Fatalf("decoded transaction=%+v err=%v", decoded, err)
	}
}

func TestLoginTransactionRejectsInvalidContextBinding(t *testing.T) {
	now := time.Date(2026, 7, 28, 12, 0, 0, 0, time.UTC)
	tenantHint := "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
	for _, test := range []struct {
		contextType string
		tenantHint  string
	}{
		{contextType: "", tenantHint: tenantHint},
		{contextType: "account"},
		{contextType: "platform", tenantHint: tenantHint},
	} {
		if _, err := NewLoginTransactionWithContext(
			"/", test.contextType, test.tenantHint, now,
		); err == nil {
			t.Fatalf("unsafe context binding accepted: %#v", test)
		}
	}
}

func TestValidReturnToRejectsOpenRedirects(t *testing.T) {
	for _, value := range []string{"https://evil.example", "//evil.example", "/\\evil", "", "workspace"} {
		if ValidReturnTo(value) {
			t.Fatalf("unsafe returnTo accepted: %q", value)
		}
	}
	if !ValidReturnTo("/workspace/tasks?from=login#latest") {
		t.Fatal("safe relative returnTo was rejected")
	}
}
