package blossom

import (
	"bytes"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"git.mleku.dev/mleku/nostr/encoders/event"
	"git.mleku.dev/mleku/nostr/encoders/hex"
	"git.mleku.dev/mleku/nostr/encoders/tag"
	"git.mleku.dev/mleku/nostr/encoders/timestamp"
)

// TestHTTPGetBlob tests GET /<sha256> endpoint
func TestHTTPGetBlob(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	// Upload a blob first
	testData := []byte("test blob content")
	sha256Hash := CalculateSHA256(testData)
	pubkey := []byte("testpubkey123456789012345678901234")

	err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
	if err != nil {
		t.Fatalf("Failed to save blob: %v", err)
	}

	sha256Hex := hex.Enc(sha256Hash)

	// Test GET request
	req := httptest.NewRequest("GET", "/"+sha256Hex, nil)
	w := httptest.NewRecorder()
	server.Handler().ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String())
	}

	body := w.Body.Bytes()
	if !bytes.Equal(body, testData) {
		t.Error("Response body mismatch")
	}

	if w.Header().Get("Content-Type") != "text/plain" {
		t.Errorf("Expected Content-Type text/plain, got %s", w.Header().Get("Content-Type"))
	}
}

// TestHTTPHeadBlob tests HEAD /<sha256> endpoint
func TestHTTPHeadBlob(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	testData := []byte("test blob content")
	sha256Hash := CalculateSHA256(testData)
	pubkey := []byte("testpubkey123456789012345678901234")

	err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
	if err != nil {
		t.Fatalf("Failed to save blob: %v", err)
	}

	sha256Hex := hex.Enc(sha256Hash)

	req := httptest.NewRequest("HEAD", "/"+sha256Hex, nil)
	w := httptest.NewRecorder()
	server.Handler().ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d", w.Code)
	}

	if w.Body.Len() != 0 {
		t.Error("HEAD request should not return body")
	}

	if w.Header().Get("Content-Length") != "17" {
		t.Errorf("Expected Content-Length 17, got %s", w.Header().Get("Content-Length"))
	}
}

// TestHTTPUpload tests PUT /upload endpoint
func TestHTTPUpload(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	_, signer := createTestKeypair(t)

	testData := []byte("test upload data")
	sha256Hash := CalculateSHA256(testData)

	// Create auth event
	authEv := createAuthEvent(t, signer, "upload", sha256Hash, 3600)

	// Create request
	req := httptest.NewRequest("PUT", "/upload", bytes.NewReader(testData))
	req.Header.Set("Authorization", createAuthHeader(authEv))
	req.Header.Set("Content-Type", "text/plain")

	w := httptest.NewRecorder()
	server.Handler().ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String())
	}

	// Parse response
	var desc BlobDescriptor
	if err := json.Unmarshal(w.Body.Bytes(), &desc); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	if desc.SHA256 != hex.Enc(sha256Hash) {
		t.Errorf("SHA256 mismatch: expected %s, got %s", hex.Enc(sha256Hash), desc.SHA256)
	}

	if desc.Size != int64(len(testData)) {
		t.Errorf("Size mismatch: expected %d, got %d", len(testData), desc.Size)
	}

	// Verify blob was saved
	exists, err := server.storage.HasBlob(sha256Hash)
	if err != nil {
		t.Fatalf("Failed to check blob: %v", err)
	}
	if !exists {
		t.Error("Blob should exist after upload")
	}
}

// TestHTTPUploadRequirements tests HEAD /upload endpoint
func TestHTTPUploadRequirements(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	testData := []byte("test data")
	sha256Hash := CalculateSHA256(testData)

	req := httptest.NewRequest("HEAD", "/upload", nil)
	req.Header.Set("X-SHA-256", hex.Enc(sha256Hash))
	req.Header.Set("X-Content-Length", "9")
	req.Header.Set("X-Content-Type", "text/plain")

	w := httptest.NewRecorder()
	server.Handler().ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d: %s", w.Code, w.Header().Get("X-Reason"))
	}
}

