package handler

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/denisovdennis/autohero/internal/model"
	"github.com/denisovdennis/autohero/internal/storage"
	"github.com/denisovdennis/autohero/internal/telegram"
)

// PaymentsHandler handles Telegram Payments invoice creation and webhook callbacks.
type PaymentsHandler struct {
	botToken             string
	paymentProviderToken string
	store                *storage.HeroStore
	logStore             *storage.LogStore
	logger               *slog.Logger
}

// NewPaymentsHandler creates a new PaymentsHandler.
func NewPaymentsHandler(
	botToken, paymentProviderToken string,
	store *storage.HeroStore,
	logStore *storage.LogStore,
	logger *slog.Logger,
) *PaymentsHandler {
	return &PaymentsHandler{
		botToken:             botToken,
		paymentProviderToken: paymentProviderToken,
		store:                store,
		logStore:             logStore,
		logger:               logger,
	}
}

// --- Request / response types ---

type createInvoiceRequest struct {
	Type     string `json:"type"`     // "subscription_weekly", "buff_refill", "resurrection_refill"
	BuffType string `json:"buffType"` // required when type == "buff_refill"
}

type createInvoiceResponse struct {
	InvoiceURL string `json:"invoiceUrl"`
}

// --- CreateInvoice ---

// CreateInvoice generates a Telegram invoice link for the requested purchase.
// POST /api/v1/payments/create-invoice
func (h *PaymentsHandler) CreateInvoice(w http.ResponseWriter, r *http.Request) {
	telegramID, ok := resolveTelegramID(r)
	if !ok {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "missing telegramId"})
		return
	}

	var req createInvoiceRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
		return
	}

	hero, err := h.store.GetByTelegramID(r.Context(), telegramID)
	if err != nil {
		h.logger.Error("create-invoice: load hero failed", "telegram_id", telegramID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to load hero"})
		return
	}
	if hero == nil {
		writeJSON(w, http.StatusNotFound, map[string]string{"error": "hero not found"})
		return
	}

	now := time.Now()
	params, err := h.buildInvoiceParams(req, hero.ID, now)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
		return
	}

	link, err := telegram.CreateInvoiceLink(h.botToken, params)
	if err != nil {
		h.logger.Error("create-invoice: telegram API failed", "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to create invoice"})
		return
	}

	h.logger.Info("invoice link created",
		"hero_id", hero.ID,
		"type", req.Type,
		"payload", params.Payload,
	)

	writeJSON(w, http.StatusOK, createInvoiceResponse{InvoiceURL: link})
}

// buildInvoiceParams maps the client request to Telegram invoice parameters.
func (h *PaymentsHandler) buildInvoiceParams(req createInvoiceRequest, heroID int64, now time.Time) (telegram.InvoiceLinkParams, error) {
	ts := now.Unix()

	switch req.Type {
	case "subscription_weekly":
		return telegram.InvoiceLinkParams{
			Title:         "Weekly Subscription",
			Description:   "7 days of x2 buffs and x2 revives",
			Payload:       fmt.Sprintf("sub_weekly_%d_%d", heroID, ts),
			ProviderToken: h.paymentProviderToken,
			Currency:      "RUB",
			Prices: []telegram.LabeledAmount{
				{Label: "Weekly Subscription", Amount: int(model.SubscriptionWeeklyPrice() * 100)}, // rubles -> kopecks
			},
		}, nil

	case "buff_refill":
		bt, valid := model.ValidBuffType(req.BuffType)
		if !valid {
			return telegram.InvoiceLinkParams{}, fmt.Errorf("invalid buff type: %s", req.BuffType)
		}
		if bt == model.BuffResurrection {
			return telegram.InvoiceLinkParams{}, fmt.Errorf("use type \"resurrection_refill\" for resurrection")
		}
		return telegram.InvoiceLinkParams{
			Title:         fmt.Sprintf("Buff Refill: %s", strings.Title(req.BuffType)),
			Description:   fmt.Sprintf("Refill %s buff charges to maximum", req.BuffType),
			Payload:       fmt.Sprintf("buff_%s_%d_%d", req.BuffType, heroID, ts),
			ProviderToken: h.paymentProviderToken,
			Currency:      "RUB",
			Prices: []telegram.LabeledAmount{
				{Label: "Buff Refill", Amount: model.BuffRefillPrice() * 100},
			},
		}, nil

	case "resurrection_refill":
		return telegram.InvoiceLinkParams{
			Title:         "Resurrection Refill",
			Description:   "Refill Resurrection charges",
			Payload:       fmt.Sprintf("buff_resurrection_%d_%d", heroID, ts),
			ProviderToken: h.paymentProviderToken,
			Currency:      "RUB",
			Prices: []telegram.LabeledAmount{
				{Label: "Resurrection Refill", Amount: model.ResurrectionRefillPrice() * 100},
			},
		}, nil

	default:
		return telegram.InvoiceLinkParams{}, fmt.Errorf("unknown purchase type: %s", req.Type)
	}
}

