package payments

import (
	"slices"
	"time"
)

// Source is the platform axis a chip balance is segmented by (where it was funded) and a
// benefit is stamped with (where it was bought — its origin). The two roles share this value
// set but mean different things (see docs/PAYMENTS.md §3); Source names the axis for both.
type Source string

const (
	// SourceVK is the VK platform: Votes purchases and VK rewarded ads fund here.
	SourceVK Source = "vk"
	// SourceTelegram is the Telegram platform: Stars purchases fund here.
	SourceTelegram Source = "telegram"
	// SourceDirect is the open web / native context: Robokassa purchases fund here.
	SourceDirect Source = "direct"
)

// Valid reports whether s is one of the three known platform sources.
func (s Source) Valid() bool {
	switch s {
	case SourceVK, SourceTelegram, SourceDirect:
		return true
	default:
		return false
	}
}

// SubtypeIOS is the one device subtype the gate keys on: VK on iOS is frozen for spending
// (Apple forbids spending virtual currency on digital goods outside IAP). It is trusted only
// for VK, where it rides inside the signed launch parameters.
const SubtypeIOS = "ios"

// SourceForIdentityKind maps a backend identity kind to the payments source whose segment that
// identity makes available: a vk/telegram identity to its own source, a durable email identity
// to the direct source (the web/native recovery anchor). A robot identity — or any unknown
// kind — maps to no source (second return false). Callers use it to build the present set the
// payments interface takes, since payments cannot read the account schema.
func SourceForIdentityKind(kind string) (Source, bool) {
	switch kind {
	case "vk":
		return SourceVK, true
	case "telegram":
		return SourceTelegram, true
	case "email":
		return SourceDirect, true
	default:
		return "", false
	}
}

// PresentSources maps an account's identity kinds to the payments sources they make available
// (§6), de-duplicated: vk→vk, telegram→telegram, email→direct; a robot or unknown kind maps to
// nothing. Callers pass the kinds from account.Identities so the gate — which holds no
// cross-schema identity knowledge — can resolve which segments are awake.
func PresentSources(kinds []string) []Source {
	var out []Source
	for _, k := range kinds {
		if s, ok := SourceForIdentityKind(k); ok && !has(out, s) {
			out = append(out, s)
		}
	}
	return out
}

// Context is the trusted execution context the store-compliance gate keys on: the platform
// Kind (the source the wrapper enforces) plus, for VK, the trusted Subtype (only the VK-iOS
// freeze depends on it). The zero Context (empty Kind) is an untrusted platform — the gate is
// fail-closed there: no spend, no purchase, no foreign-origin benefit, view only.
type Context struct {
	Kind    Source
	Subtype string
}

// NewContext builds a Context from the session platform's kind and subtype strings (as carried
// on the trusted X-Platform signal). An unrecognised or empty kind yields an untrusted Context.
func NewContext(kind, subtype string) Context {
	k := Source(kind)
	if !k.Valid() {
		return Context{}
	}
	return Context{Kind: k, Subtype: subtype}
}

// Trusted reports whether the platform is trusted (a known kind). An untrusted context denies
// every spend/purchase and the application of any foreign origin.
func (c Context) Trusted() bool { return c.Kind.Valid() }

// vkFrozen reports whether this is the VK-iOS purchase freeze: VK context on the trusted iOS
// subtype. Apple's ToS forbids only BUYING in-app values there, so a purchase (money -> chips) is
// refused; earning chips (rewarded ads) and SPENDING chips already in the VK wallet — earned or
// bought on the same account elsewhere (e.g. VK Android) — are legal and stay allowed.
func (c Context) vkFrozen() bool { return c.Kind == SourceVK && c.Subtype == SubtypeIOS }

// spendPriority is the fixed draw order when several segments are spendable in one context
// (D7): the "home" direct segment first, the store-funded segments after.
var spendPriority = []Source{SourceDirect, SourceVK, SourceTelegram}

// has reports whether present contains s.
func has(present []Source, s Source) bool {
	return slices.Contains(present, s)
}

// spendableSources returns the chip segments that may be SPENT in the context, in draw-priority
// order, restricted to the sources the account actually has (present). It is empty only when the
// platform is untrusted (fail-closed); VK-iOS is NOT excluded — the freeze is purchase-only, so
// spending VK-wallet chips there is allowed. Inside VK/TG only the same-named segment is spendable;
// on web/native all attached segments are, drained direct→vk→tg.
func spendableSources(c Context, present []Source) []Source {
	if !c.Trusted() {
		return nil
	}
	switch c.Kind {
	case SourceVK, SourceTelegram:
		if has(present, c.Kind) {
			return []Source{c.Kind}
		}
		return nil
	default: // direct (web/native)
		var out []Source
		for _, s := range spendPriority {
			if has(present, s) {
				out = append(out, s)
			}
		}
		return out
	}
}

// applicableOrigins returns the benefit origins that APPLY in the context, in draw-priority
// order, restricted to present sources. It mirrors spendableSources (both gate only on a trusted
// platform now that the VK-iOS freeze is purchase-only). Inside VK/TG only the same-named origin
// applies (a foreign, e.g. direct, origin never activates inside a store — the compliance wall);
// on web/native direct+vk+tg all apply, drained direct→vk→tg.
func applicableOrigins(c Context, present []Source) []Source {
	if !c.Trusted() {
		return nil
	}
	switch c.Kind {
	case SourceVK, SourceTelegram:
		if has(present, c.Kind) {
			return []Source{c.Kind}
		}
		return nil
	default: // direct (web/native)
		var out []Source
		for _, s := range spendPriority {
			if has(present, s) {
				out = append(out, s)
			}
		}
		return out
	}
}

// visibleSources returns the segments the wallet shows in the context, regardless of whether
// they are spendable: inside a store only the same-named segment (the others are invisible
// there); on web/native or an untrusted context all three (untrusted shows them view-only).
func visibleSources(c Context) []Source {
	switch c.Kind {
	case SourceVK, SourceTelegram:
		return []Source{c.Kind}
	default:
		return spendPriority
	}
}

// Segment is one chip balance the wallet shows: the source, its chip count, and whether it can
// be spent in the current context (false for a frozen VK-iOS balance or an untrusted platform).
type Segment struct {
	Source    Source
	Chips     int
	Spendable bool
}

// BenefitView is the benefit state applicable in the current context: whether ads are off (and
// until when, or forever) and how many hints are available. It aggregates over the origins
// applicable in the context (§5).
type BenefitView struct {
	AdsForever   bool
	AdsPaidUntil *time.Time
	Hints        int
}

// WalletView is the read model returned to the wallet: the visible segments plus the
// context-applicable benefits.
type WalletView struct {
	Segments []Segment
	Benefits BenefitView
}

// benefitDelta is the benefit change a spend or grant applies to one origin: hints added, a
// no-ads term in whole days (stacked from max(now, current end)), and the perpetual forever
// flag (which overrides terms).
type benefitDelta struct {
	hintsAdd  int
	noAdsDays int
	forever   bool
}

// zero reports whether the delta changes nothing.
func (d benefitDelta) zero() bool { return d.hintsAdd == 0 && d.noAdsDays == 0 && !d.forever }
