package converter

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/thushan/olla/internal/adapter/registry/profile"
	"github.com/thushan/olla/internal/core/constants"
	"github.com/thushan/olla/internal/core/domain"
	"github.com/thushan/olla/internal/core/ports"
)

func TestNewSGLangConverter(t *testing.T) {
	converter := NewSGLangConverter()
	assert.NotNil(t, converter)
	assert.Equal(t, constants.ProviderTypeSGLang, converter.GetFormatName())
}

func TestSGLangConverter_ConvertToFormat_EmptyModels(t *testing.T) {
	converter := NewSGLangConverter()
	result, err := converter.ConvertToFormat([]*domain.UnifiedModel{}, ports.ModelFilters{})

	require.NoError(t, err)
	response, ok := result.(profile.SGLangResponse)
	require.True(t, ok)

	assert.Equal(t, "list", response.Object)
	assert.Empty(t, response.Data)
}

func TestSGLangConverter_ConvertToFormat_SingleModel(t *testing.T) {
	converter := NewSGLangConverter()

	model := &domain.UnifiedModel{
		ID:               "meta-llama/Meta-Llama-3.1-8B-Instruct",
		MaxContextLength: func() *int64 { v := int64(131072); return &v }(),
		Aliases: []domain.AliasEntry{
			{
				Name:   "meta-llama/Meta-Llama-3.1-8B-Instruct",
				Source: constants.ProviderTypeSGLang,
			},
		},
	}

	result, err := converter.ConvertToFormat([]*domain.UnifiedModel{model}, ports.ModelFilters{})

	require.NoError(t, err)
	response, ok := result.(profile.SGLangResponse)
	require.True(t, ok)

	assert.Equal(t, "list", response.Object)
	require.Len(t, response.Data, 1)

	sglangModel := response.Data[0]
	assert.Equal(t, "meta-llama/Meta-Llama-3.1-8B-Instruct", sglangModel.ID)
	assert.Equal(t, "model", sglangModel.Object)
	assert.NotZero(t, sglangModel.Created)
	assert.Equal(t, "meta-llama", sglangModel.OwnedBy)
	assert.Equal(t, "meta-llama/Meta-Llama-3.1-8B-Instruct", sglangModel.Root)
	assert.NotNil(t, sglangModel.MaxModelLen)
	assert.Equal(t, int64(131072), *sglangModel.MaxModelLen)
}

func TestSGLangConverter_ConvertToFormat_MultipleModels(t *testing.T) {
	converter := NewSGLangConverter()

	models := []*domain.UnifiedModel{
		{
			ID:               "meta-llama/Meta-Llama-3.1-8B-Instruct",
			MaxContextLength: func() *int64 { v := int64(131072); return &v }(),
			Aliases: []domain.AliasEntry{
				{Name: "meta-llama/Meta-Llama-3.1-8B-Instruct", Source: constants.ProviderTypeSGLang},
			},
		},
		{
			ID:               "microsoft/DialoGPT-medium",
			MaxContextLength: func() *int64 { v := int64(8192); return &v }(),
			Aliases: []domain.AliasEntry{
				{Name: "microsoft/DialoGPT-medium", Source: constants.ProviderTypeSGLang},
			},
		},
	}

	result, err := converter.ConvertToFormat(models, ports.ModelFilters{})

	require.NoError(t, err)
	response, ok := result.(profile.SGLangResponse)
	require.True(t, ok)

	assert.Equal(t, "list", response.Object)
	require.Len(t, response.Data, 2)

	// Check first model
	model1 := response.Data[0]
	assert.Equal(t, "meta-llama/Meta-Llama-3.1-8B-Instruct", model1.ID)
	assert.Equal(t, "meta-llama", model1.OwnedBy)
	assert.NotNil(t, model1.MaxModelLen)
	assert.Equal(t, int64(131072), *model1.MaxModelLen)

	// Check second model
	model2 := response.Data[1]
	assert.Equal(t, "microsoft/DialoGPT-medium", model2.ID)
	assert.Equal(t, "microsoft", model2.OwnedBy)
	assert.NotNil(t, model2.MaxModelLen)
	assert.Equal(t, int64(8192), *model2.MaxModelLen)
}