// --- Telegram Webhook ---

// TelegramWebhook handles incoming Telegram Update objects for payment callbacks.
// POST /api/v1/payments/telegram-webhook
func (h *PaymentsHandler) TelegramWebhook(w http.ResponseWriter, r *http.Request) {
	var update telegramUpdate
	if err := json.NewDecoder(r.Body).Decode(&update); err != nil {
		h.logger.Warn("telegram-webhook: invalid body", "error", err)
		// Always return 200 to Telegram so it does not retry garbage.
		w.WriteHeader(http.StatusOK)
		return
	}

	// Handle pre_checkout_query — must respond within 10 seconds.
	if update.PreCheckoutQuery != nil {
		h.handlePreCheckout(update.PreCheckoutQuery)
		w.WriteHeader(http.StatusOK)
		return
	}

	// Handle successful_payment inside a message.
	if update.Message != nil && update.Message.SuccessfulPayment != nil {
		h.handleSuccessfulPayment(r.Context(), update.Message)
		w.WriteHeader(http.StatusOK)
		return
	}

	// Unknown update type — acknowledge and ignore.
	w.WriteHeader(http.StatusOK)
}

// handlePreCheckout approves a pre-checkout query after basic payload validation.
func (h *PaymentsHandler) handlePreCheckout(q *preCheckoutQuery) {
	// Validate payload format: must start with "sub_weekly_" or "buff_".
	payload := q.InvoicePayload
	valid := strings.HasPrefix(payload, "sub_weekly_") || strings.HasPrefix(payload, "buff_")
	if !valid {
		h.logger.Warn("pre_checkout: unknown payload format", "payload", payload)
		if err := telegram.AnswerPreCheckoutQuery(h.botToken, q.ID, false, "Unknown purchase type"); err != nil {
			h.logger.Error("pre_checkout: answer failed", "error", err)
		}
		return
	}

	h.logger.Info("pre_checkout: approving", "query_id", q.ID, "payload", payload)
	if err := telegram.AnswerPreCheckoutQuery(h.botToken, q.ID, true, ""); err != nil {
		h.logger.Error("pre_checkout: answer failed", "error", err)
	}
}

// handleSuccessfulPayment processes a completed Telegram payment.
func (h *PaymentsHandler) handleSuccessfulPayment(ctx context.Context, msg *telegramMessage) {
	sp := msg.SuccessfulPayment
	payload := sp.InvoicePayload

	h.logger.Info("successful_payment received",
		"payload", payload,
		"total_amount", sp.TotalAmount,
		"currency", sp.Currency,
		"telegram_charge_id", sp.TelegramPaymentChargeID,
		"provider_charge_id", sp.ProviderPaymentChargeID,
	)

	heroID, err := parseHeroIDFromPayload(payload)
	if err != nil {
		h.logger.Error("successful_payment: parse payload failed", "payload", payload, "error", err)
		return
	}

	hero, err := h.store.GetByID(ctx, heroID)
	if err != nil || hero == nil {
		h.logger.Error("successful_payment: load hero failed", "hero_id", heroID, "error", err)
		return
	}

	now := time.Now()

	switch {
	case strings.HasPrefix(payload, "sub_weekly_"):
		h.applySubscription(ctx, hero, now, sp)

	case strings.HasPrefix(payload, "buff_"):
		h.applyBuffRefill(ctx, hero, now, payload, sp)

	default:
		h.logger.Error("successful_payment: unknown payload prefix", "payload", payload)
	}
}

