package main

import (
	"strings"
	"testing"

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

func TestTiDBCloudAPIModesDefaultToOpenAPI(t *testing.T) {
	t.Setenv(envTiDBCloudIAMAPIMode, "")
	t.Setenv(envTiDBCloudBillingAPIMode, "")
	t.Setenv(envTiDBCloudInnerAPIToken, "")

	modes, err := tiDBCloudAPIModesFromEnv(tenant.ProviderTiDBCloudNativeShared)
	if err != nil {
		t.Fatalf("tiDBCloudAPIModesFromEnv: %v", err)
	}
	if modes.iam != apiModeOpenAPI || modes.billing != apiModeOpenAPI {
		t.Fatalf("modes = %#v, want both openapi", modes)
	}
}

func TestTiDBCloudAPIModesRejectInvalidValueWithoutLeakingToken(t *testing.T) {
	const secret = "inner-token-must-not-leak"
	t.Setenv(envTiDBCloudIAMAPIMode, "side-channel")
	t.Setenv(envTiDBCloudBillingAPIMode, apiModeOpenAPI)
	t.Setenv(envTiDBCloudInnerAPIToken, secret)

	_, err := tiDBCloudAPIModesFromEnv(tenant.ProviderTiDBCloudNativeShared)
	if err == nil || !strings.Contains(err.Error(), envTiDBCloudIAMAPIMode) {
		t.Fatalf("error = %v, want invalid IAM mode", err)
	}
	if strings.Contains(err.Error(), secret) {
		t.Fatalf("error leaked inner token: %v", err)
	}
}

func TestTiDBCloudAPIModesRejectMixedSharedModes(t *testing.T) {
	tests := []struct {
		name    string
		iam     string
		billing string
	}{
		{name: "inner IAM with OpenAPI Billing", iam: apiModeInner, billing: apiModeOpenAPI},
		{name: "OpenAPI IAM with inner Billing", iam: apiModeOpenAPI, billing: apiModeInner},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Setenv(envTiDBCloudIAMAPIMode, tt.iam)
			t.Setenv(envTiDBCloudBillingAPIMode, tt.billing)
			t.Setenv(envTiDBCloudInnerAPIToken, "inner-token")

			_, err := tiDBCloudAPIModesFromEnv(tenant.ProviderTiDBCloudNativeShared)
			if err == nil || !strings.Contains(err.Error(), "must use the same mode") {
				t.Fatalf("error = %v, want mixed-mode rejection", err)
			}
		})
	}
}

func TestTiDBCloudAPIModesRequireSharedTokenForInnerMode(t *testing.T) {
	t.Setenv(envTiDBCloudIAMAPIMode, apiModeInner)
	t.Setenv(envTiDBCloudBillingAPIMode, apiModeInner)
	t.Setenv(envTiDBCloudInnerAPIToken, "")

	_, err := tiDBCloudAPIModesFromEnv(tenant.ProviderTiDBCloudNativeShared)
	if err == nil || !strings.Contains(err.Error(), envTiDBCloudInnerAPIToken) {
		t.Fatalf("error = %v, want missing token", err)
	}
}

func TestInnerClientsUseExistingURLs(t *testing.T) {
	t.Setenv(envTiDBCloudIAMAPIMode, apiModeInner)
	t.Setenv(envTiDBCloudBillingAPIMode, apiModeInner)
	t.Setenv(envTiDBCloudInnerAPIToken, "inner-token")
	t.Setenv(tidbcloudnative.EnvTiDBCloudIAMAPIURL, "https://iam.example.com")
	t.Setenv(tidbcloudnative.EnvTiDBCloudBillingAPIURL, "https://billing.example.com")

	modes, err := tiDBCloudAPIModesFromEnv(tenant.ProviderTiDBCloudNativeShared)
	if err != nil {
		t.Fatalf("tiDBCloudAPIModesFromEnv: %v", err)
	}
	iamClient, err := innerClientFromEnv(modes.iam, tidbcloudnative.EnvTiDBCloudIAMAPIURL)
	if err != nil {
		t.Fatalf("innerClientFromEnv: %v", err)
	}
	billingClient, err := innerClientFromEnv(modes.billing, tidbcloudnative.EnvTiDBCloudBillingAPIURL)
	if err != nil {
		t.Fatalf("billing client: %v", err)
	}
	if iamClient == nil || billingClient == nil {
		t.Fatalf("clients = %#v/%#v, want both clients", iamClient, billingClient)
	}
}

