package push

import (
	"context"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"log"
	"sync"
	"sync/atomic"
	"time"

	webpush "github.com/SherClockHolmes/webpush-go"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

var (
	ToplamGonderilen int64
	ToplamBasarisiz  int64
)

const rateLimitSure = 5 * time.Second

type PushService struct {
	koleksiyon  func(string) *mongo.Collection
	ayarlar     *PushAyarlar
	ayarMu      sync.RWMutex
	sonYukleme  time.Time
	sonGonderim map[string]time.Time
	sonGondMu   sync.Mutex
}

type PushAyarlar struct {
	VapidPublicKey  string `json:"vapidPublicKey" bson:"vapidPublicKey"`
	VapidPrivateKey string `json:"vapidPrivateKey" bson:"vapidPrivateKey"`
	VapidSubject    string `json:"vapidSubject" bson:"vapidSubject"`
}

type PushAbonelik struct {
	Endpoint string       `json:"endpoint" bson:"endpoint"`
	Keys     AbonelikKeys `json:"keys" bson:"keys"`
}

type AbonelikKeys struct {
	P256dh string `json:"p256dh" bson:"p256dh"`
	Auth   string `json:"auth" bson:"auth"`
}

type PushMesaj struct {
	Baslik string `json:"baslik"`
	Mesaj  string `json:"mesaj"`
	Url    string `json:"url,omitempty"`
	Tag    string `json:"tag,omitempty"`
	Ikon   string `json:"ikon,omitempty"`
}

type Analytics struct {
	ToplamGonderilen int64 `json:"toplamGonderilen"`
	ToplamBasarisiz  int64 `json:"toplamBasarisiz"`
}

func YeniPushService(koleksiyon func(string) *mongo.Collection) *PushService {
	svc := &PushService{
		koleksiyon:  koleksiyon,
		sonGonderim: make(map[string]time.Time),
	}
	svc.indexOlustur()
	return svc
}

func (s *PushService) indexOlustur() {
	col := s.koleksiyon("hareketler")
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	col.Indexes().CreateOne(ctx, mongo.IndexModel{
		Keys:    bson.D{{Key: "endpoint", Value: 1}},
		Options: options.Index().SetSparse(true),
	})

	col.Indexes().CreateOne(ctx, mongo.IndexModel{
		Keys: bson.D{{Key: "vt", Value: 1}, {Key: "kullaniciId", Value: 1}},
	})
}

func (s *PushService) AyarlariYukle() (*PushAyarlar, error) {
	s.ayarMu.RLock()
	if s.ayarlar != nil && time.Since(s.sonYukleme) < 5*time.Minute {
		defer s.ayarMu.RUnlock()
		return s.ayarlar, nil
	}
	s.ayarMu.RUnlock()

	s.ayarMu.Lock()
	defer s.ayarMu.Unlock()

	if s.ayarlar != nil && time.Since(s.sonYukleme) < 5*time.Minute {
		return s.ayarlar, nil
	}

	col := s.koleksiyon("hareketler")
	var doc bson.M
	err := col.FindOne(context.TODO(), bson.M{"vt": "FikoYT.sistem.ayarlar.push"}).Decode(&doc)
	if err == mongo.ErrNoDocuments {
		ayar, genErr := s.vapidOlusturVeKaydet(col)
		if genErr != nil {
			return nil, genErr
		}
		s.ayarlar = ayar
		s.sonYukleme = time.Now()
		return ayar, nil
	}
	if err != nil {
		return nil, fmt.Errorf("push ayarları okunamadı: %w", err)
	}

	ayar := &PushAyarlar{
		VapidPublicKey:  strVal(doc["vapidPublicKey"]),
		VapidPrivateKey: strVal(doc["vapidPrivateKey"]),
		VapidSubject:    strVal(doc["vapidSubject"]),
	}
	s.ayarlar = ayar
	s.sonYukleme = time.Now()
	return ayar, nil
}

func (s *PushService) vapidOlusturVeKaydet(col *mongo.Collection) (*PushAyarlar, error) {
	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, fmt.Errorf("VAPID key üretilemedi: %w", err)
	}

	pubBytes := elliptic.Marshal(privateKey.Curve, privateKey.PublicKey.X, privateKey.PublicKey.Y)
	publicKeyB64 := base64.RawURLEncoding.EncodeToString(pubBytes)

	privBytes := privateKey.D.Bytes()
	padded := make([]byte, 32)
	copy(padded[32-len(privBytes):], privBytes)
	privateKeyB64 := base64.RawURLEncoding.EncodeToString(padded)

	ayar := &PushAyarlar{
		VapidPublicKey:  publicKeyB64,
		VapidPrivateKey: privateKeyB64,
		VapidSubject:    "mailto:admin@fikoyt.com",
	}

	_, err = col.InsertOne(context.TODO(), bson.M{
		"vt":              "FikoYT.sistem.ayarlar.push",
		"vapidPublicKey":  ayar.VapidPublicKey,
		"vapidPrivateKey": ayar.VapidPrivateKey,
		"vapidSubject":    ayar.VapidSubject,
		"olusturma":       time.Now().Format(time.RFC3339),
	})
	if err != nil {
		return nil, fmt.Errorf("VAPID key kaydedilemedi: %w", err)
	}

	log.Println("[Push] VAPID key pair oluşturuldu ve MongoDB'ye kaydedildi")
	return ayar, nil
}