// TestHTTPUploadTooLarge tests upload size limit
func TestHTTPUploadTooLarge(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	// Create request with size exceeding limit
	req := httptest.NewRequest("HEAD", "/upload", nil)
	req.Header.Set("X-SHA-256", hex.Enc(CalculateSHA256([]byte("test"))))
	req.Header.Set("X-Content-Length", "200000000") // 200MB
	req.Header.Set("X-Content-Type", "application/octet-stream")

	w := httptest.NewRecorder()
	server.Handler().ServeHTTP(w, req)

	if w.Code != http.StatusRequestEntityTooLarge {
		t.Errorf("Expected status 413, got %d", w.Code)
	}
}

// TestHTTPListBlobs tests GET /list/<pubkey> endpoint
func TestHTTPListBlobs(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	_, signer := createTestKeypair(t)
	pubkey := signer.Pub()
	pubkeyHex := hex.Enc(pubkey)

	// Upload multiple blobs
	for i := 0; i < 3; i++ {
		testData := []byte("test data " + string(rune('A'+i)))
		sha256Hash := CalculateSHA256(testData)
		err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
		if err != nil {
			t.Fatalf("Failed to save blob: %v", err)
		}
	}

	// Create auth event
	authEv := createAuthEvent(t, signer, "list", nil, 3600)

	req := httptest.NewRequest("GET", "/list/"+pubkeyHex, nil)
	req.Header.Set("Authorization", createAuthHeader(authEv))

	w := httptest.NewRecorder()
	server.Handler().ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String())
	}

	var descriptors []BlobDescriptor
	if err := json.Unmarshal(w.Body.Bytes(), &descriptors); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	if len(descriptors) != 3 {
		t.Errorf("Expected 3 blobs, got %d", len(descriptors))
	}
}

// TestHTTPDeleteBlob tests DELETE /<sha256> endpoint
func TestHTTPDeleteBlob(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	_, signer := createTestKeypair(t)
	pubkey := signer.Pub()

	testData := []byte("test delete data")
	sha256Hash := CalculateSHA256(testData)

	// Upload blob first
	err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
	if err != nil {
		t.Fatalf("Failed to save blob: %v", err)
	}

	// Create auth event
	authEv := createAuthEvent(t, signer, "delete", sha256Hash, 3600)

	sha256Hex := hex.Enc(sha256Hash)
	req := httptest.NewRequest("DELETE", "/"+sha256Hex, nil)
	req.Header.Set("Authorization", createAuthHeader(authEv))

	w := httptest.NewRecorder()
	server.Handler().ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String())
	}

	// Verify blob was deleted
	exists, err := server.storage.HasBlob(sha256Hash)
	if err != nil {
		t.Fatalf("Failed to check blob: %v", err)
	}
	if exists {
		t.Error("Blob should not exist after delete")
	}
}

// TestHTTPMirror tests PUT /mirror endpoint
func TestHTTPMirror(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	_, signer := createTestKeypair(t)

	// Create a mock remote server
	testData := []byte("mirrored blob data")
	sha256Hash := CalculateSHA256(testData)
	sha256Hex := hex.Enc(sha256Hash)

	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "text/plain")
		w.Write(testData)
	}))
	defer mockServer.Close()

	// Create mirror request
	mirrorReq := map[string]string{
		"url": mockServer.URL + "/" + sha256Hex,
	}
	reqBody, _ := json.Marshal(mirrorReq)

	authEv := createAuthEvent(t, signer, "upload", sha256Hash, 3600)

	req := httptest.NewRequest("PUT", "/mirror", bytes.NewReader(reqBody))
	req.Header.Set("Authorization", createAuthHeader(authEv))
	req.Header.Set("Content-Type", "application/json")

	w := httptest.NewRecorder()
	server.Handler().ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String())
	}

	// Verify blob was saved
	exists, err := server.storage.HasBlob(sha256Hash)
	if err != nil {
		t.Fatalf("Failed to check blob: %v", err)
	}
	if !exists {
		t.Error("Blob should exist after mirror")
	}
}

