package pricing

import (
	"math"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// TestCost_OpenAICachedSubsetDiscount proves OpenAI's cached input
// tokens are billed at the configured cached_input_per_1k rate while
// the non-cached remainder of input_tokens is billed at the regular
// rate. Critical because OpenAI returns cached_tokens as a SUBSET of
// prompt_tokens — naïvely charging the cached count on top of
// prompt_tokens would double-bill that portion.
func TestCost_OpenAICachedSubsetDiscount(t *testing.T) {
	tbl := &Table{entries: map[string]map[string]Entry{
		"openai": {"gpt-4o": {
			InputPer1K:       0.0025, // 0.0025 USD per 1k input tokens
			OutputPer1K:      0.01,
			CachedInputPer1K: 0.00125, // 0.5x discount on cached
		}},
	}}
	// 1000 prompt tokens, 750 of which were cached. 250 non-cached
	// at regular rate, 750 cached at the discount rate, 500 output.
	cost, ok := tbl.Cost("openai", "gpt-4o", 1000, 500, 750, 0)
	require.True(t, ok, "known model resolves")
	want := (250.0/1000.0)*0.0025 + (750.0/1000.0)*0.00125 + (500.0/1000.0)*0.01
	assert.InDelta(t, want, cost, 1e-12,
		"cached subset must bill at the discount rate; non-cached remainder at regular rate")
}

// TestCost_OpenAICachedFallsBackToInputRate covers the fallback
// contract: when CachedInputPer1K is unset (zero), cached tokens bill
// at the regular input rate.
func TestCost_OpenAICachedFallsBackToInputRate(t *testing.T) {
	tbl := &Table{entries: map[string]map[string]Entry{
		"openai": {"gpt-4o": {InputPer1K: 0.0025, OutputPer1K: 0.01}},
	}}
	cost, ok := tbl.Cost("openai", "gpt-4o", 1000, 500, 750, 0)
	require.True(t, ok)
	want := 0.0025 + (500.0/1000.0)*0.01
	assert.InDelta(t, want, cost, 1e-12,
		"absent cached_input_per_1k rate must fall back to input_per_1k")
}

// TestCost_OpenAIClampsCachedToInputCount is the defensive guard
// against malformed upstream responses that report cached_tokens >
// prompt_tokens. We clamp so the formula never produces a negative
// "non-cached remainder" multiplied by the input rate.
func TestCost_OpenAIClampsCachedToInputCount(t *testing.T) {
	tbl := &Table{entries: map[string]map[string]Entry{
		"openai": {"gpt-4o": {InputPer1K: 0.0025, OutputPer1K: 0.01, CachedInputPer1K: 0.00125}},
	}}
	cost, ok := tbl.Cost("openai", "gpt-4o", 100, 0, 9999, 0)
	require.True(t, ok)
	// All 100 cached, 0 non-cached. Output is 0.
	want := (100.0 / 1000.0) * 0.00125
	assert.InDelta(t, want, cost, 1e-12,
		"cached count > input count must clamp to input — never bill negative non-cached tokens")
}

// TestCost_AnthropicCacheReadAndCreationAreAdditive proves the
// Anthropic shape: cache_read and cache_creation tokens are
// ADDITIVE to input_tokens (not subset), each billed at its own
// configured rate.
func TestCost_AnthropicCacheReadAndCreationAreAdditive(t *testing.T) {
	tbl := &Table{entries: map[string]map[string]Entry{
		"anthropic": {"claude-sonnet": {
			InputPer1K:         0.003,
			OutputPer1K:        0.015,
			CacheReadPer1K:     0.0003,  // 0.1x of input
			CacheCreationPer1K: 0.00375, // 1.25x of input
		}},
	}}
	// 256 regular input + 768 cache_read + 512 cache_creation +
	// 200 output. Each input bucket bills at its own rate.
	cost, ok := tbl.Cost("anthropic", "claude-sonnet", 256, 200, 768, 512)
	require.True(t, ok, "known model resolves")
	want := (256.0/1000.0)*0.003 +
		(768.0/1000.0)*0.0003 +
		(512.0/1000.0)*0.00375 +
		(200.0/1000.0)*0.015
	assert.InDelta(t, want, cost, 1e-12,
		"each Anthropic input bucket must bill at its own configured rate")
}

// TestCost_AnthropicCacheRatesFallBackToInput covers the no-rate
// path: when neither CacheReadPer1K nor CacheCreationPer1K is set,
// cache tokens bill at the regular input rate.
func TestCost_AnthropicCacheRatesFallBackToInput(t *testing.T) {
	tbl := &Table{entries: map[string]map[string]Entry{
		"anthropic": {"claude-sonnet": {InputPer1K: 0.003, OutputPer1K: 0.015}},
	}}
	cost, ok := tbl.Cost("anthropic", "claude-sonnet", 256, 200, 768, 512)
	require.True(t, ok)
	// Without overrides: every input bucket at input_per_1k.
	want := ((256.0+768.0+512.0)/1000.0)*0.003 + (200.0/1000.0)*0.015
	assert.InDelta(t, want, cost, 1e-12,
		"absent cache rates must fall back to input_per_1k")
}

// TestEntryCosts_SurfaceSelectsFormula pins that the formula branches on
// the SURFACE, not on which table the entry came from: the same entry
// bills a subset carve-out on "openai", additive buckets on
// "anthropic"/"bedrock", and ignores cache counts everywhere else. This
// is what keeps per-provider-record entries (looked up by record id)
// mathematically identical to defaults-table entries.
func TestEntryCosts_SurfaceSelectsFormula(t *testing.T) {
	e := Entry{InputPer1K: 0.002, OutputPer1K: 0.01, CachedInputPer1K: 0.001, CacheReadPer1K: 0.0002, CacheCreationPer1K: 0.0025}

	openai := EntryCosts(e, "openai", 1000, 0, 400, 300)
	assert.InDelta(t, (600.0/1000.0)*0.002+(400.0/1000.0)*0.001, openai.TotalUSD, 1e-12,
		"openai: cached is a subset, cacheCreation ignored")

	anthropic := EntryCosts(e, "anthropic", 1000, 0, 400, 300)
	assert.InDelta(t, 0.002+(400.0/1000.0)*0.0002+(300.0/1000.0)*0.0025, anthropic.TotalUSD, 1e-12,
		"anthropic: cache buckets are additive")

	bedrock := EntryCosts(e, "bedrock", 1000, 0, 400, 300)
	assert.InDelta(t, anthropic.TotalUSD, bedrock.TotalUSD, 1e-12, "bedrock shares the anthropic formula")

	other := EntryCosts(e, "gemini", 1000, 0, 400, 300)
	assert.InDelta(t, 0.002, other.TotalUSD, 1e-12, "unknown surface: cache counts ignored")
}

// TestEntryCosts_ClampsNegativeTokens: malformed upstream counts must
// never produce a negative cost.
func TestEntryCosts_ClampsNegativeTokens(t *testing.T) {
	e := Entry{InputPer1K: 0.002, OutputPer1K: 0.01}
	c := EntryCosts(e, "openai", -50, -10, -5, -3)
	assert.Zero(t, c.TotalUSD, "all-negative counts clamp to zero cost")
}

func TestTableCost_NilSafe(t *testing.T) {
	var t1 *Table
	cost, ok := t1.Cost("x", "y", 1, 1, 0, 0)
	assert.False(t, ok, "nil table reports unknown")
	assert.Zero(t, cost, "nil table returns zero cost")
	assert.False(t, t1.Has("x", "y"), "nil table has nothing")
}

func TestNewTable_ValidatesRates(t *testing.T) {
	good := map[string]map[string]EntryJSON{
		"openai": {"gpt-4o": {InputPer1K: 0.0025, OutputPer1K: 0.01, CachedInputPer1K: 0.00125}},
	}
	tbl, err := NewTable(good)
	require.NoError(t, err)
	cost, ok := tbl.Cost("openai", "gpt-4o", 1000, 1000, 0, 0)
	require.True(t, ok, "entry survives the wire conversion")
	assert.InDelta(t, 0.0125, cost, 1e-9)

	_, ok = tbl.Cost("openai", "unknown-model", 1, 1, 0, 0)
	assert.False(t, ok, "unknown model misses")

	for name, bad := range map[string]EntryJSON{
		"negative input":  {InputPer1K: -1, OutputPer1K: 0.01},
		"NaN output":      {InputPer1K: 0.01, OutputPer1K: math.NaN()},
		"Inf cache read":  {InputPer1K: 0.01, OutputPer1K: 0.01, CacheReadPer1K: math.Inf(1)},
		"negative cached": {InputPer1K: 0.01, OutputPer1K: 0.01, CachedInputPer1K: -0.001},
	} {
		_, err := NewTable(map[string]map[string]EntryJSON{"openai": {"m": bad}})
		assert.Error(t, err, "case %q must be rejected so a corrupt config fails the chain build instead of mispricing", name)
	}
}

func TestNewTable_NilAndEmpty(t *testing.T) {
	tbl, err := NewTable(nil)
	require.NoError(t, err, "nil map builds an empty (never-matching) table")
	_, ok := tbl.Cost("openai", "gpt-4o", 1, 1, 0, 0)
	assert.False(t, ok, "empty table prices nothing")

	entries, err := NewEntries(nil)
	require.NoError(t, err)
	assert.Empty(t, entries, "nil in, empty (never-matching) map out for the per-record map")
}