func TestSGLangConverter_ConvertToFormat_ModelWithVisionCapability(t *testing.T) {
	converter := NewSGLangConverter()

	model := &domain.UnifiedModel{
		ID:           "llava-hf/llava-1.5-13b-hf",
		Capabilities: []string{"vision", "chat"},
		Aliases: []domain.AliasEntry{
			{Name: "llava-hf/llava-1.5-13b-hf", Source: constants.ProviderTypeSGLang},
		},
	}

	result, err := converter.ConvertToFormat([]*domain.UnifiedModel{model}, ports.ModelFilters{})

	require.NoError(t, err)
	response, ok := result.(profile.SGLangResponse)
	require.True(t, ok)

	require.Len(t, response.Data, 1)
	sglangModel := response.Data[0]

	assert.Equal(t, "llava-hf/llava-1.5-13b-hf", sglangModel.ID)
	assert.Equal(t, "llava-hf", sglangModel.OwnedBy)
	assert.NotNil(t, sglangModel.SupportsVision)
	assert.True(t, *sglangModel.SupportsVision)
}

func TestSGLangConverter_ConvertToFormat_ModelWithMetadata(t *testing.T) {
	converter := NewSGLangConverter()

	model := &domain.UnifiedModel{
		ID: "test-model-with-metadata",
		Metadata: map[string]interface{}{
			"radix_cache_size":     int64(2097152),
			"speculative_decoding": true,
			"frontend_enabled":     true,
			"supports_vision":      true,
			"parent_model":         "base-model",
		},
		Aliases: []domain.AliasEntry{
			{Name: "test-model-with-metadata", Source: constants.ProviderTypeSGLang},
		},
	}

	result, err := converter.ConvertToFormat([]*domain.UnifiedModel{model}, ports.ModelFilters{})

	require.NoError(t, err)
	response, ok := result.(profile.SGLangResponse)
	require.True(t, ok)

	require.Len(t, response.Data, 1)
	sglangModel := response.Data[0]

	assert.Equal(t, "test-model-with-metadata", sglangModel.ID)
	assert.Equal(t, constants.ProviderTypeSGLang, sglangModel.OwnedBy) // Default when no org in name

	// Check SGLang-specific metadata
	if sglangModel.RadixCacheSize != nil {
		assert.Equal(t, int64(2097152), *sglangModel.RadixCacheSize)
	} else {
		t.Logf("RadixCacheSize is nil, metadata extraction may have failed")
	}

	if sglangModel.SpecDecoding != nil {
		assert.True(t, *sglangModel.SpecDecoding)
	} else {
		t.Logf("SpecDecoding is nil")
	}

	if sglangModel.FrontendEnabled != nil {
		assert.True(t, *sglangModel.FrontendEnabled)
	} else {
		t.Logf("FrontendEnabled is nil")
	}

	if sglangModel.SupportsVision != nil {
		assert.True(t, *sglangModel.SupportsVision)
	} else {
		t.Logf("SupportsVision is nil")
	}

	if sglangModel.Parent != nil {
		assert.Equal(t, "base-model", *sglangModel.Parent)
	} else {
		t.Logf("Parent is nil")
	}
}

func TestSGLangConverter_ConvertToFormat_ModelWithSourceEndpoints(t *testing.T) {
	converter := NewSGLangConverter()

	model := &domain.UnifiedModel{
		ID: "test-model",
		SourceEndpoints: []domain.SourceEndpoint{
			{
				NativeName: "test-model",
			},
		},
		Aliases: []domain.AliasEntry{
			{Name: "test-model", Source: constants.ProviderTypeSGLang},
		},
	}

	result, err := converter.ConvertToFormat([]*domain.UnifiedModel{model}, ports.ModelFilters{})

	require.NoError(t, err)
	response, ok := result.(profile.SGLangResponse)
	require.True(t, ok)

	require.Len(t, response.Data, 1)
	sglangModel := response.Data[0]

	assert.Equal(t, "test-model", sglangModel.ID)
	// Parent model logic is handled through metadata or other means
	assert.Equal(t, "test-model", sglangModel.ID)
}

func TestSGLangConverter_ConvertToFormat_FallbackToUnifiedID(t *testing.T) {
	converter := NewSGLangConverter()

	model := &domain.UnifiedModel{
		ID: "fallback-test-model",
		Aliases: []domain.AliasEntry{
			{Name: "different-name", Source: "different-provider"},
		},
	}

	result, err := converter.ConvertToFormat([]*domain.UnifiedModel{model}, ports.ModelFilters{})

	require.NoError(t, err)
	response, ok := result.(profile.SGLangResponse)
	require.True(t, ok)

	require.Len(t, response.Data, 1)
	sglangModel := response.Data[0]

	// Should use the first alias when no SGLang-specific alias exists
	assert.Equal(t, "different-name", sglangModel.ID)
	assert.Equal(t, constants.ProviderTypeSGLang, sglangModel.OwnedBy)
}

