package usage

import (
	"context"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"
)

// adMobTestKeyServer spins up an httptest server that serves a single ECDSA
// public key in the AdMob verifier-keys.json format, and returns the server,
// the private key, and the key id.
func adMobTestKeyServer(t *testing.T) (*httptest.Server, *ecdsa.PrivateKey, string) {
	t.Helper()
	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatalf("generate key: %v", err)
	}
	der, err := x509.MarshalPKIXPublicKey(&priv.PublicKey)
	if err != nil {
		t.Fatalf("marshal pub: %v", err)
	}
	pemBytes := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der})

	const keyID = "3335741209"
	body := map[string]any{
		"keys": []map[string]any{
			{"keyId": 3335741209, "pem": string(pemBytes), "base64": base64.StdEncoding.EncodeToString(der)},
		},
	}
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_ = json.NewEncoder(w).Encode(body)
	}))
	t.Cleanup(srv.Close)
	return srv, priv, keyID
}

// signAdMob builds a signed AdMob SSV callback query string.
func signAdMob(t *testing.T, priv *ecdsa.PrivateKey, keyID, content string) string {
	t.Helper()
	digest := sha256.Sum256([]byte(content))
	sig, err := ecdsa.SignASN1(rand.Reader, priv, digest[:])
	if err != nil {
		t.Fatalf("sign: %v", err)
	}
	sigB64 := base64.RawURLEncoding.EncodeToString(sig)
	return fmt.Sprintf("%s&signature=%s&key_id=%s", content, sigB64, keyID)
}

func TestAdMobVerifyValid(t *testing.T) {
	srv, priv, keyID := adMobTestKeyServer(t)
	v := NewAdMobVerifier(srv.URL, srv.Client(), time.Hour, []string{"ca-app-pub-123/456"})

	content := "ad_network=admob&ad_unit=ca-app-pub-123/456&custom_data=user-uuid-42&reward_amount=1&reward_item=minutes&timestamp=1700000000&transaction_id=abc123&user_id=device-1"
	token := signAdMob(t, priv, keyID, content)

	err := v.Verify(context.Background(), AdVerifyRequest{
		UserID:             42,
		DeviceID:           "device-1",
		AdToken:            token,
		ExpectedCustomData: "user-uuid-42",
	})
	if err != nil {
		t.Fatalf("expected valid receipt, got %v", err)
	}
}

func TestAdMobVerifyForgedSignature(t *testing.T) {
	srv, priv, keyID := adMobTestKeyServer(t)
	v := NewAdMobVerifier(srv.URL, srv.Client(), time.Hour, nil)

	content := "ad_unit=ca-app-pub-123/456&custom_data=u1&transaction_id=abc"
	token := signAdMob(t, priv, keyID, content)
	// Tamper with the signed content after signing → signature no longer matches.
	tampered := "ad_unit=ca-app-pub-123/456&custom_data=u1&transaction_id=EVIL" + token[len(content):]

	if err := v.Verify(context.Background(), AdVerifyRequest{AdToken: tampered}); err == nil {
		t.Fatal("expected forged signature to be rejected")
	}
}

func TestAdMobVerifyCustomDataMismatch(t *testing.T) {
	srv, priv, keyID := adMobTestKeyServer(t)
	v := NewAdMobVerifier(srv.URL, srv.Client(), time.Hour, nil)

	content := "ad_unit=ca-app-pub-123/456&custom_data=u1&transaction_id=abc"
	token := signAdMob(t, priv, keyID, content)

	if err := v.Verify(context.Background(), AdVerifyRequest{
		AdToken:            token,
		ExpectedCustomData: "someone-else",
	}); err == nil {
		t.Fatal("expected custom_data mismatch to be rejected")
	}
}

func TestAdMobVerifyDisallowedAdUnit(t *testing.T) {
	srv, priv, keyID := adMobTestKeyServer(t)
	v := NewAdMobVerifier(srv.URL, srv.Client(), time.Hour, []string{"ca-app-pub-allowed/1"})

	content := "ad_unit=ca-app-pub-OTHER/9&custom_data=u1&transaction_id=abc"
	token := signAdMob(t, priv, keyID, content)

	if err := v.Verify(context.Background(), AdVerifyRequest{AdToken: token}); err == nil {
		t.Fatal("expected disallowed ad_unit to be rejected")
	}
}

func TestAdMobVerifyMalformedToken(t *testing.T) {
	srv, _, _ := adMobTestKeyServer(t)
	v := NewAdMobVerifier(srv.URL, srv.Client(), time.Hour, nil)

	for _, tok := range []string{"", "no-signature-here", "?garbage"} {
		if err := v.Verify(context.Background(), AdVerifyRequest{AdToken: tok}); err == nil {
			t.Errorf("expected malformed token %q to be rejected", tok)
		}
	}
}

func TestUnityVerifierUnimplemented(t *testing.T) {
	var v AdVerifier = UnityVerifier{}
	if v.Provider() != "unity" {
		t.Errorf("provider = %q, want unity", v.Provider())
	}
	if err := v.Verify(context.Background(), AdVerifyRequest{}); err == nil {
		t.Error("expected Unity verifier to report unimplemented")
	}
}
