package resources_test

import (
	"errors"
	"testing"
	"time"

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

	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
	"cloud.o-forge.io/core/oc-lib/models/resources"
	"cloud.o-forge.io/core/oc-lib/tools"
)

// ---- Mock PricingProfile ----

type MockPricingProfile struct {
	pricing.PricingProfileITF
	Purchased  bool
	ReturnErr  bool
	ReturnCost float64
}

func (m *MockPricingProfile) IsPurchasable() bool {
	return m.Purchased
}

func (m *MockPricingProfile) GetPriceHT(amount float64, explicitDuration float64, start time.Time, end time.Time, variations []*pricing.PricingVariation, _ ...string) (float64, error) {
	if m.ReturnErr {
		return 0, errors.New("mock error")
	}
	return m.ReturnCost, nil
}

// ---- Tests ----

func TestGetIDAndCreatorAndType(t *testing.T) {
	r := resources.PricedResource[pricing.PricingProfileITF]{
		ResourceID:   "res-123",
		CreatorID:    "user-abc",
		ResourceType: tools.DATA_RESOURCE,
	}
	assert.Equal(t, "res-123", r.GetID())
	assert.Equal(t, "user-abc", r.GetCreatorID())
	assert.Equal(t, tools.DATA_RESOURCE, r.GetType())
}

func TestIsPurchased(t *testing.T) {
	t.Run("nil selected pricing returns false", func(t *testing.T) {
		r := &resources.PricedResource[pricing.PricingProfileITF]{}
		assert.False(t, r.IsPurchasable())
	})

	t.Run("returns true if pricing profile is purchased", func(t *testing.T) {
		mock := &MockPricingProfile{Purchased: true}
		r := &resources.PricedResource[pricing.PricingProfileITF]{SelectedPricing: mock}
		assert.True(t, r.IsPurchasable())
	})
}

func TestGetAndSetLocationStartEnd(t *testing.T) {
	r := &resources.PricedResource[pricing.PricingProfileITF]{}

	now := time.Now()
	r.SetLocationStart(now)
	r.SetLocationEnd(now.Add(10 * time.Minute))

	assert.Equal(t, now, *r.GetLocationStart())
	assert.Equal(t, now.Add(2*time.Hour), *r.GetLocationEnd())
}

func TestGetExplicitDurationInS(t *testing.T) {
	t.Run("uses explicit duration if set", func(t *testing.T) {
		r := &resources.PricedResource[pricing.PricingProfileITF]{BookingConfiguration: &resources.BookingConfiguration{
			ExplicitBookingDurationS: 3600,
		},
		}
		assert.Equal(t, 3600.0, r.GetExplicitDurationInS())
	})

	t.Run("computes duration from start and end", func(t *testing.T) {
		start := time.Now()
		end := start.Add(10 * time.Minute)
		r := &resources.PricedResource[pricing.PricingProfileITF]{
			BookingConfiguration: &resources.BookingConfiguration{
				UsageStart: &start, UsageEnd: &end,
			},
		}
		assert.InDelta(t, 7200.0, r.GetExplicitDurationInS(), 0.1)
	})

	t.Run("defaults to 1 hour when times not set", func(t *testing.T) {
		r := &resources.PricedResource[pricing.PricingProfileITF]{}
		assert.InDelta(t, 3600.0, r.GetExplicitDurationInS(), 0.1)
	})
}

func TestGetPriceHT(t *testing.T) {
	t.Run("returns error if no pricing profile", func(t *testing.T) {
		r := &resources.PricedResource[pricing.PricingProfileITF]{ResourceID: "no-profile"}
		price, err := r.GetPriceHT()
		require.Error(t, err)
		assert.Contains(t, err.Error(), "pricing profile must be set")
		assert.Equal(t, 0.0, price)
	})

	t.Run("defaults BookingConfiguration when nil", func(t *testing.T) {
		mock := &MockPricingProfile{ReturnCost: 42.0}
		r := &resources.PricedResource[pricing.PricingProfileITF]{
			SelectedPricing: mock,
		}
		price, err := r.GetPriceHT()
		require.NoError(t, err)
		assert.Equal(t, 42.0, price)
	})

	t.Run("returns error if profile GetPriceHT fails", func(t *testing.T) {
		start := time.Now()
		end := start.Add(5 * time.Minute)
		mock := &MockPricingProfile{ReturnErr: true}
		r := &resources.PricedResource[pricing.PricingProfileITF]{
			SelectedPricing: mock,
			BookingConfiguration: &resources.BookingConfiguration{
				UsageStart: &start,
				UsageEnd:   &end,
			},
		}
		price, err := r.GetPriceHT()
		require.Error(t, err)
		assert.Equal(t, 0.0, price)
	})

	t.Run("uses SelectedPricing if set", func(t *testing.T) {
		start := time.Now()
		end := start.Add(5 * time.Minute)
		mock := &MockPricingProfile{ReturnCost: 10.0}
		r := &resources.PricedResource[pricing.PricingProfileITF]{
			SelectedPricing: mock,
			BookingConfiguration: &resources.BookingConfiguration{
				UsageStart: &start,
				UsageEnd:   &end,
			},
		}
		price, err := r.GetPriceHT()
		require.NoError(t, err)
		assert.Equal(t, 10.0, price)
	})
}