func TestSGLangConverter_ConvertToFormat_NoAliases(t *testing.T) {
	converter := NewSGLangConverter()

	model := &domain.UnifiedModel{
		ID:      "no-aliases-model",
		Aliases: []domain.AliasEntry{}, // Empty aliases
	}

	result, err := converter.ConvertToFormat([]*domain.UnifiedModel{model}, ports.ModelFilters{})

	require.NoError(t, err)
	response, ok := result.(profile.SGLangResponse)
	require.True(t, ok)

	require.Len(t, response.Data, 1)
	sglangModel := response.Data[0]

	// Should fallback to unified ID when no aliases exist
	assert.Equal(t, "no-aliases-model", sglangModel.ID)
	assert.Equal(t, constants.ProviderTypeSGLang, sglangModel.OwnedBy)
}

func TestSGLangConverter_DetermineOwner(t *testing.T) {
	converter := &SGLangConverter{BaseConverter: NewBaseConverter(constants.ProviderTypeSGLang)}

	testCases := []struct {
		modelID  string
		expected string
	}{
		{"meta-llama/Meta-Llama-3.1-8B-Instruct", "meta-llama"},
		{"microsoft/DialoGPT-medium", "microsoft"},
		{"openai/gpt-4", "openai"},
		{"simple-model", constants.ProviderTypeSGLang},
		{"", constants.ProviderTypeSGLang},
		{"org/sub/model", "org"}, // Only first part counts
	}

	for _, tc := range testCases {
		t.Run(tc.modelID, func(t *testing.T) {
			result := converter.determineOwner(tc.modelID)
			assert.Equal(t, tc.expected, result)
		})
	}
}

func TestSGLangConverter_ConvertToFormat_WithFilters(t *testing.T) {
	converter := NewSGLangConverter()

	models := []*domain.UnifiedModel{
		{
			ID: "model1",
			Aliases: []domain.AliasEntry{
				{Name: "model1", Source: constants.ProviderTypeSGLang},
			},
		},
		{
			ID: "model2",
			Aliases: []domain.AliasEntry{
				{Name: "model2", Source: constants.ProviderTypeSGLang},
			},
		},
		{
			ID: "model3",
			Aliases: []domain.AliasEntry{
				{Name: "model3", Source: constants.ProviderTypeSGLang},
			},
		},
	}

	// Filter to only model1 - we'll do this by creating a subset manually
	filteredModels := []*domain.UnifiedModel{models[0]} // Only include model1

	result, err := converter.ConvertToFormat(filteredModels, ports.ModelFilters{})

	require.NoError(t, err)
	response, ok := result.(profile.SGLangResponse)
	require.True(t, ok)

	assert.Equal(t, "list", response.Object)
	require.Len(t, response.Data, 1)
	assert.Equal(t, "model1", response.Data[0].ID)
}

func BenchmarkSGLangConverter_ConvertToFormat(b *testing.B) {
	converter := NewSGLangConverter()

	// Create a large number of models to test performance
	models := make([]*domain.UnifiedModel, 1000)
	for i := range 1000 {
		models[i] = &domain.UnifiedModel{
			ID:               "performance-test-model-" + string(rune(i)),
			MaxContextLength: func() *int64 { v := int64(8192); return &v }(),
			Aliases: []domain.AliasEntry{
				{Name: "performance-test-model-" + string(rune(i)), Source: constants.ProviderTypeSGLang},
			},
		}
	}

	// Report memory allocations
	b.ReportAllocs()
	b.ResetTimer()

	for range b.N {
		result, err := converter.ConvertToFormat(models, ports.ModelFilters{})
		if err != nil {
			b.Fatalf("ConvertToFormat failed: %v", err)
		}

		response, ok := result.(profile.SGLangResponse)
		if !ok {
			b.Fatal("Result is not SGLangResponse")
		}

		if len(response.Data) != 1000 {
			b.Fatalf("Expected 1000 models, got %d", len(response.Data))
		}
	}
}
