package models

import (
	"context"

	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/db"
	"gitea.hostxtra.co.uk/mrhid6/vantage/shared/license"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
)

// Component kinds.
const (
	// KindBase is the plan's own fee, always quantity 1.
	KindBase = "base"
	// KindLimit raises a named limit by one per unit of quantity.
	KindLimit = "limit"
	// KindFeature is an on/off feature key.
	KindFeature = "feature"
)

// LimitKeyServers is the only metered limit today.
//
// A limit_key is a field name in license.Limits, which is what lets a second
// metered dimension be a catalogue row rather than a code change. There is
// deliberately no block size: with secret-group blocks dropped from the spec it
// would be 1 in every row that will ever exist.
const LimitKeyServers = "max_servers"

// CatalogueRow is one priceable component of one plan.
//
// This is the ONLY place a Paddle price ID appears anywhere in Vantage. An empty
// PriceIDs means the component is free — a feature with no price is a toggle a
// customer may take at no charge, and giving it a price later is a staff edit
// rather than a migration or a deploy.
type CatalogueRow struct {
	ID         bson.ObjectID `bson:"_id,omitempty" json:"-"`
	Kind       string        `bson:"kind" json:"kind"`
	Deployment string        `bson:"deployment" json:"deployment"`
	Tier       string        `bson:"tier" json:"tier"`
	LimitKey   string        `bson:"limit_key,omitempty" json:"limit_key,omitempty"`
	FeatureKey string        `bson:"feature_key,omitempty" json:"feature_key,omitempty"`
	// PriceIDs is environment -> term -> Paddle price ID, e.g.
	// {"sandbox": {"monthly": "pri_…"}, "production": {"annual": "pri_…"}}.
	//
	// Nested by environment rather than kept in two collections, because
	// promoting sandbox to production must be a configuration change and not a
	// data migration. The running PADDLE_ENV picks the inner map.
	PriceIDs map[string]map[string]string `bson:"price_ids,omitempty" json:"price_ids,omitempty"`
}

// PriceID returns the price for one environment and term, or "".
func (r CatalogueRow) PriceID(env, term string) string {
	if r.PriceIDs == nil {
		return ""
	}
	return r.PriceIDs[env][term]
}

// Priced reports whether this component costs anything in an environment.
func (r CatalogueRow) Priced(env string) bool {
	for _, term := range []string{"monthly", "annual"} {
		if r.PriceID(env, term) != "" {
			return true
		}
	}
	return false
}

// SeedCatalogue inserts the twenty rows the four PAID plans need: a base, a
// server limit, and one row per feature key.
//
// The two Free plans get no rows at all, and that absence is what keeps Free
// outside Paddle: with nothing to price, no checkout can be built for it. Do not
// "fix" this by adding zero-priced Free rows.
//
// $setOnInsert only, for the same reason as SeedPlans: the price IDs are pasted
// in by staff and a redeploy must not blank them.
func SeedCatalogue(ctx context.Context) error {
	paid := []string{license.TierProfessional, license.TierEnterprise}
	for _, deployment := range license.Deployments() {
		for _, tier := range paid {
			rows := []CatalogueRow{
				{Kind: KindBase, Deployment: deployment, Tier: tier},
				{Kind: KindLimit, Deployment: deployment, Tier: tier, LimitKey: LimitKeyServers},
				{Kind: KindFeature, Deployment: deployment, Tier: tier, FeatureKey: license.FeatureConsole},
				{Kind: KindFeature, Deployment: deployment, Tier: tier, FeatureKey: license.FeatureOIDC},
				{Kind: KindFeature, Deployment: deployment, Tier: tier, FeatureKey: license.FeatureVulnScanning},
			}
			for _, r := range rows {
				filter := bson.M{
					"kind":        r.Kind,
					"deployment":  r.Deployment,
					"tier":        r.Tier,
					"limit_key":   r.LimitKey,
					"feature_key": r.FeatureKey,
				}
				if _, err := db.Admin("catalogue").UpdateOne(ctx, filter,
					bson.M{"$setOnInsert": bson.M{
						"kind":        r.Kind,
						"deployment":  r.Deployment,
						"tier":        r.Tier,
						"limit_key":   r.LimitKey,
						"feature_key": r.FeatureKey,
						"price_ids":   map[string]map[string]string{},
					}},
					options.UpdateOne().SetUpsert(true)); err != nil {
					return err
				}
			}
		}
	}
	return nil
}

// CatalogueFor returns every component of one plan.
func CatalogueFor(ctx context.Context, deployment, tier string) ([]CatalogueRow, error) {
	deployment, tier = license.NormaliseTier(deployment, tier)
	cur, err := db.Admin("catalogue").Find(ctx,
		bson.M{"deployment": deployment, "tier": tier})
	if err != nil {
		return nil, err
	}
	rows := []CatalogueRow{}
	if err := cur.All(ctx, &rows); err != nil {
		return nil, err
	}
	return rows, nil
}

// AllCatalogue returns every row, for the staff editor.
func AllCatalogue(ctx context.Context) ([]CatalogueRow, error) {
	cur, err := db.Admin("catalogue").Find(ctx, bson.M{})
	if err != nil {
		return nil, err
	}
	rows := []CatalogueRow{}
	if err := cur.All(ctx, &rows); err != nil {
		return nil, err
	}
	return rows, nil
}