func (s *PushService) AbonelikKaydet(kullaniciId interface{}, abonelik PushAbonelik) error {
	col := s.koleksiyon("hareketler")
	_, err := col.UpdateOne(context.TODO(),
		bson.M{
			"vt":       "FikoYT.sistem.push-abonelik",
			"endpoint": abonelik.Endpoint,
		},
		bson.M{"$set": bson.M{
			"vt":          "FikoYT.sistem.push-abonelik",
			"kullaniciId": kullaniciId,
			"endpoint":    abonelik.Endpoint,
			"keys":        bson.M{"p256dh": abonelik.Keys.P256dh, "auth": abonelik.Keys.Auth},
			"guncelleme":  time.Now().Format(time.RFC3339),
		}},
		options.Update().SetUpsert(true),
	)
	return err
}

func (s *PushService) AbonelikSil(kullaniciId interface{}, endpoint string) error {
	col := s.koleksiyon("hareketler")
	_, err := col.DeleteMany(context.TODO(), bson.M{
		"vt":          "FikoYT.sistem.push-abonelik",
		"kullaniciId": kullaniciId,
		"endpoint":    endpoint,
	})
	return err
}

func (s *PushService) rateLimitKontrol(kullaniciKey string) bool {
	s.sonGondMu.Lock()
	defer s.sonGondMu.Unlock()
	son, var_ := s.sonGonderim[kullaniciKey]
	if var_ && time.Since(son) < rateLimitSure {
		return false
	}
	s.sonGonderim[kullaniciKey] = time.Now()
	return true
}

