package main

import (
	"bytes"
	"crypto/rand"
	"testing"

	"golang.org/x/crypto/nacl/box"
	"golang.org/x/crypto/nacl/secretbox"
)

func TestAppDataV2RoundTripUsesFreshNonce(t *testing.T) {
	fileKey := randomFileKey(t)
	plaintext := []byte("third-party AppData")
	first, err := EncryptAppData(plaintext, fileKey)
	if err != nil {
		t.Fatal(err)
	}
	second, err := EncryptAppData(plaintext, fileKey)
	if err != nil {
		t.Fatal(err)
	}
	if bytes.Equal(first, second) {
		t.Fatal("v2 writes reused a nonce")
	}
	opened, err := DecryptAppData(first, fileKey)
	if err != nil || !bytes.Equal(opened, plaintext) {
		t.Fatalf("v2 round trip failed: opened=%q err=%v", opened, err)
	}
}

func TestAppDataLegacyV1Fallback(t *testing.T) {
	fileKey := randomFileKey(t)
	plaintext := []byte("legacy")
	ciphertext := secretbox.Seal(
		nil,
		plaintext,
		&fileKey.NonceForData,
		&fileKey.FileKey,
	)
	opened, err := DecryptAppData(ciphertext, fileKey)
	if err != nil || !bytes.Equal(opened, plaintext) {
		t.Fatalf("v1 fallback failed: opened=%q err=%v", opened, err)
	}
}

func TestOpenDataFileKeySupportsCurrAndLegacyNames(t *testing.T) {
	client := NewClient()
	safePk, safeSk, err := box.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	rawKey := make([]byte, fileKeyBytes)
	legacyNonce := make([]byte, secretboxNonceBytes)
	if _, err := rand.Read(rawKey); err != nil {
		t.Fatal(err)
	}
	if _, err := rand.Read(legacyNonce); err != nil {
		t.Fatal(err)
	}
	var nonce [secretboxNonceBytes]byte
	if _, err := rand.Read(nonce[:]); err != nil {
		t.Fatal(err)
	}
	encrypted := box.Seal(nil, rawKey, &nonce, client.encPk, safeSk)

	for name, envelope := range map[string]map[string]any{
		"current": {
			"encFileKey":   encrypted,
			"nonceForKey":  nonce[:],
			"nonceForData": legacyNonce,
			"apEncPk":      safePk[:],
		},
		"legacy": {
			"k":  encrypted,
			"nk": nonce[:],
			"nd": legacyNonce,
			"p":  safePk[:],
		},
	} {
		t.Run(name, func(t *testing.T) {
			opened, err := client.OpenDataFileKey(pack(envelope))
			if err != nil {
				t.Fatal(err)
			}
			if !bytes.Equal(opened.FileKey[:], rawKey) ||
				!bytes.Equal(opened.NonceForData[:], legacyNonce) {
				t.Fatal("opened filekey does not match")
			}
		})
	}
}

func TestOpenAccountKey(t *testing.T) {
	client := NewClient()
	safePk, safeSk, err := box.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	accountKey := make([]byte, fileKeyBytes)
	if _, err := rand.Read(accountKey); err != nil {
		t.Fatal(err)
	}
	var nonce [secretboxNonceBytes]byte
	if _, err := rand.Read(nonce[:]); err != nil {
		t.Fatal(err)
	}
	payload := append(
		append([]byte{}, nonce[:]...),
		box.Seal(nil, accountKey, &nonce, client.encPk, safeSk)...,
	)
	opened, err := client.OpenAccountKey(payload, safePk[:])
	if err != nil || !bytes.Equal(opened, accountKey) {
		t.Fatalf("open Account Key failed: opened=%x err=%v", opened, err)
	}
}

func randomFileKey(t *testing.T) *FileKeyData {
	t.Helper()
	value := &FileKeyData{}
	if _, err := rand.Read(value.FileKey[:]); err != nil {
		t.Fatal(err)
	}
	if _, err := rand.Read(value.NonceForData[:]); err != nil {
		t.Fatal(err)
	}
	return value
}
