package server

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"

	"github.com/mem9-ai/drive9/pkg/meta"
	"github.com/mem9-ai/drive9/pkg/tenant"
)

func TestAdminTenantGetAndListMetadataWithDefaultQuota(t *testing.T) {
	rt := newQuotaRuntime(t, tenant.ProviderTiDBCloudNative)
	ctx := context.Background()
	if _, err := rt.meta.DB().ExecContext(ctx, `UPDATE tenants SET display_name = '', label = JSON_OBJECT('environment', 'production') WHERE id = ?`, rt.tenantID); err != nil {
		t.Fatal(err)
	}
	if err := rt.meta.SetQuotaConfig(ctx, &meta.QuotaConfig{TenantID: rt.tenantID, MaxStorageBytes: 100, MaxFileSizeBytes: 10, MaxFileCount: 7}); err != nil {
		t.Fatal(err)
	}
	if err := rt.meta.EnsureQuotaUsageRow(ctx, rt.tenantID); err != nil {
		t.Fatal(err)
	}
	rt.prov.iamIdentities = []*tenant.TiDBCloudAPIKeyIdentity{{OrganizationID: "org-1", Role: tenant.TiDBCloudRoleOrgOwner}}
	ts := httptest.NewServer(rt.server)
	defer ts.Close()

	getReq, err := http.NewRequest(http.MethodGet, ts.URL+"/v1/admin/tenants/"+rt.tenantID, nil)
	if err != nil {
		t.Fatal(err)
	}
	setAdminTestCredentials(getReq)
	getResp, err := http.DefaultClient.Do(getReq)
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = getResp.Body.Close() }()
	if getResp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(getResp.Body)
		t.Fatalf("get status = %d: %s", getResp.StatusCode, body)
	}
	var got adminTenantResponse
	if err := json.NewDecoder(getResp.Body).Decode(&got); err != nil {
		t.Fatal(err)
	}
	if got.DisplayName != rt.tenantID {
		t.Fatalf("get display_name = %q, want tenant id %q", got.DisplayName, rt.tenantID)
	}
	if got.Label["environment"] != "production" {
		t.Fatalf("get label = %#v", got.Label)
	}
	if got.Quota == nil {
		t.Fatal("get quota is nil")
	}

	query := url.Values{}
	query.Set("display_name", rt.tenantID[4:12])
	query.Set("label", "environment==production")
	listReq, err := http.NewRequest(http.MethodGet, ts.URL+"/v1/admin/tenants?"+query.Encode(), nil)
	if err != nil {
		t.Fatal(err)
	}
	setAdminTestCredentials(listReq)
	listResp, err := http.DefaultClient.Do(listReq)
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = listResp.Body.Close() }()
	if listResp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(listResp.Body)
		t.Fatalf("list status = %d: %s", listResp.StatusCode, body)
	}
	var listed adminTenantListResponse
	if err := json.NewDecoder(listResp.Body).Decode(&listed); err != nil {
		t.Fatal(err)
	}
	if len(listed.Tenants) != 1 || listed.Tenants[0].TenantID != rt.tenantID {
		t.Fatalf("listed tenants = %#v", listed.Tenants)
	}
	if listed.Tenants[0].Quota == nil {
		t.Fatal("list quota is nil without include_quota")
	}
}

func TestAdminTenantListRejectsMalformedLabelFilter(t *testing.T) {
	tests := []struct {
		name      string
		selector  string
		wantError string
	}{
		{name: "missing separator", selector: "environment", wantError: "label must use key==value format"},
		{name: "invalid key", selector: "example_com/environment==production", wantError: invalidAdminTenantLabelKeyMessage},
		{name: "invalid value", selector: "environment==-production", wantError: invalidAdminTenantLabelValueMessage},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			rt := newQuotaRuntime(t, tenant.ProviderTiDBCloudNative)
			rt.prov.iamIdentities = []*tenant.TiDBCloudAPIKeyIdentity{{OrganizationID: "org-1", Role: tenant.TiDBCloudRoleOrgOwner}}
			req := httptest.NewRequest(http.MethodGet, "/v1/admin/tenants?label="+url.QueryEscape(tt.selector), nil)
			setAdminTestCredentials(req)
			rec := httptest.NewRecorder()
			rt.server.ServeHTTP(rec, req)
			if rec.Code != http.StatusBadRequest || !strings.Contains(rec.Body.String(), tt.wantError) {
				t.Fatalf("response = %d %q, want 400 containing %q", rec.Code, rec.Body.String(), tt.wantError)
			}
		})
	}
}

