package ui

import (
	"crypto/ed25519"
	"crypto/rand"
	"encoding/base64"
	"reflect"
	"strings"
	"testing"

	"aegis/internal/cryptokit"
	"aegis/internal/identity"
)

func TestNormalizeGroups(t *testing.T) {
	got, err := normalizeGroups(" comp.lang.go, sci.crypt,comp.lang.go ")
	if err != nil {
		t.Fatal(err)
	}
	want := []string{"comp.lang.go", "sci.crypt"}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("normalizeGroups() = %v, want %v", got, want)
	}
}

func TestNormalizeGroupsRejectsInjection(t *testing.T) {
	if _, err := normalizeGroups("comp.lang.go\r\nPOST"); err == nil {
		t.Fatal("normalizeGroups() accepted protocol injection")
	}
}

func TestFormatCount(t *testing.T) {
	for _, test := range []struct {
		value int64
		want  string
	}{{0, "0"}, {999, "999"}, {1000, "1 000"}, {1234567, "1 234 567"}} {
		if got := formatCount(test.value); got != test.want {
			t.Errorf("formatCount(%d) = %q, want %q", test.value, got, test.want)
		}
	}
}

func TestBuildTextArticleUsesMIMEAndEncodesUTF8Headers(t *testing.T) {
	article, err := buildTextArticle(
		[]string{"alt.test"},
		"Élodie <elodie@example.org>",
		"Caffè e privacy",
		"Corpo UTF-8: café\nseconda riga",
	)
	if err != nil {
		t.Fatal(err)
	}
	for _, want := range []string{
		"MIME-Version: 1.0\r\n",
		"Content-Type: text/plain; charset=UTF-8\r\n",
		"Content-Transfer-Encoding: 8bit\r\n",
		"Face: ",
		"Subject: =?UTF-8?q?Caff=C3=A8_e_privacy?=\r\n",
		"From: =?UTF-8?q?=C3=89lodie?= <elodie@example.org>\r\n",
		"Corpo UTF-8: café\r\nseconda riga",
	} {
		if !strings.Contains(article, want) {
			t.Fatalf("article missing %q:\n%s", want, article)
		}
	}
	for _, line := range strings.Split(article, "\r\n") {
		if strings.Contains(line, "\n") {
			t.Fatalf("article contains bare LF: %q", line)
		}
	}
}

func TestBuildTextArticleRejectsInvalidBody(t *testing.T) {
	if _, err := buildTextArticle([]string{"alt.test"}, "reader@example.org", "Test", string([]byte{'x', 0xff})); err == nil {
		t.Fatal("buildTextArticle accepted invalid UTF-8")
	}
}

func TestBuildArticleWithoutVFaceIsAllowed(t *testing.T) {
	article, err := buildArticleWithIdentityHeaders(
		[]string{"alt.test"}, "reader@example.org", "Plain post", "body", nil,
		articleCryptoOptions{Mode: "Plain"},
	)
	if err != nil {
		t.Fatal(err)
	}
	if strings.Contains(article, "X-VFace-") || strings.Contains(article, "X-Ed25519-Pub:") {
		t.Fatalf("plain article unexpectedly contains VFace headers:\n%s", article)
	}
}

func TestPrepareArticleCryptoSignsCanonicalBody(t *testing.T) {
	public, private, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	privateSeed := base64.StdEncoding.EncodeToString(private.Seed())
	headers, contentType, transferEncoding, wireBody, err := prepareArticleCrypto(
		"hello\r\nworld",
		"Sign with Ed25519",
		articleCryptoOptions{SigningKey: privateSeed},
	)
	if err != nil {
		t.Fatal(err)
	}
	if contentType != "text/plain; charset=UTF-8" || transferEncoding != "8bit" {
		t.Fatalf("unexpected MIME metadata: %q, %q", contentType, transferEncoding)
	}
	const signaturePrefix = "X-Aegis-Signature: ed25519; "
	var signature string
	for _, header := range headers {
		if strings.HasPrefix(header, signaturePrefix) {
			signature = strings.TrimPrefix(header, signaturePrefix)
		}
	}
	if signature == "" {
		t.Fatal("missing Ed25519 signature")
	}
	if err := cryptokit.VerifyEd25519([]byte(wireBody), signature, base64.StdEncoding.EncodeToString(public)); err != nil {
		t.Fatal(err)
	}
}

func TestPrepareArticleCryptoRejectsMessageEncryption(t *testing.T) {
	if _, _, _, _, err := prepareArticleCrypto("secret body", "Encrypt with age", articleCryptoOptions{}); err == nil {
		t.Fatal("message encryption mode was accepted")
	}
}

func TestBuildArticleIncludesToHeader(t *testing.T) {
	article, err := buildArticleWithIdentityHeaders(
		[]string{"alt.test"}, "reader@example.org", "To header", "body", nil,
		articleCryptoOptions{Mode: "Plain", To: "mail2news@example.org"},
	)
	if err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(article, "To: mail2news@example.org\r\n") {
		t.Fatalf("article missing To header:\n%s", article)
	}
}

func TestBuildTextArticleWithVFaceIncludesMandatoryIdentityHeaders(t *testing.T) {
	if _, err := identity.FindCLI(); err != nil {
		t.Skip(err)
	}
	public, _, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	publicKey := base64.StdEncoding.EncodeToString(public)
	profile, err := identity.GenerateVFace("pseudonym", "reader@example.org", publicKey)
	if err != nil {
		t.Fatal(err)
	}
	article, err := buildTextArticleWithVFace(
		[]string{"alt.test"},
		"pseudonym <reader@example.org>",
		"VFace test",
		"body",
		profile,
		articleCryptoOptions{Mode: "Plain", ExpectedPublicKey: profile.PublicKey},
	)
	if err != nil {
		t.Fatal(err)
	}
	for _, want := range []string{
		"X-VFace-Version: 1\r\n",
		"X-Ed25519-Pub: " + publicKey + "\r\n",
		"Identity-Hash: " + profile.IdentityHash + "\r\n",
		"X-VFace-Hash: sha256:" + profile.IdentityHash + "\r\n",
		"X-VFace-PNG-SHA256: " + profile.PNGHash + "\r\n",
		"X-VFace-Verify: " + identity.VFaceVerificationURL + "\r\n",
	} {
		if !strings.Contains(article, want) {
			t.Fatalf("article missing %q:\n%s", want, article)
		}
	}
}
