package billing

import (
	"context"
	"fmt"

	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/catalogue"
	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/db"
	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/models"
	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/paddle"
	"go.mongodb.org/mongo-driver/v2/bson"
)

type transactionData struct {
	ID             string `json:"id"`
	SubscriptionID string `json:"subscription_id"`
	Origin         string `json:"origin"`
	Items          []struct {
		Price struct {
			ID string `json:"id"`
		} `json:"price"`
		Quantity int `json:"quantity"`
	} `json:"items"`
}

// handleTransactionCompleted issues the next term's licence on a renewal.
//
// A renewal is the one moment a scheduled REDUCTION takes effect: the customer's
// desired (smaller) configuration becomes granted. Mid-term reductions never
// shrink a live licence. On a first charge (origin not recurring) the
// subscription.created/updated handler already issued, so this is a no-op to
// avoid a double issue.
func handleTransactionCompleted(ctx context.Context, ev Event) error {
	d, err := decode[transactionData](ev)
	if err != nil {
		return err
	}
	if d.Origin != "subscription_recurring" {
		return nil
	}
	if d.SubscriptionID == "" {
		return fmt.Errorf("renewal transaction %s has no subscription_id", d.ID)
	}

	var sub models.Subscription
	if err := db.Admin("subscriptions").FindOne(ctx,
		bson.M{"paddle_subscription_id": d.SubscriptionID}).Decode(&sub); err != nil {
		return fmt.Errorf("renewal for unknown subscription %s: %w", d.SubscriptionID, err)
	}

	var inst models.Instance
	if err := db.Admin("admin_instances").FindOne(ctx,
		bson.M{"instance_id": sub.InstanceID}).Decode(&inst); err != nil {
		return fmt.Errorf("renewal names unknown instance %s: %w", sub.InstanceID, err)
	}

	// Prefer the transaction's own item list (authoritative for this period);
	// fall back to the subscription's recorded items.
	items := make([]catalogue.Item, 0, len(d.Items))
	for _, it := range d.Items {
		items = append(items, catalogue.Item{PriceID: it.Price.ID, Quantity: it.Quantity})
	}
	if len(items) == 0 {
		for _, it := range sub.Items {
			items = append(items, catalogue.Item{PriceID: it.PriceID, Quantity: it.Quantity})
		}
	}
	match, err := catalogue.ResolveItems(ctx, paddle.Get().Env(), items)
	if err != nil {
		return fmt.Errorf("resolve renewal items for %s: %w", d.SubscriptionID, err)
	}

	// Collapse a scheduled reduction: desired becomes granted, and the pending
	// marker is cleared, since a new term has begun. This is the only place a
	// licence ever gets a smaller cap.
	if err := promoteScheduledReduction(ctx, inst.InstanceID); err != nil {
		return err
	}

	// Issue the next term. Renewal resets relink_count inside licensing.Issue.
	if err := promoteAndIssue(ctx, &inst, match, models.ReasonRenewal); err != nil {
		return err
	}

	// Clear lifecycle notices so the next term starts the sequence fresh (mirrors
	// the self-serve renew path).
	_, _ = db.Admin("admin_instances").UpdateOne(ctx,
		bson.M{"instance_id": inst.InstanceID},
		bson.M{"$unset": bson.M{"notices_sent": ""}})
	return nil
}

// promoteScheduledReduction collapses a pending reduction into granted at
// renewal and clears scheduled_change_at. A no-op when nothing is pending — the
// match resolved from the renewal's items is authoritative either way, so this
// only keeps the entitlement's own bookkeeping honest.
func promoteScheduledReduction(ctx context.Context, instanceID string) error {
	ent, err := models.GetEntitlement(ctx, instanceID)
	if err != nil {
		return nil // no entitlement to reconcile
	}
	if ent.ScheduledChangeAt == nil {
		return nil
	}
	ent.Granted = ent.Desired
	ent.ScheduledChangeAt = nil
	return models.UpsertEntitlement(ctx, *ent)
}

// handlePaymentFailed records the failure for staff visibility. No licence
// action — the licence runs to its (grace-padded) expiry and Paddle retries.
func handlePaymentFailed(ctx context.Context, ev Event) error {
	d, err := decode[transactionData](ev)
	if err != nil {
		return err
	}
	if d.SubscriptionID == "" {
		return nil
	}
	_, err = db.Admin("subscriptions").UpdateOne(ctx,
		bson.M{"paddle_subscription_id": d.SubscriptionID},
		bson.M{"$set": bson.M{"status": models.SubPastDue}})
	return err
}
