package economy

import (
	"context"
	"crypto"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"net/http"
	"strings"
	"time"
)

// AdVerifier تأیید می‌کند که کاربر یک تبلیغ rewarded را کامل دیده است.
type AdVerifier interface {
	Verify(ctx context.Context, userID int64, token string) (bool, error)
}

// IAPProof داده‌های لازم برای تأیید یک خرید درون‌برنامه‌ای.
// برای مایکت: Store/ProductID/Token کافی است (تأیید سمت‌سرور با access token).
// برای کافه‌بازار: PurchaseData + Signature برای تأیید امضای RSA آفلاین.
type IAPProof struct {
	Store        string
	ProductID    string
	Token        string
	PurchaseData string
	Signature    string
}

// IAPVerifier تأیید می‌کند که یک خرید درون‌برنامه‌ای معتبر و پرداخت‌شده است.
type IAPVerifier interface {
	Verify(ctx context.Context, p IAPProof) (bool, error)
}

// --- استاب توسعه: هر توکن غیرخالی را معتبر می‌داند (فقط برای محیط dev/تست) ---

type DevAdVerifier struct{}

func (DevAdVerifier) Verify(_ context.Context, _ int64, token string) (bool, error) {
	return token != "", nil
}

type DevIAPVerifier struct{}

func (DevIAPVerifier) Verify(_ context.Context, p IAPProof) (bool, error) {
	return p.Token != "", nil
}

// --- آداپتر تپسل (تأیید سمت‌سرور تبلیغ rewarded) ---
//
// تپسل پس از پایان کامل تبلیغ، یک توکن/شناسه می‌دهد که سرور باید آن را
// نزد تپسل اعتبارسنجی کند. آدرس و قالب دقیق بسته به پنل تپسل تنظیم می‌شود؛
// اینجا اسکلت فراخوانی با کلید قرار داده شده است.
type TapsellAdVerifier struct {
	APIKey  string
	BaseURL string // پیش‌فرض اگر خالی باشد ست می‌شود
	client  *http.Client
}

func NewTapsellAdVerifier(apiKey string) *TapsellAdVerifier {
	return &TapsellAdVerifier{
		APIKey:  apiKey,
		BaseURL: "https://api.tapsell.ir",
		client:  &http.Client{Timeout: 5 * time.Second},
	}
}