func TestInnerModeRequiresBothClients(t *testing.T) {
	t.Setenv(envTiDBCloudIAMAPIMode, apiModeInner)
	t.Setenv(envTiDBCloudBillingAPIMode, apiModeInner)
	t.Setenv(envTiDBCloudInnerAPIToken, "inner-token")
	t.Setenv(tidbcloudnative.EnvTiDBCloudIAMAPIURL, "https://iam.example.com")
	t.Setenv(tidbcloudnative.EnvTiDBCloudBillingAPIURL, "https://billing.example.com")

	modes, err := tiDBCloudAPIModesFromEnv(tenant.ProviderTiDBCloudNativeShared)
	if err != nil {
		t.Fatalf("tiDBCloudAPIModesFromEnv: %v", err)
	}
	iamClient, err := innerClientFromEnv(modes.iam, tidbcloudnative.EnvTiDBCloudIAMAPIURL)
	if err != nil {
		t.Fatalf("IAM client: %v", err)
	}
	billingClient, err := innerClientFromEnv(modes.billing, tidbcloudnative.EnvTiDBCloudBillingAPIURL)
	if err != nil {
		t.Fatalf("Billing client: %v", err)
	}
	if iamClient == nil || billingClient == nil {
		t.Fatalf("clients = %#v/%#v, want both clients", iamClient, billingClient)
	}
}

func TestTiDBCloudAPIModesRejectInnerIAMForNativeProvider(t *testing.T) {
	t.Setenv(envTiDBCloudIAMAPIMode, apiModeInner)
	t.Setenv(envTiDBCloudBillingAPIMode, apiModeOpenAPI)
	t.Setenv(envTiDBCloudInnerAPIToken, "inner-token")

	_, err := tiDBCloudAPIModesFromEnv(tenant.ProviderTiDBCloudNative)
	if err == nil || !strings.Contains(err.Error(), envTiDBCloudIAMAPIMode) || !strings.Contains(err.Error(), tenant.ProviderTiDBCloudNative) {
		t.Fatalf("error = %v, want unsupported native inner IAM configuration", err)
	}
}

func TestTiDBCloudAPIModesRejectInnerBillingForNativeProvider(t *testing.T) {
	t.Setenv(envTiDBCloudIAMAPIMode, apiModeOpenAPI)
	t.Setenv(envTiDBCloudBillingAPIMode, apiModeInner)
	t.Setenv(envTiDBCloudInnerAPIToken, "inner-token")

	_, err := tiDBCloudAPIModesFromEnv(tenant.ProviderTiDBCloudNative)
	if err == nil || !strings.Contains(err.Error(), envTiDBCloudBillingAPIMode) || !strings.Contains(err.Error(), tenant.ProviderTiDBCloudNative) {
		t.Fatalf("error = %v, want unsupported native inner Billing configuration", err)
	}
}

func TestInnerClientProvidersFlattenNilPointers(t *testing.T) {
	var client *innerauth.Client
	if provider := innerAccountProvider(client); provider != nil {
		t.Fatalf("account provider = %#v, want nil", provider)
	}
	if provider := innerBillingPlanProvider(client); provider != nil {
		t.Fatalf("billing provider = %#v, want nil", provider)
	}
}

func TestInnerDataClientDisabledByDefault(t *testing.T) {
	t.Setenv(envTiDBCloudDataAPIURL, "")
	t.Setenv(envTiDBCloudDataAPIToken, "")

	client, err := innerDataClientFromEnv(tenant.ProviderTiDBCloudNativeShared)
	if err != nil {
		t.Fatalf("innerDataClientFromEnv: %v", err)
	}
	if client != nil {
		t.Fatalf("client = %#v, want nil", client)
	}
}

func TestInnerDataClientRequiresToken(t *testing.T) {
	t.Setenv(envTiDBCloudDataAPIURL, "https://data.example.com")
	t.Setenv(envTiDBCloudDataAPIToken, "")

	_, err := innerDataClientFromEnv(tenant.ProviderTiDBCloudNativeShared)
	if err == nil || !strings.Contains(err.Error(), envTiDBCloudDataAPIToken) {
		t.Fatalf("error = %v, want missing %s", err, envTiDBCloudDataAPIToken)
	}
}

