package httpapi

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

func TestEncryptRefreshRoundTrip(t *testing.T) {
	s := &Server{sessionKey: deriveSessionKey("a-strong-session-secret")}
	plain := strings.Repeat("refresh.token.", 2000) // ~28 KB, mimics Casdoor's large JWT

	enc, err := s.encryptRefresh(plain)
	if err != nil {
		t.Fatalf("encrypt: %v", err)
	}
	if strings.Contains(enc, plain) {
		t.Fatal("ciphertext leaks plaintext")
	}
	got, err := s.decryptRefresh(enc)
	if err != nil {
		t.Fatalf("decrypt: %v", err)
	}
	if got != plain {
		t.Fatalf("roundtrip mismatch: len(got)=%d, len(want)=%d", len(got), len(plain))
	}
}

func TestEncryptRefreshNonceIsRandom(t *testing.T) {
	s := &Server{sessionKey: deriveSessionKey("secret")}
	a, _ := s.encryptRefresh("same plaintext")
	b, _ := s.encryptRefresh("same plaintext")
	if a == b {
		t.Fatal("two encryptions of the same plaintext are identical — nonce not random")
	}
}

func TestDecryptRefreshRejectsTamper(t *testing.T) {
	s := &Server{sessionKey: deriveSessionKey("secret")}
	enc, err := s.encryptRefresh("token")
	if err != nil {
		t.Fatalf("encrypt: %v", err)
	}
	raw, err := base64.StdEncoding.DecodeString(enc)
	if err != nil {
		t.Fatalf("decode: %v", err)
	}
	raw[len(raw)-1] ^= 0xFF // flip a bit in the GCM tag
	if _, err := s.decryptRefresh(base64.StdEncoding.EncodeToString(raw)); err == nil {
		t.Fatal("tampered ciphertext must fail authentication")
	}
}

func TestDecryptRefreshRejectsWrongKey(t *testing.T) {
	enc, err := (&Server{sessionKey: deriveSessionKey("key-one")}).encryptRefresh("token")
	if err != nil {
		t.Fatalf("encrypt: %v", err)
	}
	if _, err := (&Server{sessionKey: deriveSessionKey("key-two")}).decryptRefresh(enc); err == nil {
		t.Fatal("decryption under a different key must fail")
	}
}

func TestEncryptRefreshNoKey(t *testing.T) {
	s := &Server{sessionKey: nil}
	if _, err := s.encryptRefresh("token"); err == nil {
		t.Fatal("encrypt without a key must error")
	}
}

func TestSessionIDDeterministicAndHashed(t *testing.T) {
	raw, id, err := newSessionToken()
	if err != nil {
		t.Fatalf("newSessionToken: %v", err)
	}
	if id != sessionID(raw) {
		t.Fatal("newSessionToken id must equal sessionID(raw)")
	}
	if id == raw || strings.Contains(id, raw) {
		t.Fatal("stored id must be a hash of the cookie value, not the value itself")
	}
	// SHA-256 hex is always 64 chars.
	if len(id) != 64 {
		t.Fatalf("session id length: got %d, want 64", len(id))
	}
}

func TestNewSessionTokenUnique(t *testing.T) {
	seen := make(map[string]bool)
	for i := 0; i < 1000; i++ {
		raw, _, err := newSessionToken()
		if err != nil {
			t.Fatalf("newSessionToken: %v", err)
		}
		if seen[raw] {
			t.Fatal("duplicate session token generated")
		}
		seen[raw] = true
	}
}

func TestDeriveSessionKey(t *testing.T) {
	if deriveSessionKey("") != nil {
		t.Fatal("empty secret must yield a nil key (feature disabled)")
	}
	if got := len(deriveSessionKey("x")); got != 32 {
		t.Fatalf("derived key length: got %d, want 32 (AES-256)", got)
	}
}

func TestDeriveSiteOrigin(t *testing.T) {
	cases := []struct{ in, want string }{
		{"https://drop.example.net/oauth/callback", "https://drop.example.net"},
		{"http://localhost:5173/oauth/callback", "http://localhost:5173"},
		{"", ""},
		{"not a url", ""},
	}
	for _, c := range cases {
		if got := deriveSiteOrigin(c.in); got != c.want {
			t.Errorf("deriveSiteOrigin(%q) = %q, want %q", c.in, got, c.want)
		}
	}
}
