package tenants

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

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

func TestParseClusterYAML(t *testing.T) {
	cases := []struct {
		name     string
		text     string
		bizTypes []string
		source   string
		want     *Entry
	}{
		{
			name:     "matching biz + tenant",
			text:     "bizType: prod-devtier\nvmAccountId: 1001\nvmProjectId: 0\nclusterName: essential-42",
			bizTypes: []string{"prod-devtier"},
			source:   SourceS3,
			want:     &Entry{Tenant: "1001:0", BizType: "prod-devtier", Source: SourceS3},
		},
		{
			name:     "non-matching biz",
			text:     "bizType: something-else\nvmAccountId: 1001\nvmProjectId: 0",
			bizTypes: []string{"prod-devtier"},
			source:   SourceS3,
			want:     nil,
		},
		{
			name:     "multiple biz types, second matches",
			text:     "bizType: staging-tidbcloud\nvmAccountId: 1001\nvmProjectId: 0",
			bizTypes: []string{"tidbcloud", "staging-tidbcloud", "dev-tidbcloud"},
			source:   SourceS3,
			want:     &Entry{Tenant: "1001:0", BizType: "staging-tidbcloud", Source: SourceS3},
		},
		{
			name:     "missing account",
			text:     "bizType: prod-devtier\nvmProjectId: 0",
			bizTypes: []string{"prod-devtier"},
			source:   SourceS3,
			want:     nil,
		},
		{
			name:     "missing project falls back to default",
			text:     "bizType: prod-devtier\nvmAccountId: 1001",
			bizTypes: []string{"prod-devtier"},
			source:   SourceS3,
			want:     &Entry{Tenant: "1001:0", BizType: "prod-devtier", Source: SourceS3},
		},
		{
			name:     "quoted values stripped",
			text:     "bizType: \"prod-devtier\"\nvmAccountId: \"1001\"\nvmProjectId: \"42\"",
			bizTypes: []string{"prod-devtier"},
			source:   SourceS3,
			want:     &Entry{Tenant: "1001:42", BizType: "prod-devtier", Source: SourceS3},
		},
		{
			name:     "trailing comments stripped",
			text:     "bizType: prod-devtier # the biz\nvmAccountId: 1001 # the account\nvmProjectId: 0",
			bizTypes: []string{"prod-devtier"},
			source:   SourceS3,
			want:     &Entry{Tenant: "1001:0", BizType: "prod-devtier", Source: SourceS3},
		},
		{
			name:     "blank lines and comments ignored",
			text:     "# header\n\nbizType: prod-devtier\n\nvmAccountId: 1001\nvmProjectId: 0",
			bizTypes: []string{"prod-devtier"},
			source:   SourceS3,
			want:     &Entry{Tenant: "1001:0", BizType: "prod-devtier", Source: SourceS3},
		},
		{
			name:     "list items ignored",
			text:     "bizType: prod-devtier\nvmAccountId: 1001\nvmProjectId: 0\nextra:\n  - item1\n  - item2",
			bizTypes: []string{"prod-devtier"},
			source:   SourceS3,
			want:     &Entry{Tenant: "1001:0", BizType: "prod-devtier", Source: SourceS3},
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got, err := ParseClusterYAML(tc.text, tc.bizTypes, tc.source)
			assert.NoError(t, err)
			assert.Equal(t, tc.want, got)
		})
	}
}

func TestTenantID(t *testing.T) {
	cases := []struct {
		account, project, def string
		want                  string
	}{
		{"1001", "0", "0", "1001:0"},
		{"1001", "", "0", "1001:0"},
		{"1001", "42", "0", "1001:42"},
		{"", "0", "0", ""},
		{"  1001  ", "  0  ", "0", "1001:0"},
	}
	for _, tc := range cases {
		assert.Equal(t, tc.want, TenantID(tc.account, tc.project, tc.def))
	}
}