func TestInnerDataClientRequiresURLWithoutLeakingToken(t *testing.T) {
	const secret = "data-token-must-not-leak"
	t.Setenv(envTiDBCloudDataAPIURL, "")
	t.Setenv(envTiDBCloudDataAPIToken, secret)

	_, err := innerDataClientFromEnv(tenant.ProviderTiDBCloudNativeShared)
	if err == nil || !strings.Contains(err.Error(), envTiDBCloudDataAPIURL) {
		t.Fatalf("error = %v, want missing %s", err, envTiDBCloudDataAPIURL)
	}
	if strings.Contains(err.Error(), secret) {
		t.Fatalf("error leaked data API token: %v", err)
	}
}

func TestInnerDataClientRejectsNonSharedProviders(t *testing.T) {
	for _, provider := range []string{tenant.ProviderTiDBCloudNative, tenant.ProviderTiDBZero, tenant.ProviderDB9, tenant.ProviderLocal} {
		t.Run(provider, func(t *testing.T) {
			t.Setenv(envTiDBCloudDataAPIURL, "https://data.example.com")
			t.Setenv(envTiDBCloudDataAPIToken, "data-token")

			_, err := innerDataClientFromEnv(provider)
			if err == nil || !strings.Contains(err.Error(), tenant.ProviderTiDBCloudNativeShared) {
				t.Fatalf("error = %v, want %s-only rejection", err, tenant.ProviderTiDBCloudNativeShared)
			}
		})
	}
}

func TestInnerDataClientFromEnv(t *testing.T) {
	t.Setenv(envTiDBCloudDataAPIURL, "https://data.example.com")
	t.Setenv(envTiDBCloudDataAPIToken, "data-token")

	client, err := innerDataClientFromEnv(tenant.ProviderTiDBCloudNativeShared)
	if err != nil {
		t.Fatalf("innerDataClientFromEnv: %v", err)
	}
	if client == nil {
		t.Fatalf("client = nil, want configured data-plane inner client")
	}
}

func TestInnerDataClientNamesEnvOnInvalidURL(t *testing.T) {
	t.Setenv(envTiDBCloudDataAPIURL, "https://user:secret@data.example.com")
	t.Setenv(envTiDBCloudDataAPIToken, "data-token")

	_, err := innerDataClientFromEnv(tenant.ProviderTiDBCloudNativeShared)
	if err == nil || !strings.Contains(err.Error(), envTiDBCloudDataAPIURL) {
		t.Fatalf("error = %v, want %s named", err, envTiDBCloudDataAPIURL)
	}
	if strings.Contains(err.Error(), "data-token") {
		t.Fatalf("error leaked data API token: %v", err)
	}
}

func TestSharedAccessProviders(t *testing.T) {
	newClient := func(t *testing.T, baseURL string) *innerauth.Client {
		t.Helper()
		client, err := innerauth.NewClient(innerauth.ClientConfig{BaseURL: baseURL, Token: "token"})
		if err != nil {
			t.Fatalf("innerauth.NewClient: %v", err)
		}
		return client
	}

	t.Run("data client overrides both slots", func(t *testing.T) {
		iam := newClient(t, "https://iam.example.com")
		billing := newClient(t, "https://billing.example.com")
		data := newClient(t, "https://data.example.com")
		account, plan := sharedAccessProviders(iam, billing, data)
		if account != innerauth.AccountProvider(data) {
			t.Fatalf("account provider = %#v, want the data client", account)
		}
		if plan != innerauth.BillingPlanProvider(data) {
			t.Fatalf("billing provider = %#v, want the data client", plan)
		}
	})

	t.Run("nil data keeps control-plane clients", func(t *testing.T) {
		iam := newClient(t, "https://iam.example.com")
		billing := newClient(t, "https://billing.example.com")
		account, plan := sharedAccessProviders(iam, billing, nil)
		if account != innerauth.AccountProvider(iam) {
			t.Fatalf("account provider = %#v, want the IAM client", account)
		}
		if plan != innerauth.BillingPlanProvider(billing) {
			t.Fatalf("billing provider = %#v, want the Billing client", plan)
		}
	})

	t.Run("nil data flattens typed-nil control-plane clients", func(t *testing.T) {
		var iam, billing *innerauth.Client
		account, plan := sharedAccessProviders(iam, billing, nil)
		if account != nil || plan != nil {
			t.Fatalf("providers = %#v/%#v, want both nil", account, plan)
		}
	})
}