// TestHTTPMediaUpload tests PUT /media endpoint
func TestHTTPMediaUpload(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	_, signer := createTestKeypair(t)

	testData := []byte("test media data")
	sha256Hash := CalculateSHA256(testData)

	authEv := createAuthEvent(t, signer, "media", sha256Hash, 3600)

	req := httptest.NewRequest("PUT", "/media", bytes.NewReader(testData))
	req.Header.Set("Authorization", createAuthHeader(authEv))
	req.Header.Set("Content-Type", "image/png")

	w := httptest.NewRecorder()
	server.Handler().ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String())
	}

	var desc BlobDescriptor
	if err := json.Unmarshal(w.Body.Bytes(), &desc); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	if desc.SHA256 == "" {
		t.Error("Expected SHA256 in response")
	}
}

// TestHTTPReport tests PUT /report endpoint
func TestHTTPReport(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	_, signer := createTestKeypair(t)
	pubkey := signer.Pub()

	// Upload a blob first
	testData := []byte("test blob")
	sha256Hash := CalculateSHA256(testData)

	err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
	if err != nil {
		t.Fatalf("Failed to save blob: %v", err)
	}

	// Create report event (kind 1984)
	reportEv := &event.E{
		CreatedAt: timestamp.Now().V,
		Kind:      1984,
		Tags:      tag.NewS(tag.NewFromAny("x", hex.Enc(sha256Hash))),
		Content:   []byte("This blob violates policy"),
		Pubkey:    pubkey,
	}

	if err := reportEv.Sign(signer); err != nil {
		t.Fatalf("Failed to sign report: %v", err)
	}

	reqBody := reportEv.Serialize()
	req := httptest.NewRequest("PUT", "/report", bytes.NewReader(reqBody))
	req.Header.Set("Content-Type", "application/json")

	w := httptest.NewRecorder()
	server.Handler().ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String())
	}
}

// TestHTTPRangeRequest tests range request support
func TestHTTPRangeRequest(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	testData := []byte("0123456789abcdef")
	sha256Hash := CalculateSHA256(testData)
	pubkey := []byte("testpubkey123456789012345678901234")

	err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
	if err != nil {
		t.Fatalf("Failed to save blob: %v", err)
	}

	sha256Hex := hex.Enc(sha256Hash)

	// Test range request
	req := httptest.NewRequest("GET", "/"+sha256Hex, nil)
	req.Header.Set("Range", "bytes=4-9")

	w := httptest.NewRecorder()
	server.Handler().ServeHTTP(w, req)

	if w.Code != http.StatusPartialContent {
		t.Errorf("Expected status 206, got %d", w.Code)
	}

	body := w.Body.Bytes()
	expected := testData[4:10]
	if !bytes.Equal(body, expected) {
		t.Errorf("Range response mismatch: expected %s, got %s", string(expected), string(body))
	}

	if w.Header().Get("Content-Range") == "" {
		t.Error("Missing Content-Range header")
	}
}

// TestHTTPNotFound tests 404 handling
func TestHTTPNotFound(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	req := httptest.NewRequest("GET", "/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", nil)
	w := httptest.NewRecorder()
	server.Handler().ServeHTTP(w, req)

	if w.Code != http.StatusNotFound {
		t.Errorf("Expected status 404, got %d", w.Code)
	}
}