// applySubscription activates a weekly subscription for the hero.
func (h *PaymentsHandler) applySubscription(ctx context.Context, hero *model.Hero, now time.Time, sp *successfulPayment) {
	hero.ActivateSubscription(now)

	// Upgrade buff charges to subscriber limits.
	hero.EnsureBuffChargesPopulated(now)
	for bt := range model.BuffFreeChargesPerType {
		state := hero.GetBuffCharges(bt, now)
		subMax := hero.MaxBuffCharges(bt)
		if state.Remaining < subMax {
			state.Remaining = subMax
			hero.BuffCharges[string(bt)] = state
		}
	}

	payment := &model.Payment{
		HeroID:      hero.ID,
		Type:        "subscription_weekly",
		AmountRUB:   int(model.SubscriptionWeeklyPrice()),
		Status:      model.PaymentCompleted,
		CreatedAt:   now,
		CompletedAt: &now,
	}
	if err := h.store.CreatePayment(ctx, payment); err != nil {
		h.logger.Error("successful_payment: create payment record failed", "hero_id", hero.ID, "error", err)
	}

	if err := h.store.Save(ctx, hero); err != nil {
		h.logger.Error("successful_payment: save hero failed", "hero_id", hero.ID, "error", err)
		return
	}

	h.addLog(hero.ID, fmt.Sprintf("Subscribed for 7 days (%d₽) — x2 buffs & revives!", model.SubscriptionWeeklyPrice()))
	h.logger.Info("subscription activated via Telegram Payment",
		"hero_id", hero.ID,
		"telegram_charge_id", sp.TelegramPaymentChargeID,
		"expires_at", hero.SubscriptionExpiresAt,
	)
}

// applyBuffRefill resets a specific buff's charges after a successful payment.
func (h *PaymentsHandler) applyBuffRefill(ctx context.Context, hero *model.Hero, now time.Time, payload string, sp *successfulPayment) {
	buffTypeStr, err := parseBuffTypeFromPayload(payload)
	if err != nil {
		h.logger.Error("successful_payment: parse buff type failed", "payload", payload, "error", err)
		return
	}

	bt, valid := model.ValidBuffType(buffTypeStr)
	if !valid {
		h.logger.Error("successful_payment: invalid buff type in payload", "buff_type", buffTypeStr)
		return
	}

	priceRUB := model.BuffRefillPrice()
	paymentType := model.PaymentBuffReplenish
	if bt == model.BuffResurrection {
		priceRUB = model.ResurrectionRefillPrice()
		paymentType = model.PaymentResurrectionReplenish
	}

	hero.ResetBuffCharges(&bt, now)

	payment := &model.Payment{
		HeroID:      hero.ID,
		Type:        paymentType,
		BuffType:    string(bt),
		AmountRUB:   priceRUB,
		Status:      model.PaymentCompleted,
		CreatedAt:   now,
		CompletedAt: &now,
	}
	if err := h.store.CreatePayment(ctx, payment); err != nil {
		h.logger.Error("successful_payment: create payment record failed", "hero_id", hero.ID, "error", err)
	}

	if err := h.store.Save(ctx, hero); err != nil {
		h.logger.Error("successful_payment: save hero failed", "hero_id", hero.ID, "error", err)
		return
	}

	h.addLog(hero.ID, fmt.Sprintf("Purchased buff refill: %s (%d₽)", bt, priceRUB))
	h.logger.Info("buff refill via Telegram Payment",
		"hero_id", hero.ID,
		"buff_type", bt,
		"price_rub", priceRUB,
		"telegram_charge_id", sp.TelegramPaymentChargeID,
	)
}

