package blossom_test

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"
	"time"

	"fiatjaf.com/nostr"
	"fiatjaf.com/nostr/keyer"
	"fiatjaf.com/nostr/khatru"
	khatru_blossom "fiatjaf.com/nostr/khatru/blossom"
	blossomclient "fiatjaf.com/nostr/nipb0/blossom"
)

func TestBlossomBasicOperations(t *testing.T) {
	// setup two test servers
	server1 := setupTestServer(t, ":38081")
	defer server1.Close()

	server2 := setupTestServer(t, ":38082")
	defer server2.Close()

	// create signers for two different pubkeys
	secretKey1 := nostr.Generate()
	secretKey2 := nostr.Generate()
	signer1 := keyer.NewPlainKeySigner(secretKey1)
	signer2 := keyer.NewPlainKeySigner(secretKey2)

	// create clients
	client1 := blossomclient.NewClient(server1.URL, signer1)
	client2 := blossomclient.NewClient(server1.URL, signer2)
	client2Server := blossomclient.NewClient(server2.URL, signer2)

	ctx := context.Background()

	// test content
	testContent := []byte("Hello, Blossom World!")
	contentType := "text/plain"

	// 1. Upload blob from first pubkey
	t.Run("UploadBlob", func(t *testing.T) {
		reader := bytes.NewReader(testContent)
		bd, err := client1.UploadBlob(ctx, reader, contentType)
		if err != nil {
			t.Fatalf("Failed to upload blob: %v", err)
		}

		if bd.Size != len(testContent) {
			t.Errorf("Expected size %d, got %d", len(testContent), bd.Size)
		}

		// content type may include charset, just check that it starts with our expected type
		if !strings.HasPrefix(bd.Type, contentType) {
			t.Errorf("Expected type to start with %s, got %s", contentType, bd.Type)
		}

		// verify SHA256 is calculated correctly
		if len(bd.SHA256) != 64 {
			t.Errorf("Expected SHA256 to be 64 characters, got %d", len(bd.SHA256))
		}
	})

	// 2. Download the blob
	t.Run("DownloadBlob", func(t *testing.T) {
		// first get the list to find the hash
		blobs, err := client1.List(ctx)
		if err != nil {
			t.Fatalf("Failed to list blobs: %v", err)
		}

		if len(blobs) != 1 {
			t.Fatalf("Expected 1 blob, got %d", len(blobs))
		}

		hash := blobs[0].SHA256
		downloaded, err := client1.Download(ctx, hash)
		if err != nil {
			t.Fatalf("Failed to download blob: %v", err)
		}

		if !bytes.Equal(downloaded, testContent) {
			t.Errorf("Downloaded content mismatch. Expected %q, got %q", testContent, downloaded)
		}
	})

	// 3. Upload same blob from different pubkey
	t.Run("UploadSameBlobDifferentPubkey", func(t *testing.T) {
		// get the hash from the first upload
		blobs, err := client1.List(ctx)
		if err != nil {
			t.Fatalf("Failed to list blobs: %v", err)
		}

		if len(blobs) != 1 {
			t.Fatalf("Expected 1 blob, got %d", len(blobs))
		}

		hash := blobs[0].SHA256
		reader := bytes.NewReader(testContent)
		bd, err := client2.UploadBlob(ctx, reader, contentType)
		if err != nil {
			t.Fatalf("Failed to upload same blob with different pubkey: %v", err)
		}

		if bd.SHA256 != hash {
			t.Errorf("Hash mismatch for same content. Expected %s, got %s", hash, bd.SHA256)
		}
	})

	// 4. Check list for both pubkeys
	t.Run("ListBlobs", func(t *testing.T) {
		// list from first pubkey
		blobs1, err := client1.List(ctx)
		if err != nil {
			t.Fatalf("Failed to list blobs from pubkey1: %v", err)
		}

		// list from second pubkey
		blobs2, err := client2.List(ctx)
		if err != nil {
			t.Fatalf("Failed to list blobs from pubkey2: %v", err)
		}

		// both should see the same blob
		if len(blobs1) != 1 || len(blobs2) != 1 {
			t.Errorf("Expected both pubkeys to see 1 blob, got %d and %d", len(blobs1), len(blobs2))
		}

		if blobs1[0].SHA256 != blobs2[0].SHA256 {
			t.Errorf("Hash mismatch between pubkey lists: %s vs %s", blobs1[0].SHA256, blobs2[0].SHA256)
		}
	})

	// 5. Delete from first pubkey
	t.Run("DeleteFromFirstPubkey", func(t *testing.T) {
		// get the hash
		blobs, err := client1.List(ctx)
		if err != nil {
			t.Fatalf("Failed to list blobs: %v", err)
		}

		hash := blobs[0].SHA256

		// delete from first pubkey
		err = client1.Delete(ctx, hash)
		if err != nil {
			t.Fatalf("Failed to delete blob from pubkey1: %v", err)
		}

		// first pubkey should see no blobs
		blobs1, err := client1.List(ctx)
		if err != nil {
			t.Fatalf("Failed to list blobs after delete: %v", err)
		}

		// second pubkey should still see the blob
		blobs2, err := client2.List(ctx)
		if err != nil {
			t.Fatalf("Failed to list blobs from pubkey2: %v", err)
		}

		if len(blobs1) != 0 {
			t.Errorf("Expected pubkey1 to see 0 blobs after delete, got %d", len(blobs1))
		}

		if len(blobs2) != 1 {
			t.Errorf("Expected pubkey2 to still see 1 blob after pubkey1 delete, got %d", len(blobs2))
		}

		// download should still work
		downloaded, err := client2.Download(ctx, hash)
		if err != nil {
			t.Fatalf("Failed to download blob after pubkey1 delete: %v", err)
		}

		if !bytes.Equal(downloaded, testContent) {
			t.Errorf("Downloaded content mismatch after delete. Expected %q, got %q", testContent, downloaded)
		}
	})

	// 6. Upload different blobs to second server
	t.Run("UploadToSecondServer", func(t *testing.T) {
		testContent2 := []byte("Hello from server 2!")
		reader := bytes.NewReader(testContent2)
		bd, err := client2Server.UploadBlob(ctx, reader, "text/plain")
		if err != nil {
			t.Fatalf("Failed to upload blob to server2: %v", err)
		}

		// verify it appears in list
		blobs, err := client2Server.List(ctx)
		if err != nil {
			t.Fatalf("Failed to list blobs from server2: %v", err)
		}

		if len(blobs) != 1 {
			t.Errorf("Expected 1 blob on server2, got %d", len(blobs))
		}

		// verify download
		downloaded, err := client2Server.Download(ctx, bd.SHA256)
		if err != nil {
			t.Fatalf("Failed to download from server2: %v", err)
		}

		if !bytes.Equal(downloaded, testContent2) {
			t.Errorf("Downloaded content mismatch from server2")
		}
	})

	// 7. Mirror from server1 to server2
	t.Run("MirrorBetweenServers", func(t *testing.T) {
		// get a blob from server1 (should still be there from pubkey2)
		blobs1, err := client2.List(ctx)
		if err != nil {
			t.Fatalf("Failed to list blobs from server1: %v", err)
		}

		if len(blobs1) == 0 {
			t.Skip("No blobs to mirror")
		}

		sourceURL := server1.URL + "/" + blobs1[0].SHA256

		// mirror to server2
		bd, err := client2Server.MirrorBlob(ctx, sourceURL)
		if err != nil {
			t.Fatalf("Failed to mirror blob: %v", err)
		}

		// verify mirrored blob appears in server2 list
		blobs2, err := client2Server.List(ctx)
		if err != nil {
			t.Fatalf("Failed to list blobs from server2 after mirror: %v", err)
		}

		// should now have 2 blobs (original + mirrored)
		if len(blobs2) < 2 {
			t.Errorf("Expected at least 2 blobs on server2 after mirror, got %d", len(blobs2))
		}

		// verify the mirrored blob can be downloaded
		downloaded, err := client2Server.Download(ctx, bd.SHA256)
		if err != nil {
			t.Fatalf("Failed to download mirrored blob: %v", err)
		}

		// should match the original content
		originalDownloaded, err := client2.Download(ctx, blobs1[0].SHA256)
		if err != nil {
			t.Fatalf("Failed to download original blob for comparison: %v", err)
		}

		if !bytes.Equal(downloaded, originalDownloaded) {
			t.Errorf("Mirrored blob content mismatch")
		}
	})

	// 8. Final list verification
	t.Run("FinalListVerification", func(t *testing.T) {
		// check final state of both servers
		blobs1, err := client2.List(ctx) // server1 with pubkey2
		if err != nil {
			t.Fatalf("Failed final list from server1: %v", err)
		}

		blobs2, err := client2Server.List(ctx) // server2 with pubkey2
		if err != nil {
			t.Fatalf("Failed final list from server2: %v", err)
		}

		// server1 should have the original blob
		if len(blobs1) == 0 {
			t.Errorf("Expected server1 to have blobs, got 0")
		}

		// server2 should have at least the original blob + mirrored blob
		if len(blobs2) < 2 {
			t.Errorf("Expected server2 to have at least 2 blobs, got %d", len(blobs2))
		}

		t.Logf("Final state - Server1: %d blobs, Server2: %d blobs", len(blobs1), len(blobs2))
	})
}

func setupTestServer(t *testing.T, addr string) *httptest.Server {
	relay := khatru.NewRelay()

	// use memory-based blob index for testing
	memoryIndex := khatru_blossom.NewMemoryBlobIndex()

	// setup blob storage in memory
	blobStorage := make(map[string][]byte)

	bl := khatru_blossom.New(relay, "http://localhost"+addr)
	bl.Store = memoryIndex

	bl.StoreBlob = func(ctx context.Context, sha256 string, ext string, body []byte) error {
		blobStorage[sha256] = make([]byte, len(body))
		copy(blobStorage[sha256], body)
		return nil
	}

	bl.LoadBlob = func(ctx context.Context, sha256 string, ext string) (io.ReadSeeker, *url.URL, error) {
		if data, ok := blobStorage[sha256]; ok {
			return bytes.NewReader(data), nil, nil
		}
		return nil, nil, fmt.Errorf("blob not found")
	}

	bl.DeleteBlob = func(ctx context.Context, sha256 string, ext string) error {
		delete(blobStorage, sha256)
		return nil
	}

	server := httptest.NewUnstartedServer(relay)
	server.Start()

	// wait a moment for server to be ready
	time.Sleep(10 * time.Millisecond)

	return server
}