// TestHTTPServerIntegration tests full server integration
func TestHTTPServerIntegration(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	// Start HTTP server
	httpServer := httptest.NewServer(server.Handler())
	defer httpServer.Close()

	_, signer := createTestKeypair(t)

	// Upload blob via HTTP
	testData := []byte("integration test data")
	sha256Hash := CalculateSHA256(testData)
	sha256Hex := hex.Enc(sha256Hash)

	authEv := createAuthEvent(t, signer, "upload", sha256Hash, 3600)

	uploadReq, _ := http.NewRequest("PUT", httpServer.URL+"/upload", bytes.NewReader(testData))
	uploadReq.Header.Set("Authorization", createAuthHeader(authEv))
	uploadReq.Header.Set("Content-Type", "text/plain")

	client := &http.Client{}
	resp, err := client.Do(uploadReq)
	if err != nil {
		t.Fatalf("Failed to upload: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		t.Fatalf("Upload failed: status %d, body: %s", resp.StatusCode, string(body))
	}

	// Retrieve blob via HTTP
	getReq, _ := http.NewRequest("GET", httpServer.URL+"/"+sha256Hex, nil)
	getResp, err := client.Do(getReq)
	if err != nil {
		t.Fatalf("Failed to get blob: %v", err)
	}
	defer getResp.Body.Close()

	if getResp.StatusCode != http.StatusOK {
		t.Fatalf("Get failed: status %d", getResp.StatusCode)
	}

	body, _ := io.ReadAll(getResp.Body)
	if !bytes.Equal(body, testData) {
		t.Error("Retrieved blob data mismatch")
	}
}

// TestCORSHeaders tests CORS header handling
func TestCORSHeaders(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	req := httptest.NewRequest("GET", "/test", nil)
	w := httptest.NewRecorder()

	server.Handler().ServeHTTP(w, req)

	if w.Header().Get("Access-Control-Allow-Origin") != "*" {
		t.Error("Missing CORS header")
	}
}

// TestAuthorizationRequired tests authorization requirement
func TestAuthorizationRequired(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	// Configure server to require auth
	server.requireAuth = true

	testData := []byte("test")
	sha256Hash := CalculateSHA256(testData)
	pubkey := []byte("testpubkey123456789012345678901234")

	err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
	if err != nil {
		t.Fatalf("Failed to save blob: %v", err)
	}

	sha256Hex := hex.Enc(sha256Hash)

	// Request without auth should fail
	req := httptest.NewRequest("GET", "/"+sha256Hex, nil)
	w := httptest.NewRecorder()
	server.Handler().ServeHTTP(w, req)

	if w.Code != http.StatusUnauthorized {
		t.Errorf("Expected status 401, got %d", w.Code)
	}
}

// TestACLIntegration tests ACL integration
func TestACLIntegration(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	// Note: This test assumes ACL is configured
	// In a real scenario, you'd set up a proper ACL instance

	_, signer := createTestKeypair(t)
	testData := []byte("test")
	sha256Hash := CalculateSHA256(testData)

	authEv := createAuthEvent(t, signer, "upload", sha256Hash, 3600)

	req := httptest.NewRequest("PUT", "/upload", bytes.NewReader(testData))
	req.Header.Set("Authorization", createAuthHeader(authEv))

	w := httptest.NewRecorder()
	server.Handler().ServeHTTP(w, req)

	// Should succeed if ACL allows, or fail if not
	// The exact behavior depends on ACL configuration
	if w.Code != http.StatusOK && w.Code != http.StatusForbidden {
		t.Errorf("Unexpected status: %d", w.Code)
	}
}

// TestMimeTypeDetection tests MIME type detection from various sources
func TestMimeTypeDetection(t *testing.T) {
	tests := []struct {
		contentType string
		ext         string
		expected    string
	}{
		{"image/png", "", "image/png"},
		{"", ".png", "image/png"},
		{"", ".pdf", "application/pdf"},
		{"application/pdf", ".txt", "application/pdf"},
		{"", ".unknown", "application/octet-stream"},
		{"", "", "application/octet-stream"},
	}

	for _, tt := range tests {
		result := DetectMimeType(tt.contentType, tt.ext)
		if result != tt.expected {
			t.Errorf("DetectMimeType(%q, %q) = %q, want %q",
				tt.contentType, tt.ext, result, tt.expected)
		}
	}
}

// TestSHA256Validation tests SHA256 validation
func TestSHA256Validation(t *testing.T) {
	validHashes := []string{
		"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
		"abc123def456789012345678901234567890123456789012345678901234abcd",
	}

	invalidHashes := []string{
		"",
		"abc",
		"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855x",
		"12345",
	}

	for _, hash := range validHashes {
		if !ValidateSHA256Hex(hash) {
			t.Errorf("Hash %s should be valid", hash)
		}
	}

	for _, hash := range invalidHashes {
		if ValidateSHA256Hex(hash) {
			t.Errorf("Hash %s should be invalid", hash)
		}
	}
}

// TestBlobURLBuilding tests URL building
func TestBlobURLBuilding(t *testing.T) {
	baseURL := "https://example.com"
	sha256Hex := "abc123def456"
	ext := ".pdf"

	url := BuildBlobURL(baseURL, sha256Hex, ext)
	expected := baseURL + "/" + sha256Hex + ext

	if url != expected {
		t.Errorf("Expected %s, got %s", expected, url)
	}

	// Test without extension
	url2 := BuildBlobURL(baseURL, sha256Hex, "")
	expected2 := baseURL + "/" + sha256Hex

	if url2 != expected2 {
		t.Errorf("Expected %s, got %s", expected2, url2)
	}
}

// TestErrorResponses tests error response formatting
func TestErrorResponses(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	w := httptest.NewRecorder()

	server.setErrorResponse(w, http.StatusBadRequest, "Invalid request")

	if w.Code != http.StatusBadRequest {
		t.Errorf("Expected status %d, got %d", http.StatusBadRequest, w.Code)
	}

	if w.Header().Get("X-Reason") == "" {
		t.Error("Missing X-Reason header")
	}
}

// TestExtractSHA256FromURL tests URL hash extraction
func TestExtractSHA256FromURL(t *testing.T) {
	tests := []struct {
		url      string
		expected string
		hasError bool
	}{
		{"https://example.com/abc123def456789012345678901234567890123456789012345678901234abcd", "abc123def456789012345678901234567890123456789012345678901234abcd", false},
		{"https://example.com/user/path/abc123def456789012345678901234567890123456789012345678901234abcd.pdf", "abc123def456789012345678901234567890123456789012345678901234abcd", false},
		{"https://example.com/", "", true},
		{"no hash here", "", true},
	}

	for _, tt := range tests {
		hash, err := ExtractSHA256FromURL(tt.url)
		if tt.hasError {
			if err == nil {
				t.Errorf("Expected error for URL %s", tt.url)
			}
		} else {
			if err != nil {
				t.Errorf("Unexpected error for URL %s: %v", tt.url, err)
			}
			if hash != tt.expected {
				t.Errorf("Expected %s, got %s for URL %s", tt.expected, hash, tt.url)
			}
		}
	}
}

// TestStorageReport tests report storage
func TestStorageReport(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	sha256Hash := CalculateSHA256([]byte("test"))
	reportData := []byte("report data")

	err := server.storage.SaveReport(sha256Hash, reportData)
	if err != nil {
		t.Fatalf("Failed to save report: %v", err)
	}

	// Reports are stored but not retrieved in current implementation
	// This test verifies the operation doesn't fail
}

// BenchmarkStorageOperations benchmarks storage operations
func BenchmarkStorageOperations(b *testing.B) {
	server, cleanup := testSetup(&testing.T{})
	defer cleanup()

	testData := []byte("benchmark test data")
	sha256Hash := CalculateSHA256(testData)
	pubkey := []byte("testpubkey123456789012345678901234")

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
		_, _, _ = server.storage.GetBlob(sha256Hash)
		_ = server.storage.DeleteBlob(sha256Hash, pubkey)
	}
}