// addLog writes an adventure log entry for the hero.
func (h *PaymentsHandler) addLog(heroID int64, message string) {
	if h.logStore == nil {
		return
	}
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	if err := h.logStore.Add(ctx, heroID, message); err != nil {
		h.logger.Warn("payments: failed to write adventure log", "hero_id", heroID, "error", err)
	}
}

// --- SetWebhook ---

// SetWebhook registers the Telegram webhook URL for payment callbacks.
// POST /admin/payments/set-webhook
func (h *PaymentsHandler) SetWebhook(w http.ResponseWriter, r *http.Request) {
	var req struct {
		URL string `json:"url"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.URL == "" {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "provide a non-empty url"})
		return
	}

	if err := telegram.SetWebhook(h.botToken, req.URL); err != nil {
		h.logger.Error("set-webhook failed", "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
		return
	}

	h.logger.Info("telegram webhook set", "url", req.URL)
	writeJSON(w, http.StatusOK, map[string]string{"status": "ok", "url": req.URL})
}

// --- Telegram Update types (subset needed for payments) ---

type telegramUpdate struct {
	UpdateID         int64             `json:"update_id"`
	PreCheckoutQuery *preCheckoutQuery `json:"pre_checkout_query,omitempty"`
	Message          *telegramMessage  `json:"message,omitempty"`
}

type preCheckoutQuery struct {
	ID               string `json:"id"`
	From             tgUser `json:"from"`
	Currency         string `json:"currency"`
	TotalAmount      int    `json:"total_amount"`
	InvoicePayload   string `json:"invoice_payload"`
}

type telegramMessage struct {
	MessageID         int                `json:"message_id"`
	From              *tgUser            `json:"from,omitempty"`
	SuccessfulPayment *successfulPayment `json:"successful_payment,omitempty"`
}

type successfulPayment struct {
	Currency                string `json:"currency"`
	TotalAmount             int    `json:"total_amount"`
	InvoicePayload          string `json:"invoice_payload"`
	TelegramPaymentChargeID string `json:"telegram_payment_charge_id"`
	ProviderPaymentChargeID string `json:"provider_payment_charge_id"`
}

type tgUser struct {
	ID        int64  `json:"id"`
	IsBot     bool   `json:"is_bot"`
	FirstName string `json:"first_name"`
}

// --- Payload parsing helpers ---

// parseHeroIDFromPayload extracts the hero ID from a payment payload string.
// Payload formats:
//
//	"sub_weekly_{heroID}_{timestamp}"
//	"buff_{buffType}_{heroID}_{timestamp}"
func parseHeroIDFromPayload(payload string) (int64, error) {
	parts := strings.Split(payload, "_")

	switch {
	case strings.HasPrefix(payload, "sub_weekly_") && len(parts) >= 4:
		// sub_weekly_{heroID}_{ts}
		return strconv.ParseInt(parts[2], 10, 64)

	case strings.HasPrefix(payload, "buff_") && len(parts) >= 4:
		// buff_{type}_{heroID}_{ts}
		return strconv.ParseInt(parts[2], 10, 64)

	default:
		return 0, fmt.Errorf("unrecognized payload format: %s", payload)
	}
}

// parseBuffTypeFromPayload extracts the buff type string from a buff refill payload.
// "buff_{type}_{heroID}_{ts}" -> "{type}"
func parseBuffTypeFromPayload(payload string) (string, error) {
	parts := strings.Split(payload, "_")
	if len(parts) < 4 || parts[0] != "buff" {
		return "", fmt.Errorf("invalid buff payload format: %s", payload)
	}
	return parts[1], nil
}
