package listing

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"net/http/httptest"
	"strconv"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
)

func TestVerifyExternalUploadSignature(t *testing.T) {
	gin.SetMode(gin.TestMode)
	body := []byte(`{"data":{"loginMethod":"QQ账号密码"}}`)
	secret := "test-upload-secret"
	timestamp := time.Now().Unix()

	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	req := httptest.NewRequest("POST", "/api/open/listing-uploads", nil)
	req.Header.Set("X-HFB-Timestamp", strconvFormatInt(timestamp))
	req.Header.Set("X-HFB-Signature", signExternalUploadForTest(secret, timestamp, body))
	c.Request = req

	handler := &Handler{externalUploadSecret: secret}
	if err := handler.verifyExternalUpload(c, body); err != nil {
		t.Fatalf("verifyExternalUpload() error = %v", err)
	}
}

func TestVerifyExternalUploadAllowsUnsignedRequest(t *testing.T) {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	c.Request = httptest.NewRequest("POST", "/api/open/listing-uploads", nil)

	handler := &Handler{}
	if err := handler.verifyExternalUpload(c, []byte(`{}`)); err != nil {
		t.Fatalf("verifyExternalUpload() error = %v", err)
	}
}

func TestVerifyExternalUploadAllowsUnsignedRequestWithSecret(t *testing.T) {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	c.Request = httptest.NewRequest("POST", "/api/open/listing-uploads", nil)

	handler := &Handler{externalUploadSecret: "test-upload-secret"}
	if err := handler.verifyExternalUpload(c, []byte(`{}`)); err != nil {
		t.Fatalf("verifyExternalUpload() error = %v", err)
	}
}

func TestVerifyExternalUploadRejectsBadSignature(t *testing.T) {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	req := httptest.NewRequest("POST", "/api/open/listing-uploads", nil)
	req.Header.Set("X-HFB-Timestamp", strconvFormatInt(time.Now().Unix()))
	req.Header.Set("X-HFB-Signature", "bad-signature")
	c.Request = req

	handler := &Handler{externalUploadSecret: "test-upload-secret"}
	if err := handler.verifyExternalUpload(c, []byte(`{}`)); err != errExternalUploadSignature {
		t.Fatalf("verifyExternalUpload() error = %v, want errExternalUploadSignature", err)
	}
}

func TestVerifyExternalUploadRejectsStaleTimestamp(t *testing.T) {
	gin.SetMode(gin.TestMode)
	body := []byte(`{}`)
	secret := "test-upload-secret"
	timestamp := time.Now().Add(-10 * time.Minute).Unix()
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	req := httptest.NewRequest("POST", "/api/open/listing-uploads", nil)
	req.Header.Set("X-HFB-Timestamp", strconvFormatInt(timestamp))
	req.Header.Set("X-HFB-Signature", signExternalUploadForTest(secret, timestamp, body))
	c.Request = req

	handler := &Handler{externalUploadSecret: secret}
	if err := handler.verifyExternalUpload(c, body); err != errExternalUploadTimestamp {
		t.Fatalf("verifyExternalUpload() error = %v, want errExternalUploadTimestamp", err)
	}
}

func TestExternalUploadIPAllowed(t *testing.T) {
	tests := []struct {
		name    string
		ip      string
		allowed []string
		want    bool
	}{
		{name: "empty whitelist", ip: "203.0.113.10", allowed: nil, want: true},
		{name: "exact match", ip: "203.0.113.10", allowed: []string{"203.0.113.10"}, want: true},
		{name: "cidr match", ip: "10.1.2.3", allowed: []string{"10.0.0.0/8"}, want: true},
		{name: "blocked", ip: "203.0.113.10", allowed: []string{"198.51.100.0/24"}, want: false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := externalUploadIPAllowed(tt.ip, tt.allowed); got != tt.want {
				t.Fatalf("externalUploadIPAllowed() = %v, want %v", got, tt.want)
			}
		})
	}
}

func signExternalUploadForTest(secret string, timestamp int64, body []byte) string {
	ts := strconvFormatInt(timestamp)
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(ts))
	mac.Write([]byte("."))
	mac.Write(body)
	return hex.EncodeToString(mac.Sum(nil))
}

func strconvFormatInt(value int64) string {
	return strconv.FormatInt(value, 10)
}