func (s *PushService) Gonder(kullaniciId interface{}, mesaj PushMesaj) error {
	kullaniciKey := fmt.Sprintf("%v", kullaniciId)
	log.Printf("[Push] Gonder cagrisi: kullaniciId=%v (%T), baslik=%s", kullaniciId, kullaniciId, mesaj.Baslik)

	if !s.rateLimitKontrol(kullaniciKey) {
		log.Printf("[Push] Rate limit: %s icin atlandi", kullaniciKey)
		return nil
	}

	ayar, err := s.AyarlariYukle()
	if err != nil {
		log.Printf("[Push] VAPID ayar yuklenemedi: %v", err)
		return err
	}

	// kullaniciId string ise ObjectId'ye çevir
	var sorguId interface{} = kullaniciId
	if idStr, ok := kullaniciId.(string); ok && len(idStr) == 24 {
		if oid, err := primitive.ObjectIDFromHex(idStr); err == nil {
			sorguId = oid
		}
	}

	col := s.koleksiyon("hareketler")
	cursor, err := col.Find(context.TODO(), bson.M{
		"vt":          "FikoYT.sistem.push-abonelik",
		"kullaniciId": sorguId,
	})
	log.Printf("[Push] Abonelik sorgusu: kullaniciId=%v (%T)", sorguId, sorguId)
	if err != nil {
		return fmt.Errorf("abonelikler okunamadı: %w", err)
	}
	defer cursor.Close(context.TODO())

	payload := fmt.Sprintf(`{"baslik":%q,"mesaj":%q,"url":%q,"tag":%q,"ikon":%q}`,
		mesaj.Baslik, mesaj.Mesaj, mesaj.Url, mesaj.Tag, mesaj.Ikon)

	opts := &webpush.Options{
		Subscriber:      ayar.VapidSubject,
		VAPIDPublicKey:  ayar.VapidPublicKey,
		VAPIDPrivateKey: ayar.VapidPrivateKey,
		TTL:             60 * 60 * 24,
	}

	var silmeEndpoints []string
	gonderimSayisi := 0

	for cursor.Next(context.TODO()) {
		var doc bson.M
		if err := cursor.Decode(&doc); err != nil {
			continue
		}

		endpoint := strVal(doc["endpoint"])
		keysDoc, _ := doc["keys"].(bson.M)
		if endpoint == "" || keysDoc == nil {
			continue
		}

		sub := &webpush.Subscription{
			Endpoint: endpoint,
			Keys: webpush.Keys{
				P256dh: strVal(keysDoc["p256dh"]),
				Auth:   strVal(keysDoc["auth"]),
			},
		}

		resp, pushErr := webpush.SendNotification([]byte(payload), sub, opts)
		if pushErr != nil {
			time.Sleep(2 * time.Second)
			resp, pushErr = webpush.SendNotification([]byte(payload), sub, opts)
		}

		if pushErr != nil {
			log.Printf("[Push] Gönderim hatası (%s): %v", endpointKisa(endpoint), pushErr)
			atomic.AddInt64(&ToplamBasarisiz, 1)
			silmeEndpoints = append(silmeEndpoints, endpoint)
			continue
		}
		resp.Body.Close()
		gonderimSayisi++
		log.Printf("[Push] Gonderildi! HTTP %d -> %s", resp.StatusCode, endpointKisa(endpoint))

		if resp.StatusCode == 410 || resp.StatusCode == 404 {
			log.Printf("[Push] Süresi dolmuş abonelik tespit edildi (HTTP %d): %s", resp.StatusCode, endpointKisa(endpoint))
			silmeEndpoints = append(silmeEndpoints, endpoint)
			atomic.AddInt64(&ToplamBasarisiz, 1)
			continue
		}

		atomic.AddInt64(&ToplamGonderilen, 1)
	}

	log.Printf("[Push] Toplam %d abonelige gonderildi (kullanici=%s)", gonderimSayisi, kullaniciKey)

	if len(silmeEndpoints) > 0 {
		go s.suresiDolmusAbonelikleriSil(silmeEndpoints)
	}

	return nil
}

func (s *PushService) TopluGonder(kullaniciIdler []interface{}, mesaj PushMesaj) {
	for _, kid := range kullaniciIdler {
		go func(id interface{}) {
			if err := s.Gonder(id, mesaj); err != nil {
				log.Printf("[Push] Toplu gönderim hatası (kullanıcı: %s): %v", id, err)
			}
		}(kid)
	}
}

func (s *PushService) suresiDolmusAbonelikleriSil(endpoints []string) {
	col := s.koleksiyon("hareketler")
	for _, ep := range endpoints {
		col.DeleteMany(context.TODO(), bson.M{
			"vt":       "FikoYT.sistem.push-abonelik",
			"endpoint": ep,
		})
	}
	log.Printf("[Push] %d süresi dolmuş abonelik silindi", len(endpoints))
}

func (s *PushService) VapidPublicKey() (string, error) {
	ayar, err := s.AyarlariYukle()
	if err != nil {
		return "", err
	}
	return ayar.VapidPublicKey, nil
}

func (s *PushService) Analytics() Analytics {
	return Analytics{
		ToplamGonderilen: atomic.LoadInt64(&ToplamGonderilen),
		ToplamBasarisiz:  atomic.LoadInt64(&ToplamBasarisiz),
	}
}

func endpointKisa(ep string) string {
	if len(ep) > 40 {
		return ep[:40]
	}
	return ep
}

func strVal(v interface{}) string {
	if s, ok := v.(string); ok {
		return s
	}
	return ""
}