func (t *TapsellAdVerifier) Verify(ctx context.Context, userID int64, token string) (bool, error) {
	if t.APIKey == "" || token == "" {
		return false, fmt.Errorf("tapsell: missing api key or token")
	}
	// TODO: مسیر دقیق اعتبارسنجی تپسل را مطابق مستندات پنل تنظیم کنید.
	url := fmt.Sprintf("%s/rewarded/verify?token=%s", t.BaseURL, token)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return false, err
	}
	req.Header.Set("Authorization", t.APIKey)
	resp, err := t.client.Do(req)
	if err != nil {
		return false, err
	}
	defer resp.Body.Close()
	var out struct {
		Valid     bool `json:"valid"`
		Completed bool `json:"completed"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		return false, err
	}
	return out.Valid && out.Completed, nil
}

// --- مایکت: تأیید امضای RSA آفلاین (اولویت) یا سمت‌سرور با X-Access-Token ---
//
// افزونه‌ی myket_iap مثلِ کافه‌بازار، purchaseData (JSONِ خام) و signature (base64)
// برمی‌گرداند که با کلیدِ عمومیِ RSA و SHA1withRSA به‌صورتِ آفلاین تأیید می‌شوند —
// بدونِ رفت‌وبرگشتِ شبکه به‌ازای هر خرید. اگر کلیدِ عمومی تنظیم نشده باشد یا امضا
// ارسال نشده باشد، به تأییدِ سمت‌سرور برمی‌گردیم:
// GET https://developer.myket.ir/api/applications/{pkg}/purchases/products/{sku}/tokens/{token}
// هدر: X-Access-Token: <accessToken> . پاسخ purchaseState=0 یعنی معتبر (غیرِ صفر=مسترد).
type MyketVerifier struct {
	PackageName string
	AccessToken string
	pub         *rsa.PublicKey // کلیدِ عمومیِ RSA برای تأییدِ آفلاین (اختیاری)
	client      *http.Client
}

// NewMyketVerifier یک تأییدکننده با access token (سمت‌سرور) می‌سازد.
func NewMyketVerifier(pkg, accessToken string) *MyketVerifier {
	return &MyketVerifier{
		PackageName: pkg,
		AccessToken: accessToken,
		client:      &http.Client{Timeout: 6 * time.Second},
	}
}

// WithRSAKey کلیدِ عمومیِ RSA را برای تأییدِ آفلاین ضمیمه می‌کند (اگر خالی نباشد).
func (v *MyketVerifier) WithRSAKey(publicKey string) (*MyketVerifier, error) {
	if strings.TrimSpace(publicKey) == "" {
		return v, nil
	}
	pub, err := parseRSAPublicKey(publicKey)
	if err != nil {
		return nil, fmt.Errorf("myket rsa: %w", err)
	}
	v.pub = pub
	return v, nil
}

func (v *MyketVerifier) Verify(ctx context.Context, p IAPProof) (bool, error) {
	// مسیرِ ترجیحی: تأییدِ امضای آفلاین (اگر کلید و امضا موجود باشند).
	if v.pub != nil && p.PurchaseData != "" && p.Signature != "" {
		sig, err := base64.StdEncoding.DecodeString(p.Signature)
		if err != nil {
			return false, fmt.Errorf("myket: bad signature encoding: %w", err)
		}
		h := sha1.Sum([]byte(p.PurchaseData))
		if err := rsa.VerifyPKCS1v15(v.pub, crypto.SHA1, h[:], sig); err != nil {
			return false, nil // امضای نامعتبر ⇒ خریدِ نامعتبر
		}
		return true, nil
	}
	// مسیرِ جایگزین: تأییدِ سمت‌سرور با access token.
	if v.AccessToken == "" || p.Token == "" {
		return false, fmt.Errorf("myket: missing access token or purchase token")
	}
	url := fmt.Sprintf(
		"https://developer.myket.ir/api/applications/%s/purchases/products/%s/tokens/%s",
		v.PackageName, p.ProductID, p.Token)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return false, err
	}
	req.Header.Set("X-Access-Token", v.AccessToken)
	resp, err := v.client.Do(req)
	if err != nil {
		return false, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return false, nil // ۴۰۴/۴۰۱ ⇒ توکنِ نامعتبر
	}
	var out struct {
		PurchaseState int `json:"purchaseState"` // ۰=خریداری‌شده، غیرِصفر=مسترد
	}
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		return false, err
	}
	return out.PurchaseState == 0, nil
}

// --- کافه‌بازار: تأیید امضای RSA آفلاین (فقط کلید عمومی لازم است) ---
//
// کلاینت purchaseData (متنِ خامِ JSON) و signature (base64) را می‌فرستد؛ صحتِ امضا
// با کلیدِ عمومیِ RSA و الگوریتم SHA1withRSA (مانند Google Play/Bazaar) بررسی می‌شود.
type BazaarRSAVerifier struct {
	pub *rsa.PublicKey
}

// NewBazaarRSAVerifier کلیدِ عمومی را از رشته‌ی base64 (DER/PKIX) یا PEM می‌پذیرد.
func NewBazaarRSAVerifier(publicKey string) (*BazaarRSAVerifier, error) {
	pub, err := parseRSAPublicKey(publicKey)
	if err != nil {
		return nil, err
	}
	return &BazaarRSAVerifier{pub: pub}, nil
}

func (v *BazaarRSAVerifier) Verify(_ context.Context, p IAPProof) (bool, error) {
	if p.PurchaseData == "" || p.Signature == "" {
		return false, fmt.Errorf("bazaar: missing purchase data or signature")
	}
	sig, err := base64.StdEncoding.DecodeString(p.Signature)
	if err != nil {
		return false, fmt.Errorf("bazaar: bad signature encoding: %w", err)
	}
	h := sha1.Sum([]byte(p.PurchaseData))
	if err := rsa.VerifyPKCS1v15(v.pub, crypto.SHA1, h[:], sig); err != nil {
		return false, nil // امضای نامعتبر ⇒ خریدِ نامعتبر (نه خطای سرور)
	}
	return true, nil
}

// parseRSAPublicKey کلیدِ عمومیِ RSA را از PEM یا base64ِ خامِ DER (خروجیِ کنسولِ
// توسعه‌دهنده) استخراج می‌کند.
func parseRSAPublicKey(key string) (*rsa.PublicKey, error) {
	key = strings.TrimSpace(key)
	var der []byte
	if strings.Contains(key, "BEGIN") {
		block, _ := pem.Decode([]byte(key))
		if block == nil {
			return nil, fmt.Errorf("rsa: invalid PEM")
		}
		der = block.Bytes
	} else {
		clean := strings.NewReplacer("\n", "", "\r", "", " ", "").Replace(key)
		b, err := base64.StdEncoding.DecodeString(clean)
		if err != nil {
			return nil, fmt.Errorf("rsa: invalid base64 key: %w", err)
		}
		der = b
	}
	pub, err := x509.ParsePKIXPublicKey(der)
	if err != nil {
		return nil, fmt.Errorf("rsa: parse key: %w", err)
	}
	rsaPub, ok := pub.(*rsa.PublicKey)
	if !ok {
		return nil, fmt.Errorf("rsa: not an RSA public key")
	}
	return rsaPub, nil
}

// StoreRouter بر اساس store، تأیید را به مایکت یا کافه‌بازار می‌سپارد.
// اگر فروشگاهی پیکربندی نشده باشد و devFallback روشن باشد، از DevIAPVerifier
// استفاده می‌شود (برای توسعه/تست).
type StoreRouter struct {
	Myket       IAPVerifier
	Bazaar      IAPVerifier
	DevFallback bool
}

func (r StoreRouter) Verify(ctx context.Context, p IAPProof) (bool, error) {
	var v IAPVerifier
	switch p.Store {
	case "myket":
		v = r.Myket
	case "bazaar", "cafe", "cafebazaar":
		v = r.Bazaar
	}
	if v == nil {
		if r.DevFallback {
			return DevIAPVerifier{}.Verify(ctx, p)
		}
		return false, fmt.Errorf("iap: store %q not configured", p.Store)
	}
	return v.Verify(ctx, p)
}
