package push

import (
	"context"
	"crypto/rand"
	"database/sql"
	"encoding/base64"
	"encoding/json"
	"log/slog"
	"net/http"
	"github.com/go-chi/chi/v5"

	"git.dustin.coffee/hobokenchicken/dumpsterChat/internal/middleware"
	webpush "github.com/SherClockHolmes/webpush-go"
)

type Handler struct {
	db        *sql.DB
	vapidPub  string
	vapidPriv string
	vapidSubj string
	logger    *slog.Logger
}

func NewHandler(db *sql.DB, vapidPub, vapidPriv, vapidSubj string, logger *slog.Logger) *Handler {
	return &Handler{
		db:        db,
		vapidPub:  vapidPub,
		vapidPriv: vapidPriv,
		vapidSubj: vapidSubj,
		logger:    logger,
	}
}

func (h *Handler) RegisterRoutes(r chi.Router) {
	r.Post("/push/subscribe", h.Subscribe)
	r.Post("/push/unsubscribe", h.Unsubscribe)
	r.Get("/push/vapid-public-key", h.GetPublicKey)
}

// @Summary Get VAPID public key
// @Description Get the VAPID public key for push notification subscriptions
// @Tags push
// @Produce json
// @Success 200 {object} map[string]string
// @Router /push/vapid-public-key [get]
func (h *Handler) GetPublicKey(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{"publicKey": h.vapidPub})
}

// @Summary Subscribe to push notifications
// @Description Subscribe to push notifications
// @Tags push
// @Accept json
// @Produce json
// @Security SessionAuth
// @Param body body object true "Push subscription with endpoint, p256dh, and auth"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 401 {object} map[string]string
// @Router /push/subscribe [post]
func (h *Handler) Subscribe(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
		return
	}

	var req struct {
		Endpoint string `json:"endpoint"`
		P256DH   string `json:"p256dh"`
		Auth     string `json:"auth"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, `{"error":"invalid body"}`, http.StatusBadRequest)
		return
	}

	_, err := h.db.ExecContext(r.Context(),
		`INSERT INTO push_subscriptions (user_id, endpoint, p256dh, auth)
		 VALUES ($1, $2, $3, $4)
		 ON CONFLICT (user_id, endpoint) DO UPDATE SET p256dh = $3, auth = $4`,
		userID, req.Endpoint, req.P256DH, req.Auth,
	)
	if err != nil {
		h.logger.Error("failed to save push subscription", "error", err)
		http.Error(w, `{"error":"failed"}`, http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{"status": "subscribed"})
}

// @Summary Unsubscribe from push notifications
// @Description Unsubscribe from push notifications
// @Tags push
// @Accept json
// @Produce json
// @Security SessionAuth
// @Param body body object true "Endpoint to unsubscribe"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 401 {object} map[string]string
// @Router /push/unsubscribe [post]
func (h *Handler) Unsubscribe(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
		return
	}

	var req struct {
		Endpoint string `json:"endpoint"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, `{"error":"invalid body"}`, http.StatusBadRequest)
		return
	}

	_, err := h.db.ExecContext(r.Context(),
		`DELETE FROM push_subscriptions WHERE user_id = $1 AND endpoint = $2`,
		userID, req.Endpoint,
	)
	if err != nil {
		h.logger.Error("failed to delete push subscription", "error", err)
		http.Error(w, `{"error":"failed"}`, http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{"status": "unsubscribed"})
}

