package license

// Plan is the contents of one (deployment, tier) pair at issue time.
//
// This table is the seed. The admin service owns the authoritative copy in its
// `plans` collection, and every issued licence snapshots the plan it was cut
// from — so editing a plan never rewrites an existing licence, the same rule as
// workflow_runs.steps_snapshot.
//
// Limits here are the BASE allowance: what the tier grants before anything is
// bought. A metered dimension adds to it, which is why max_servers is a real
// number at Professional and Enterprise rather than Unlimited.
type Plan struct {
	Tier         string
	Name         string
	Deployment   string
	SupportLevel string
	Limits       Limits
	Features     []string
}

// planKey is the composite the table is keyed on.
//
// Keying on tier alone was what made Free cloud-only by construction. There is
// now a self-hosted Free plan, so that guarantee is gone and the Free limit is
// enforced per account AND deployment instead. See licensing.checkFreeLimit.
type planKey struct {
	Deployment string
	Tier       string
}

// baseFree, baseProfessional and baseEnterprise are shared by both deployments.
//
// The allowances are deliberately identical across cloud and self-hosted: what
// differs between the two is the term on offer, not what you get. Duplicating
// them per deployment would be four places to forget.
var (
	baseFree = Limits{
		MaxServers: 3, MaxMonitors: 3, MaxSecretGroups: 1,
		MaxChannels: 1, AuditRetentionDays: 30,
	}
	baseProfessional = Limits{
		MaxServers: 3, MaxMonitors: Unlimited, MaxSecretGroups: Unlimited,
		MaxChannels: Unlimited, AuditRetentionDays: 365,
	}
	baseEnterprise = Limits{
		MaxServers: 10, MaxMonitors: Unlimited, MaxSecretGroups: Unlimited,
		MaxChannels: Unlimited, AuditRetentionDays: Unlimited,
	}
)

var plans = map[planKey]Plan{
	planKey{DeploymentCloud, TierFree}: {
		Tier: TierFree, Name: "Free", Deployment: DeploymentCloud,
		SupportLevel: SupportCommunity, Limits: baseFree,
		// Empty rather than nil: nil marshals as JSON null, and this table is
		// the seed every plan and licence is cut from.
		Features: []string{},
	},
	planKey{DeploymentCloud, TierProfessional}: {
		Tier: TierProfessional, Name: "Professional", Deployment: DeploymentCloud,
		SupportLevel: SupportEmail24x5, Limits: baseProfessional,
		// Console and SSO are opt-in per customer, so no tier bundles them. The
		// field stays because a future tier might.
		Features: []string{},
	},
	planKey{DeploymentCloud, TierEnterprise}: {
		Tier: TierEnterprise, Name: "Enterprise", Deployment: DeploymentCloud,
		SupportLevel: SupportEmailCall24x7, Limits: baseEnterprise,
		Features:     []string{},
	},
	planKey{DeploymentSelfHosted, TierFree}: {
		Tier: TierFree, Name: "Free", Deployment: DeploymentSelfHosted,
		SupportLevel: SupportCommunity, Limits: baseFree,
		Features:     []string{},
	},
	planKey{DeploymentSelfHosted, TierProfessional}: {
		Tier: TierProfessional, Name: "Professional", Deployment: DeploymentSelfHosted,
		SupportLevel: SupportEmail24x5, Limits: baseProfessional,
		Features:     []string{},
	},
	planKey{DeploymentSelfHosted, TierEnterprise}: {
		Tier: TierEnterprise, Name: "Enterprise", Deployment: DeploymentSelfHosted,
		SupportLevel: SupportEmailCall24x7, Limits: baseEnterprise,
		Features:     []string{},
	},
}

// PlanFor returns the seed plan for one deployment and tier.
//
// It normalises first, so a legacy self_hosted licence resolves to the plan that
// replaced it rather than to nothing.
func PlanFor(deployment, tier string) (Plan, bool) {
	deployment, tier = NormaliseTier(deployment, tier)
	p, ok := plans[planKey{deployment, tier}]
	return p, ok
}

// NormaliseTier maps a legacy tier forward.
//
// tier "self_hosted" predates deployments being separate from tiers. Such a
// licence granted what Professional now grants, on a self-hosted install, so it
// maps to exactly that. Called by PlanFor and by anything reading a tier off an
// already-signed payload.
func NormaliseTier(deployment, tier string) (string, string) {
	if tier == TierSelfHosted {
		return DeploymentSelfHosted, TierProfessional
	}
	return deployment, tier
}

// Tiers is the offer order, for any UI that lists them.
func Tiers() []string { return []string{TierFree, TierProfessional, TierEnterprise} }

// Deployments is the offer order.
func Deployments() []string { return []string{DeploymentCloud, DeploymentSelfHosted} }

// TermsFor reports which billing terms a deployment sells.
//
// Self-hosted is annual only, and the reason is in this package's doc comment: an
// offline licence cannot be revoked, so the term length IS the revocation
// window. A self-hosted monthly licence would renew that window twelve times a
// year for no commercial gain.
func TermsFor(deployment string) []string {
	if deployment == DeploymentSelfHosted {
		return []string{"annual"}
	}
	return []string{"monthly", "annual"}
}