func TestAdminTenantCreateValidatesMetadataBeforePoolMutation(t *testing.T) {
	tests := []struct {
		name       string
		metadata   map[string]any
		wantStatus int
		wantError  string
	}{
		{
			name:       "invalid display name",
			metadata:   map[string]any{"display_name": "Search Production"},
			wantStatus: http.StatusBadRequest,
			wantError:  invalidAdminTenantDisplayNameMessage,
		},
		{
			name:       "display name too short",
			metadata:   map[string]any{"display_name": "abc"},
			wantStatus: http.StatusBadRequest,
			wantError:  invalidAdminTenantDisplayNameMessage,
		},
		{
			name:       "display name too long",
			metadata:   map[string]any{"display_name": strings.Repeat("x", 65)},
			wantStatus: http.StatusBadRequest,
			wantError:  invalidAdminTenantDisplayNameMessage,
		},
		{
			name:       "display name with surrounding whitespace",
			metadata:   map[string]any{"display_name": " search-production "},
			wantStatus: http.StatusBadRequest,
			wantError:  invalidAdminTenantDisplayNameMessage,
		},
		{
			name:       "whitespace-only display name",
			metadata:   map[string]any{"display_name": "   "},
			wantStatus: http.StatusBadRequest,
			wantError:  invalidAdminTenantDisplayNameMessage,
		},
		{
			name:       "too many labels",
			metadata:   map[string]any{"label": tenantMetadataTestLabels(31)},
			wantStatus: http.StatusBadRequest,
			wantError:  tooManyAdminTenantLabelsMessage,
		},
		{
			name:       "invalid label key",
			metadata:   map[string]any{"label": map[string]string{"Example.com/environment": "production"}},
			wantStatus: http.StatusBadRequest,
			wantError:  invalidAdminTenantLabelKeyMessage,
		},
		{
			name:       "label name too long",
			metadata:   map[string]any{"label": map[string]string{strings.Repeat("k", 64): "production"}},
			wantStatus: http.StatusBadRequest,
			wantError:  invalidAdminTenantLabelKeyMessage,
		},
		{
			name:       "label value too long",
			metadata:   map[string]any{"label": map[string]string{"environment": strings.Repeat("v", 64)}},
			wantStatus: http.StatusBadRequest,
			wantError:  invalidAdminTenantLabelValueMessage,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			rt := newQuotaRuntime(t, tenant.ProviderTiDBCloudNative)
			rt.prov.iamIdentities = []*tenant.TiDBCloudAPIKeyIdentity{{OrganizationID: "org-1", Role: tenant.TiDBCloudRoleOrgOwner}}
			ts := httptest.NewServer(rt.server)
			defer ts.Close()

			body := map[string]any{"public_key": "public-1", "private_key": "private-1"}
			for key, value := range tt.metadata {
				body[key] = value
			}
			resp := postJSON(t, ts.URL+"/v1/admin/tenants", body, "")
			defer func() { _ = resp.Body.Close() }()
			gotBody, err := io.ReadAll(resp.Body)
			if err != nil {
				t.Fatal(err)
			}
			if resp.StatusCode != tt.wantStatus {
				t.Fatalf("status = %d, want %d: %s", resp.StatusCode, tt.wantStatus, gotBody)
			}
			if !strings.Contains(string(gotBody), tt.wantError) {
				t.Fatalf("body = %q, want error %q", gotBody, tt.wantError)
			}
			if rt.prov.markPoolUsedCalls.Load() != 0 {
				t.Fatalf("mark pool used calls = %d, want 0", rt.prov.markPoolUsedCalls.Load())
			}
		})
	}
}

func TestAdminTenantMetadataValidationBoundaries(t *testing.T) {
	validDisplayNames := []string{"", "abcd", "a-b1", strings.Repeat("x", 64)}
	for _, displayName := range validDisplayNames {
		labels := tenantMetadataTestLabels(28)
		labels[strings.Repeat("a", 253)+"/"+strings.Repeat("b", 63)] = strings.Repeat("v", 63)
		labels["example.com/empty"] = ""
		metadata := meta.TenantMetadata{DisplayName: displayName, Label: labels}
		if err := validateAdminTenantMetadata(metadata); err != nil {
			t.Errorf("valid boundary metadata with display name %q: %v", displayName, err)
		}
	}
}

func TestKubernetesLabelValidation(t *testing.T) {
	tests := []struct {
		name  string
		value string
		valid bool
	}{
		{name: "unprefixed key", value: "Environment_1", valid: true},
		{name: "qualified key", value: "example.com/environment", valid: true},
		{name: "maximum lengths", value: strings.Repeat("a", 253) + "/" + strings.Repeat("b", 63), valid: true},
		{name: "empty prefix", value: "/environment", valid: false},
		{name: "empty name", value: "example.com/", valid: false},
		{name: "multiple separators", value: "example.com/team/environment", valid: false},
		{name: "uppercase prefix", value: "Example.com/environment", valid: false},
		{name: "prefix too long", value: strings.Repeat("a", 254) + "/environment", valid: false},
		{name: "name too long", value: strings.Repeat("a", 64), valid: false},
		{name: "unicode name", value: "environment-生产", valid: false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := isKubernetesLabelKey(tt.value); got != tt.valid {
				t.Fatalf("isKubernetesLabelKey(%q) = %t, want %t", tt.value, got, tt.valid)
			}
		})
	}

	values := []struct {
		name  string
		value string
		valid bool
	}{
		{name: "empty", value: "", valid: true},
		{name: "maximum length", value: strings.Repeat("v", 63), valid: true},
		{name: "too long", value: strings.Repeat("v", 64), valid: false},
		{name: "invalid start", value: "-production", valid: false},
		{name: "unicode", value: "生产", valid: false},
	}
	for _, tt := range values {
		t.Run("value "+tt.name, func(t *testing.T) {
			if got := isKubernetesLabelValue(tt.value); got != tt.valid {
				t.Fatalf("isKubernetesLabelValue(%q) = %t, want %t", tt.value, got, tt.valid)
			}
		})
	}
}

func tenantMetadataTestLabels(count int) map[string]string {
	labels := make(map[string]string, count)
	for i := 0; i < count; i++ {
		labels[fmt.Sprintf("key-%d", i)] = "value"
	}
	return labels
}

func setAdminTestCredentials(req *http.Request) {
	req.Header.Set(quotaPublicKeyHeader, "public-1")
	req.Header.Set(quotaPrivateKeyHeader, "private-1")
}