// SendPush sends a push notification to a user.
func (h *Handler) SendPush(ctx context.Context, userID string, payload map[string]interface{}) {
	if h.vapidPub == "" || h.vapidPriv == "" {
		return
	}

	rows, err := h.db.QueryContext(ctx,
		`SELECT endpoint, p256dh, auth FROM push_subscriptions WHERE user_id = $1`,
		userID,
	)
	if err != nil {
		h.logger.Error("failed to query push subscriptions", "error", err)
		return
	}
	defer rows.Close()

	body, _ := json.Marshal(payload)

	for rows.Next() {
		var endpoint, p256dh, auth string
		if err := rows.Scan(&endpoint, &p256dh, &auth); err != nil {
			continue
		}

		sub := &webpush.Subscription{
			Endpoint: endpoint,
			Keys: webpush.Keys{
				P256dh: p256dh,
				Auth:   auth,
			},
		}

		resp, err := webpush.SendNotification(body, sub, &webpush.Options{
			Subscriber:      h.vapidSubj,
			VAPIDPublicKey:  h.vapidPub,
			VAPIDPrivateKey: h.vapidPriv,
			TTL:             30,
		})
		if err != nil {
			h.logger.Error("failed to send push", "error", err, "user_id", userID)
			if resp != nil && resp.StatusCode == 410 {
				h.db.ExecContext(ctx, `DELETE FROM push_subscriptions WHERE endpoint = $1`, endpoint)
			}
			continue
		}
		if resp != nil {
			resp.Body.Close()
		}
	}
}

// SendChannelNotification sends a push notification to all server members (except the author)
// when a new message is posted in a channel.
func (h *Handler) SendChannelNotification(ctx context.Context, channelID, authorID, channelName, content string) {
	if h.vapidPub == "" || h.vapidPriv == "" {
		return
	}

	var serverID string
	err := h.db.QueryRowContext(ctx, `SELECT server_id FROM channels WHERE id = $1`, channelID).Scan(&serverID)
	if err != nil {
		h.logger.Error("failed to find channel server", "error", err)
		return
	}

	body := content
	if len(body) > 200 {
		body = body[:200] + "..."
	}

	payload, _ := json.Marshal(map[string]interface{}{
		"title": "New message in #" + channelName,
		"body":  body,
		"url":   "/channels/" + channelID,
	})

	rows, err := h.db.QueryContext(ctx, `
		SELECT DISTINCT ps.user_id, ps.endpoint, ps.p256dh, ps.auth
		FROM push_subscriptions ps
		JOIN members m ON m.user_id = ps.user_id
		LEFT JOIN notification_settings ns ON ns.user_id = ps.user_id AND ns.channel_id = $3
		WHERE m.server_id = $1 AND ps.user_id != $2
		  AND (ns.level IS NULL OR ns.level = 'all')
	`, serverID, authorID, channelID)
	if err != nil {
		h.logger.Error("failed to query push subscriptions", "error", err)
		return
	}
	defer rows.Close()

	for rows.Next() {
		var userID, endpoint, p256dh, auth string
		if err := rows.Scan(&userID, &endpoint, &p256dh, &auth); err != nil {
			continue
		}

		sub := &webpush.Subscription{
			Endpoint: endpoint,
			Keys: webpush.Keys{
				P256dh: p256dh,
				Auth:   auth,
			},
		}

		resp, err := webpush.SendNotification(payload, sub, &webpush.Options{
			Subscriber:      h.vapidSubj,
			VAPIDPublicKey:  h.vapidPub,
			VAPIDPrivateKey: h.vapidPriv,
			TTL:             30,
		})
		if err != nil {
			h.logger.Error("failed to send push", "error", err, "user_id", userID)
			if resp != nil && resp.StatusCode == 410 {
				h.db.ExecContext(ctx, `DELETE FROM push_subscriptions WHERE endpoint = $1`, endpoint)
			}
			continue
		}
		if resp != nil {
			resp.Body.Close()
		}
	}
}

// GenerateVAPIDKeys generates a new VAPID key pair.
func GenerateVAPIDKeys() (publicKey, privateKey string, err error) {
	privateKeyBytes := make([]byte, 32)
	_, err = rand.Read(privateKeyBytes)
	if err != nil {
		return "", "", err
	}

	pubBytes := make([]byte, 65)
	copy(pubBytes, privateKeyBytes)

	publicKey = base64.RawURLEncoding.EncodeToString(pubBytes)
	privateKey = base64.RawURLEncoding.EncodeToString(privateKeyBytes)

	return publicKey, privateKey, nil
}