func TestMergeEntries(t *testing.T) {
	cases := []struct {
		name string
		in   []Entry
		want []Entry
	}{
		{
			name: "dedup by tenant",
			in: []Entry{
				{Tenant: "1001:0", BizType: "a", Source: SourceS3},
				{Tenant: "1001:0", BizType: "a", Source: SourceK8s},
			},
			want: []Entry{
				{Tenant: "1001:0", BizType: "a", Source: SourceS3},
			},
		},
		{
			name: "different tenants preserved",
			in: []Entry{
				{Tenant: "1001:0", BizType: "a", Source: SourceS3},
				{Tenant: "1002:0", BizType: "a", Source: SourceK8s},
			},
			want: []Entry{
				{Tenant: "1001:0", BizType: "a", Source: SourceS3},
				{Tenant: "1002:0", BizType: "a", Source: SourceK8s},
			},
		},
		{
			name: "k8s first then s3 -> s3 wins",
			in: []Entry{
				{Tenant: "1001:0", BizType: "a", Source: SourceK8s},
				{Tenant: "1001:0", BizType: "a", Source: SourceS3},
			},
			want: []Entry{
				{Tenant: "1001:0", BizType: "a", Source: SourceS3},
			},
		},
		{
			name: "first-seen order preserved on dedup",
			in: []Entry{
				{Tenant: "1003:0"},
				{Tenant: "1001:0"},
				{Tenant: "1003:0", Source: SourceS3},
			},
			want: []Entry{
				{Tenant: "1003:0", Source: SourceS3},
				{Tenant: "1001:0"},
			},
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got := mergeEntries(tc.in)
			assert.True(t, reflect.DeepEqual(got, tc.want), "got %+v want %+v", got, tc.want)
		})
	}
}

func TestSortByTenant(t *testing.T) {
	in := []Entry{
		{Tenant: "1001:42"},
		{Tenant: "999:0"},
		{Tenant: "1001:0"},
		{Tenant: "alpha:0"},
		{Tenant: "1001:5"},
	}
	want := []Entry{
		{Tenant: "999:0"},
		{Tenant: "1001:0"},
		{Tenant: "1001:5"},
		{Tenant: "1001:42"},
		{Tenant: "alpha:0"}, // non-numeric sorts after numeric
	}
	sortByTenant(in)
	assert.True(t, reflect.DeepEqual(in, want), "got %+v want %+v", in, want)
}

func TestSimpleYAMLScalars(t *testing.T) {
	// Parser is line-based; strips leading whitespace before looking for `key: value`,
	// so nested keys like `  foo: bar` get picked up too.
	text := `# header
bizType: prod-devtier
vmAccountId: 1001
empty:
quoted: "with spaces"
comment: value # trailing
list:
- a
- b
nested:
  foo: bar`
	got := simpleYAMLScalars(text)
	want := map[string]string{
		"biztype":     "prod-devtier",
		"vmaccountid": "1001",
		"quoted":      "with spaces",
		"comment":     "value",
		"foo":         "bar",
	}
	assert.True(t, reflect.DeepEqual(got, want), "got %#v want %#v", got, want)
}

func TestReadEntriesFile(t *testing.T) {
	dir := t.TempDir()
	canonical := filepath.Join(dir, "canonical.json")
	assert.NoError(t, os.WriteFile(canonical, []byte(`{
  "total": 2,
  "tenants": [
    {"tenant": "1001:0", "bizType": "tidbcloud", "source": "k8s"},
    {"tenant": "1002:0", "bizType": "tidbcloud", "source": "s3"}
  ]
}`), 0o644))
	got, err := ReadEntriesFile(canonical)
	assert.NoError(t, err)
	want := []Entry{
		{Tenant: "1001:0", BizType: "tidbcloud", Source: "k8s"},
		{Tenant: "1002:0", BizType: "tidbcloud", Source: "s3"},
	}
	assert.True(t, reflect.DeepEqual(got, want), "got %+v want %+v", got, want)

	bad := filepath.Join(dir, "bad.json")
	assert.NoError(t, os.WriteFile(bad, []byte(`not json`), 0o644))
	_, err = ReadEntriesFile(bad)
	assert.Error(t, err, "expected error for invalid JSON")
}