// TestConcurrentUploads tests concurrent uploads
func TestConcurrentUploads(t *testing.T) {
	server, cleanup := testSetup(t)
	defer cleanup()

	_, signer := createTestKeypair(t)

	const numUploads = 10
	done := make(chan error, numUploads)

	for i := 0; i < numUploads; i++ {
		go func(id int) {
			testData := []byte("concurrent test " + string(rune('A'+id)))
			sha256Hash := CalculateSHA256(testData)
			authEv := createAuthEvent(t, signer, "upload", sha256Hash, 3600)

			req := httptest.NewRequest("PUT", "/upload", bytes.NewReader(testData))
			req.Header.Set("Authorization", createAuthHeader(authEv))

			w := httptest.NewRecorder()
			server.Handler().ServeHTTP(w, req)

			if w.Code != http.StatusOK {
				done <- &testError{code: w.Code, body: w.Body.String()}
				return
			}
			done <- nil
		}(i)
	}

	for i := 0; i < numUploads; i++ {
		if err := <-done; err != nil {
			t.Errorf("Concurrent upload failed: %v", err)
		}
	}
}

type testError struct {
	code int
	body string
}

func (e *testError) Error() string {
	return strings.Join([]string{"HTTP", string(rune(e.code)), e.body}, " ")
}
