package costexplorer

import (
	"testing"

	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/shopspring/decimal"
)

func TestGetExportUsageUsesMeasureUnitStrategy(t *testing.T) {
	tests := []struct {
		name      string
		cost      entity.CostsDetailsQuery
		category  entity.CategoryItemShow
		price     decimal.Decimal
		ctx       exportUsageContext
		wantUsage decimal.Decimal
		wantUnit  string
	}{
		{
			name: "RU uses raw price divided by unit price directly",
			cost: entity.CostsDetailsQuery{
				Unit:           "custom-request-unit:aws:ap-southeast-1",
				Cost:           375,
				ConsumedSource: entity.ConsumedSource_Invoice,
				RawPrice:       decimal.NewFromInt(60),
			},
			category:  entity.CategoryItemShow{MeasureUnit: "RU"},
			price:     decimal.NewFromFloat(0.01),
			wantUsage: decimal.NewFromInt(6000),
			wantUnit:  "RU",
		},
		{
			name: "GiB-month converts MiB-minutes to average GiB",
			cost: entity.CostsDetailsQuery{
				Unit:           "storage:aws:us-east-1",
				Cost:           1,
				ConsumedSource: entity.ConsumedSource_Invoice,
				RawPrice:       decimal.NewFromInt(2048),
			},
			category: entity.CategoryItemShow{MeasureUnit: "GiB-month"},
			price:    decimal.NewFromInt(1),
			ctx: exportUsageContext{
				usagePeriodMinutes: decimal.NewFromInt(2),
			},
			wantUsage: decimal.NewFromInt(1),
			wantUnit:  "GiB-month",
		},
		{
			name: "RCU converts minute-level total to average RCU",
			cost: entity.CostsDetailsQuery{
				Unit:           "rcu:aws:us-east-1:premium:all:total",
				Cost:           1,
				ConsumedSource: entity.ConsumedSource_Invoice,
				RawPrice:       decimal.NewFromInt(60),
			},
			category: entity.CategoryItemShow{MeasureUnit: "RCU"},
			price:    decimal.NewFromFloat(0.01),
			ctx: exportUsageContext{
				usagePeriodMinutes: decimal.NewFromInt(60),
			},
			wantUsage: decimal.NewFromInt(100),
			wantUnit:  "RCU",
		},
		{
			name: "credit source uses measure unit strategy",
			cost: entity.CostsDetailsQuery{
				Unit:           "serverless-request-unit:aws:us-east-1:starter",
				Cost:           76,
				ConsumedSource: entity.ConsumedSource_Credits,
				RawPrice:       decimal.NewFromInt(1),
			},
			category:  entity.CategoryItemShow{MeasureUnit: "RU"},
			price:     decimal.NewFromFloat(0.0001),
			wantUsage: decimal.NewFromInt(10000),
			wantUnit:  "RU",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			usage, unit := getExportUsage(tt.cost, tt.category, tt.price, tt.ctx)
			if !usage.Equal(tt.wantUsage) {
				t.Fatalf("expected usage %s, got %s", tt.wantUsage, usage)
			}
			if unit != tt.wantUnit {
				t.Fatalf("expected unit %q, got %q", tt.wantUnit, unit)
			}
		})
	}
}

func TestGetExportUsageSkipsUnsupportedCases(t *testing.T) {
	tests := []struct {
		name     string
		cost     entity.CostsDetailsQuery
		category entity.CategoryItemShow
		price    decimal.Decimal
		ctx      exportUsageContext
	}{
		{
			name: "request unit key without category measure unit",
			cost: entity.CostsDetailsQuery{
				Unit:           "serverless-request-unit:aws:us-east-1:starter",
				Cost:           76,
				ConsumedSource: entity.ConsumedSource_Invoice,
				RawPrice:       decimal.NewFromInt(1),
			},
			price: decimal.NewFromFloat(0.0001),
		},
		{
			name: "unsupported category measure unit",
			cost: entity.CostsDetailsQuery{
				Unit:           "backup:aws:us-east-1",
				Cost:           12,
				ConsumedSource: entity.ConsumedSource_Invoice,
				RawPrice:       decimal.NewFromInt(1),
			},
			category: entity.CategoryItemShow{MeasureUnit: "GB"},
			price:    decimal.NewFromFloat(0.1),
		},
		{
			name: "zero price",
			cost: entity.CostsDetailsQuery{
				Unit:           "serverless-request-unit:aws:us-east-1:starter",
				Cost:           76,
				ConsumedSource: entity.ConsumedSource_Invoice,
				RawPrice:       decimal.NewFromInt(1),
			},
			category: entity.CategoryItemShow{MeasureUnit: "RU"},
			price:    decimal.Zero,
		},
		{
			name: "usage period minutes missing for RCU",
			cost: entity.CostsDetailsQuery{
				Unit:           "rcu:aws:us-east-1:premium:all:total",
				Cost:           1,
				ConsumedSource: entity.ConsumedSource_Invoice,
				RawPrice:       decimal.NewFromInt(60),
			},
			category: entity.CategoryItemShow{MeasureUnit: "RCU"},
			price:    decimal.NewFromFloat(0.01),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			usage, unit := getExportUsage(tt.cost, tt.category, tt.price, tt.ctx)
			if !usage.IsZero() {
				t.Fatalf("expected zero usage, got %s", usage)
			}
			if unit != "" {
				t.Fatalf("expected empty unit, got %q", unit)
			}
		})
	}
}

func TestNewDailyExportUsageContext(t *testing.T) {
	ctx := newDailyExportUsageContext()
	if !ctx.usagePeriodMinutes.Equal(decimal.NewFromInt(24 * 60)) {
		t.Fatalf("expected daily period 1440 minutes, got %s", ctx.usagePeriodMinutes)
	}
}

func TestBillsExportDetailsAccumulatorUsesCategoryMeasureUnit(t *testing.T) {
	acc := &billsExportDetailsAccumulator{
		unitsCategory: map[string]string{
			"unit-from-category": "Request Category",
		},
		unitsProvider: map[string]string{
			"unit-from-category": "AWS",
		},
		unitsRegion: map[string]string{
			"unit-from-category": "us-east-1",
		},
		unitsPrice: map[string]decimal.Decimal{
			"unit-from-category": decimal.NewFromFloat(0.001),
		},
		categoryLabels: map[string]entity.CategoryItemShow{
			"Request Category": {
				Category:         "Request Category",
				ShowKindName:     "Product",
				ShowCategoryName: "Request Units",
				UseCateGory:      true,
				MeasureUnit:      "RU",
			},
		},
		uniqueKeyMap: map[string]int{},
	}

	ok := acc.add(entity.CostsDetailsQuery{
		TenantID:              1,
		BilledDate:            "2026-06-01",
		Unit:                  "unit-from-category",
		Cost:                  123,
		ConsumedSource:        entity.ConsumedSource_Invoice,
		CostPrice:             decimal.NewFromInt(1),
		RawPrice:              decimal.NewFromInt(1),
		Labels:                map[string]string{},
		BindedBillingConfigID: 10,
	})
	if !ok {
		t.Fatal("expected export row to be accepted")
	}

	rows := acc.result()
	if len(rows) != 1 {
		t.Fatalf("expected 1 row, got %d", len(rows))
	}
	if !rows[0].Usage.Equal(decimal.NewFromInt(1000)) {
		t.Fatalf("expected usage 1000, got %s", rows[0].Usage)
	}
	if rows[0].Unit != "RU" {
		t.Fatalf("expected unit RU, got %q", rows[0].Unit)
	}
}
