package usage

import (
	"context"
	"crypto/ecdsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"
)

// AdVerifyRequest carries the inputs an AdVerifier needs to authenticate a
// rewarded-ad receipt.
type AdVerifyRequest struct {
	// UserID is the authenticated user requesting the unlock.
	UserID int64
	// DeviceID is the device UUID that played the ad.
	DeviceID string
	// AdToken is the provider's server-side verification receipt.  For AdMob
	// SSV this is the full callback query string (everything after the '?').
	AdToken string
	// ExpectedCustomData, when non-empty, must match the receipt's custom_data
	// field (binds the receipt to this user).
	ExpectedCustomData string
}

// AdVerifier authenticates a rewarded-ad receipt with the ad network.
// Implementations must be safe for concurrent use.
type AdVerifier interface {
	// Verify returns nil if the receipt is genuine and bound to the request.
	Verify(ctx context.Context, req AdVerifyRequest) error
	// Provider names the ad network (e.g. "admob", "unity").
	Provider() string
}

// --------------------------------------------------------------------------
// AdMob Server-Side Verification (SSV)
// --------------------------------------------------------------------------

// DefaultAdMobKeyServerURL is Google's public ECDSA key endpoint for AdMob SSV.
const DefaultAdMobKeyServerURL = "https://www.gstatic.com/admob/reward/verifier-keys.json"

// AdMobVerifier verifies AdMob SSV callbacks.  It fetches and caches Google's
// ECDSA public keys, then checks the ECDSA-SHA256 signature over the callback
// content, that the receipt's custom_data matches the user, and (optionally)
// that the ad_unit is one we recognise.
type AdMobVerifier struct {
	keyServerURL   string
	httpClient     *http.Client
	allowedAdUnits map[string]struct{}
	keyTTL         time.Duration

	mu        sync.RWMutex
	keys      map[string]*ecdsa.PublicKey
	fetchedAt time.Time
}

// NewAdMobVerifier creates an AdMobVerifier.  keyServerURL empty → the default
// Google endpoint.  keyTTL <= 0 → 12h.  allowedAdUnits empty → any ad unit is
// accepted (only the signature and custom_data are enforced).
func NewAdMobVerifier(keyServerURL string, httpClient *http.Client, keyTTL time.Duration, allowedAdUnits []string) *AdMobVerifier {
	if keyServerURL == "" {
		keyServerURL = DefaultAdMobKeyServerURL
	}
	if httpClient == nil {
		httpClient = &http.Client{Timeout: 5 * time.Second}
	}
	if keyTTL <= 0 {
		keyTTL = 12 * time.Hour
	}
	allowed := make(map[string]struct{}, len(allowedAdUnits))
	for _, u := range allowedAdUnits {
		allowed[u] = struct{}{}
	}
	return &AdMobVerifier{
		keyServerURL:   keyServerURL,
		httpClient:     httpClient,
		allowedAdUnits: allowed,
		keyTTL:         keyTTL,
	}
}

// Provider implements AdVerifier.
func (v *AdMobVerifier) Provider() string { return "admob" }

// errAdMobVerify is returned for any receipt that fails authentication.
var errAdMobVerify = errors.New("admob: receipt verification failed")

// Verify implements AdVerifier for AdMob SSV.
//
// AdMob signs the callback by computing ECDSA-SHA256 over the query string up
// to (but excluding) "&signature="; the signature (web-safe base64, ASN.1 DER)
// and key_id follow.  See Google's SSV documentation.
func (v *AdMobVerifier) Verify(ctx context.Context, req AdVerifyRequest) error {
	raw := strings.TrimPrefix(req.AdToken, "?")
	if raw == "" {
		return errAdMobVerify
	}

	// Content to verify = everything before "&signature=".
	sigMarker := strings.Index(raw, "&signature=")
	if sigMarker < 0 {
		return errAdMobVerify
	}
	content := raw[:sigMarker]

	params, err := url.ParseQuery(raw)
	if err != nil {
		return errAdMobVerify
	}
	sigB64 := params.Get("signature")
	keyID := params.Get("key_id")
	if sigB64 == "" || keyID == "" {
		return errAdMobVerify
	}

	// Bind the receipt to the user / ad unit.
	if req.ExpectedCustomData != "" && params.Get("custom_data") != req.ExpectedCustomData {
		return errAdMobVerify
	}
	if len(v.allowedAdUnits) > 0 {
		if _, ok := v.allowedAdUnits[params.Get("ad_unit")]; !ok {
			return errAdMobVerify
		}
	}

	sig, err := base64.RawURLEncoding.DecodeString(sigB64)
	if err != nil {
		// Some senders include padding; fall back to standard URL encoding.
		sig, err = base64.URLEncoding.DecodeString(sigB64)
		if err != nil {
			return errAdMobVerify
		}
	}

	pub, err := v.publicKey(ctx, keyID)
	if err != nil {
		return err
	}

	digest := sha256.Sum256([]byte(content))
	if !ecdsa.VerifyASN1(pub, digest[:], sig) {
		return errAdMobVerify
	}
	return nil
}

