package billing

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"github.com/flowy-live/llink/internal/utils/flog"

	"github.com/stripe/stripe-go/v85"
	"github.com/stripe/stripe-go/v85/webhook"
)

func (s *serviceImpl) HandleWebhook(ctx context.Context, payload []byte, signature string) error {
	event, err := webhook.ConstructEvent(payload, signature, s.cfg.WebhookSecret)
	if err != nil {
		return fmt.Errorf("verify stripe signature: %w", err)
	}

	flog.Info("stripe webhook", "type", event.Type, "id", event.ID)

	switch event.Type {
	case "checkout.session.completed":
		// subscription.created fires right after with full detail; we handle
		// the subscription there.
		return nil
	case "customer.subscription.created", "customer.subscription.updated":
		return s.handleSubscriptionUpsert(ctx, event)
	case "customer.subscription.deleted":
		return s.handleSubscriptionDeleted(ctx, event)
	default:
		return nil
	}
}

func (s *serviceImpl) handleSubscriptionUpsert(ctx context.Context, event stripe.Event) error {
	var sub stripe.Subscription
	if err := json.Unmarshal(event.Data.Raw, &sub); err != nil {
		return fmt.Errorf("decode subscription: %w", err)
	}

	networkID := sub.Metadata["network_id"]
	if networkID == "" {
		return fmt.Errorf("subscription %s missing network_id metadata", sub.ID)
	}

	local, err := subscriptionFromStripe(&sub, networkID)
	if err != nil {
		return err
	}
	if err := s.repo.upsertSubscription(ctx, local); err != nil {
		return fmt.Errorf("upsert subscription: %w", err)
	}
	return nil
}

func (s *serviceImpl) handleSubscriptionDeleted(ctx context.Context, event stripe.Event) error {
	var sub stripe.Subscription
	if err := json.Unmarshal(event.Data.Raw, &sub); err != nil {
		return fmt.Errorf("decode subscription: %w", err)
	}

	networkID := sub.Metadata["network_id"]
	if networkID == "" {
		return fmt.Errorf("subscription %s missing network_id metadata", sub.ID)
	}

	if err := s.repo.deleteSubscriptionByID(ctx, sub.ID); err != nil {
		return fmt.Errorf("delete subscription: %w", err)
	}
	return nil
}

func subscriptionFromStripe(sub *stripe.Subscription, networkID string) (*Subscription, error) {
	if len(sub.Items.Data) == 0 {
		return nil, fmt.Errorf("subscription %s has no items", sub.ID)
	}
	item := sub.Items.Data[0]
	cadence, err := cadenceFromInterval(item.Price)
	if err != nil {
		return nil, fmt.Errorf("subscription %s: %w", sub.ID, err)
	}

	var customerID string
	if sub.Customer != nil {
		customerID = sub.Customer.ID
	}

	return &Subscription{
		ID:                 sub.ID,
		NetworkID:          networkID,
		StripeCustomerID:   customerID,
		Status:             string(sub.Status),
		PriceID:            item.Price.ID,
		Cadence:            cadence,
		Quantity:           int(item.Quantity),
		CancelAtPeriodEnd:  sub.CancelAtPeriodEnd,
		CurrentPeriodStart: time.Unix(item.CurrentPeriodStart, 0).UTC(),
		CurrentPeriodEnd:   time.Unix(item.CurrentPeriodEnd, 0).UTC(),
	}, nil
}

func cadenceFromInterval(price *stripe.Price) (Cadence, error) {
	if price == nil || price.Recurring == nil {
		return "", fmt.Errorf("price is not recurring")
	}
	switch price.Recurring.Interval {
	case stripe.PriceRecurringIntervalMonth:
		return CadenceMonthly, nil
	case stripe.PriceRecurringIntervalYear:
		return CadenceAnnual, nil
	default:
		return "", fmt.Errorf("unsupported price interval %q", price.Recurring.Interval)
	}
}
