package query

import (
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/tidbcloud/aws-shared-provider/tools/cloud-instances/provider"
	"github.com/tidbcloud/aws-shared-provider/tools/cloud-instances/types"
)

type stubProvider struct{}

func (p *stubProvider) DownloadRaw(region string) (*provider.RawBundle, error) {
	return &provider.RawBundle{
		Provider: "stub",
		Region:   region,
		Parts: []provider.RawPart{
			{Scope: region, Name: "data", Data: []byte(region)},
		},
	}, nil
}

func (p *stubProvider) ParseRaw(raw *provider.RawBundle) (*types.UnifiedData, error) {
	ud := types.NewUnifiedData("stub")
	ud.Instances[raw.Region] = []types.UnifiedInstance{{
		Provider:     "stub",
		InstanceType: string(raw.Parts[0].Data),
		Region:       raw.Region,
	}}
	return ud, nil
}

func (p *stubProvider) GetRegions() map[string]string {
	return map[string]string{"test-region": "Test Region"}
}

func (p *stubProvider) IsValidRegion(regionID string) bool {
	return regionID == "test-region"
}

func (p *stubProvider) GetRegionName(regionID string) string {
	return "Test Region"
}

func (p *stubProvider) GetCacheFileName(region string) string {
	return "stub_" + region + ".json"
}

func (p *stubProvider) GetProviderName() string {
	return "stub"
}

func TestQueryUsesRawCache(t *testing.T) {
	cacheDir := t.TempDir()
	qe := NewQueryEngine(cacheDir)
	qe.Providers = map[string]provider.CloudProvider{"stub": &stubProvider{}}

	opts := FilterOptions{
		Provider:      "stub",
		Regions:       []string{"test-region"},
		FromCache:     false,
		ForceDownload: false,
	}

	if err := qe.Download(opts); err != nil {
		t.Fatalf("download: %v", err)
	}

	if !qe.CacheMgr.RawExists("stub", "test-region", "manifest") {
		t.Fatalf("expected raw manifest to exist")
	}

	instances, err := qe.Query(FilterOptions{
		Provider:  "stub",
		Regions:   []string{"test-region"},
		FromCache: true,
	})
	if err != nil {
		t.Fatalf("query from cache: %v", err)
	}
	if len(instances) != 1 {
		t.Fatalf("expected 1 instance, got %d", len(instances))
	}
	if instances[0].InstanceType != "test-region" {
		t.Fatalf("unexpected instance type")
	}
}

func TestQueryRespectsMaxCacheAge(t *testing.T) {
	cacheDir := t.TempDir()
	qe := NewQueryEngine(cacheDir)
	qe.Providers = map[string]provider.CloudProvider{"stub": &stubProvider{}}

	opts := FilterOptions{
		Provider:      "stub",
		Regions:       []string{"test-region"},
		FromCache:     false,
		ForceDownload: false,
	}

	if err := qe.Download(opts); err != nil {
		t.Fatalf("download: %v", err)
	}

	manifestPath := filepath.Join(cacheDir, "raw", "stub", "test-region", "manifest.json")
	old := time.Now().Add(-2 * time.Hour)
	if err := os.Chtimes(manifestPath, old, old); err != nil {
		t.Fatalf("chtimes: %v", err)
	}

	_, err := qe.Query(FilterOptions{
		Provider:    "stub",
		Regions:     []string{"test-region"},
		FromCache:   false,
		MaxCacheAge: 30 * time.Minute,
	})
	if err != nil {
		t.Fatalf("query with ttl: %v", err)
	}
}

func TestApplyFiltersCurrency(t *testing.T) {
	qe := NewQueryEngine(t.TempDir())
	instances := []types.UnifiedInstance{
		{InstanceType: "a", Price: 1.0, Currency: "USD"},
		{InstanceType: "b", Price: 1.0, Currency: "CNY"},
	}

	filtered := qe.applyFilters(instances, FilterOptions{
		MinPrice: 0.5,
		MaxPrice: 2.0,
		Currency: "USD",
	})

	if len(filtered) != 1 || filtered[0].InstanceType != "a" {
		t.Fatalf("expected USD instance to match currency filter")
	}
}

func TestApplyFiltersLocalStorage(t *testing.T) {
	qe := NewQueryEngine(t.TempDir())
	instances := []types.UnifiedInstance{
		{InstanceType: "local", Storage: "1 x 900 NVMe SSD"},
		{InstanceType: "ebs", Storage: "EBS only"},
		{InstanceType: "unknown", Storage: "Varies"},
	}

	filtered := qe.applyFilters(instances, FilterOptions{
		LocalStorageFilter: "with",
	})
	if len(filtered) != 1 || filtered[0].InstanceType != "local" {
		t.Fatalf("expected only local-storage instances")
	}

	filtered = qe.applyFilters(instances, FilterOptions{
		LocalStorageFilter: "without",
	})
	if len(filtered) != 1 || filtered[0].InstanceType != "ebs" {
		t.Fatalf("expected only non-local-storage instances")
	}
}

func TestSortInstances(t *testing.T) {
	base := []types.UnifiedInstance{
		{InstanceType: "b", Price: 2.0, VCPU: "4", Memory: "8 GiB"},
		{InstanceType: "a", Price: 1.0, VCPU: "2", Memory: "4 GiB"},
		{InstanceType: "unknown", Price: 0, VCPU: "", Memory: ""},
	}

	cases := []struct {
		name     string
		sortBy   string
		expected []string
	}{
		{name: "price", sortBy: "price", expected: []string{"a", "b", "unknown"}},
		{name: "vcpu", sortBy: "vcpu", expected: []string{"a", "b", "unknown"}},
		{name: "memory", sortBy: "memory", expected: []string{"a", "b", "unknown"}},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			data := append([]types.UnifiedInstance(nil), base...)
			if err := sortInstances(data, tc.sortBy); err != nil {
				t.Fatalf("sortInstances: %v", err)
			}
			assertInstanceOrder(t, data, tc.expected)
		})
	}

	if err := sortInstances(append([]types.UnifiedInstance(nil), base...), "invalid"); err == nil {
		t.Fatalf("expected error for invalid sort option")
	}
}

func assertInstanceOrder(t *testing.T, instances []types.UnifiedInstance, expected []string) {
	t.Helper()
	if len(instances) != len(expected) {
		t.Fatalf("expected %d instances, got %d", len(expected), len(instances))
	}
	for i, name := range expected {
		if instances[i].InstanceType != name {
			t.Fatalf("expected %q at %d, got %q", name, i, instances[i].InstanceType)
		}
	}
}