// publicKey returns the cached ECDSA public key for keyID, refreshing the key
// set from the server when the cache is empty, stale, or missing the key.
func (v *AdMobVerifier) publicKey(ctx context.Context, keyID string) (*ecdsa.PublicKey, error) {
	v.mu.RLock()
	pub, ok := v.keys[keyID]
	fresh := time.Since(v.fetchedAt) < v.keyTTL
	v.mu.RUnlock()
	if ok && fresh {
		return pub, nil
	}

	if err := v.refreshKeys(ctx); err != nil {
		// Serve a stale cached key rather than fail outright on a transient
		// fetch error.
		if ok {
			return pub, nil
		}
		return nil, err
	}

	v.mu.RLock()
	pub, ok = v.keys[keyID]
	v.mu.RUnlock()
	if !ok {
		return nil, fmt.Errorf("admob: unknown key_id %q", keyID)
	}
	return pub, nil
}

// admobKeySet mirrors the JSON returned by the AdMob verifier-keys endpoint.
type admobKeySet struct {
	Keys []struct {
		KeyID  json.Number `json:"keyId"`
		PEM    string      `json:"pem"`
		Base64 string      `json:"base64"`
	} `json:"keys"`
}

// refreshKeys fetches and parses the public key set from the key server.
func (v *AdMobVerifier) refreshKeys(ctx context.Context) error {
	httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, v.keyServerURL, nil)
	if err != nil {
		return fmt.Errorf("admob refreshKeys: %w", err)
	}
	resp, err := v.httpClient.Do(httpReq)
	if err != nil {
		return fmt.Errorf("admob refreshKeys: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("admob refreshKeys: status %d", resp.StatusCode)
	}

	var ks admobKeySet
	if err := json.NewDecoder(resp.Body).Decode(&ks); err != nil {
		return fmt.Errorf("admob refreshKeys decode: %w", err)
	}

	parsed := make(map[string]*ecdsa.PublicKey, len(ks.Keys))
	for _, k := range ks.Keys {
		pub, err := parseECDSAPublicKey(k.PEM)
		if err != nil {
			continue // skip unparseable keys rather than failing the whole set
		}
		parsed[k.KeyID.String()] = pub
	}
	if len(parsed) == 0 {
		return errors.New("admob refreshKeys: no usable keys")
	}

	v.mu.Lock()
	v.keys = parsed
	v.fetchedAt = time.Now()
	v.mu.Unlock()
	return nil
}

// parseECDSAPublicKey parses a PEM-encoded PKIX ECDSA public key.
func parseECDSAPublicKey(pemStr string) (*ecdsa.PublicKey, error) {
	block, _ := pem.Decode([]byte(pemStr))
	if block == nil {
		return nil, errors.New("admob: invalid PEM")
	}
	anyKey, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("admob: parse pkix: %w", err)
	}
	pub, ok := anyKey.(*ecdsa.PublicKey)
	if !ok {
		return nil, errors.New("admob: not an ECDSA key")
	}
	return pub, nil
}

// --------------------------------------------------------------------------
// Unity Ads (interface placeholder)
// --------------------------------------------------------------------------

// UnityVerifier is a placeholder for Unity Ads SSV; the interface is wired now
// and the verification logic will be implemented when Unity is integrated.
type UnityVerifier struct{}

// Provider implements AdVerifier.
func (UnityVerifier) Provider() string { return "unity" }

// errUnityUnimplemented signals that Unity SSV is not yet available.
var errUnityUnimplemented = errors.New("unity: SSV verification not implemented")

// Verify implements AdVerifier.
func (UnityVerifier) Verify(context.Context, AdVerifyRequest) error {
	return errUnityUnimplemented
}
