// Package azure provides the Microsoft Azure provider implementation.
// This file contains unit tests for the Azure provider.
//
// Requirements: 3.2, 3.3, 3.4, 3.6
package azure

import (
	"context"
	"testing"

	"AutoSSO/internal/config"
	"AutoSSO/internal/provider"
)

func TestNewAzureProvider(t *testing.T) {
	t.Run("nil config returns error", func(t *testing.T) {
		_, err := NewAzureProvider(nil)
		if err == nil {
			t.Error("NewAzureProvider(nil) should return error")
		}
	})

	t.Run("empty TenantID returns error", func(t *testing.T) {
		cfg := &config.AzureConfig{
			TenantID:     "",
			ClientID:     "test-client-id",
			ClientSecret: "test-client-secret",
		}
		_, err := NewAzureProvider(cfg)
		if err == nil {
			t.Error("NewAzureProvider with empty TenantID should return error")
		}
	})

	t.Run("empty ClientID returns error", func(t *testing.T) {
		cfg := &config.AzureConfig{
			TenantID:     "test-tenant-id",
			ClientID:     "",
			ClientSecret: "test-client-secret",
		}
		_, err := NewAzureProvider(cfg)
		if err == nil {
			t.Error("NewAzureProvider with empty ClientID should return error")
		}
	})

	t.Run("empty ClientSecret returns error", func(t *testing.T) {
		cfg := &config.AzureConfig{
			TenantID:     "test-tenant-id",
			ClientID:     "test-client-id",
			ClientSecret: "",
		}
		_, err := NewAzureProvider(cfg)
		if err == nil {
			t.Error("NewAzureProvider with empty ClientSecret should return error")
		}
	})

	t.Run("valid config creates provider", func(t *testing.T) {
		cfg := &config.AzureConfig{
			TenantID:     "test-tenant-id",
			ClientID:     "test-client-id",
			ClientSecret: "test-client-secret",
			Subscriptions: []config.CloudAccount{
				{
					ID:         "sub-id-1",
					Name:       "test-subscription",
					Type:       "prod",
					RoleSuffix: "-0",
				},
			},
		}
		p, err := NewAzureProvider(cfg)
		if err != nil {
			t.Fatalf("NewAzureProvider() error = %v", err)
		}
		if p == nil {
			t.Fatal("NewAzureProvider() returned nil provider")
		}
		defer p.Close()

		// Verify provider name
		if p.Name() != "Azure" {
			t.Errorf("Name() = %v, want Azure", p.Name())
		}
	})

	t.Run("subscription map is built correctly", func(t *testing.T) {
		cfg := &config.AzureConfig{
			TenantID:     "test-tenant-id",
			ClientID:     "test-client-id",
			ClientSecret: "test-client-secret",
			Subscriptions: []config.CloudAccount{
				{
					ID:         "ad881159-0593-4502-96e4-e2698b790a3d",
					Name:       "tidb-dedicated-prod-controlplane",
					Type:       "prod",
					RoleSuffix: "",
				},
				{
					ID:         "efe59dff-e62f-4356-b93d-de124c967a1f",
					Name:       "tidb-dedicated-prod-dataplane-000",
					Type:       "prod",
					RoleSuffix: "-0",
				},
				{
					ID:         "01f5d24b-a99c-45e1-a3e2-5ddc1e5bb71d",
					Name:       "tidb-o11y",
					Type:       "prod",
					RoleSuffix: "-o11y",
				},
			},
		}
		p, err := NewAzureProvider(cfg)
		if err != nil {
			t.Fatalf("NewAzureProvider() error = %v", err)
		}
		defer p.Close()

		// Verify subscription map contains both IDs and names
		subMap := p.GetSubscriptionMap()

		// Check by ID
		if _, exists := subMap["ad881159-0593-4502-96e4-e2698b790a3d"]; !exists {
			t.Error("Subscription map should contain subscription by ID")
		}

		// Check by name
		if _, exists := subMap["tidb-dedicated-prod-controlplane"]; !exists {
			t.Error("Subscription map should contain subscription by name")
		}

		// Check role suffix
		if sub, exists := subMap["tidb-dedicated-prod-dataplane-000"]; exists {
			if sub.RoleSuffix != "-0" {
				t.Errorf("RoleSuffix = %v, want -0", sub.RoleSuffix)
			}
		} else {
			t.Error("Subscription map should contain tidb-dedicated-prod-dataplane-000")
		}
	})
}

func TestAzureProvider_Name(t *testing.T) {
	cfg := &config.AzureConfig{
		TenantID:     "test-tenant-id",
		ClientID:     "test-client-id",
		ClientSecret: "test-client-secret",
	}
	p, err := NewAzureProvider(cfg)
	if err != nil {
		t.Fatalf("NewAzureProvider() error = %v", err)
	}
	defer p.Close()

	if p.Name() != string(provider.ProviderAzure) {
		t.Errorf("Name() = %v, want %v", p.Name(), provider.ProviderAzure)
	}
}

func TestAzureProvider_Close(t *testing.T) {
	cfg := &config.AzureConfig{
		TenantID:     "test-tenant-id",
		ClientID:     "test-client-id",
		ClientSecret: "test-client-secret",
	}
	p, err := NewAzureProvider(cfg)
	if err != nil {
		t.Fatalf("NewAzureProvider() error = %v", err)
	}

	// First close should succeed
	err = p.Close()
	if err != nil {
		t.Errorf("Close() error = %v", err)
	}

	// Second close should also succeed (idempotent)
	err = p.Close()
	if err != nil {
		t.Errorf("Second Close() error = %v", err)
	}
}

func TestAzureProvider_AddPermission_Validation(t *testing.T) {
	cfg := &config.AzureConfig{
		TenantID:     "test-tenant-id",
		ClientID:     "test-client-id",
		ClientSecret: "test-client-secret",
	}
	p, err := NewAzureProvider(cfg)
	if err != nil {
		t.Fatalf("NewAzureProvider() error = %v", err)
	}
	defer p.Close()

	ctx := context.Background()

	t.Run("nil request returns error", func(t *testing.T) {
		err := p.AddPermission(ctx, nil)
		if err == nil {
			t.Error("AddPermission(nil) should return error")
		}
	})

	t.Run("empty account ID returns error", func(t *testing.T) {
		req := &provider.PermissionRequest{
			AccountID:      "",
			UserEmail:      "user@example.com",
			PermissionSets: []string{"Reader"},
		}
		err := p.AddPermission(ctx, req)
		if err == nil {
			t.Error("AddPermission with empty AccountID should return error")
		}
	})

	t.Run("empty user email returns error", func(t *testing.T) {
		req := &provider.PermissionRequest{
			AccountID:      "test-subscription-id",
			UserEmail:      "",
			PermissionSets: []string{"Reader"},
		}
		err := p.AddPermission(ctx, req)
		if err == nil {
			t.Error("AddPermission with empty UserEmail should return error")
		}
	})

	t.Run("empty permission sets returns error", func(t *testing.T) {
		req := &provider.PermissionRequest{
			AccountID:      "test-subscription-id",
			UserEmail:      "user@example.com",
			PermissionSets: []string{},
		}
		err := p.AddPermission(ctx, req)
		if err == nil {
			t.Error("AddPermission with empty PermissionSets should return error")
		}
	})
}

func TestAzureProvider_RemovePermission_Validation(t *testing.T) {
	cfg := &config.AzureConfig{
		TenantID:     "test-tenant-id",
		ClientID:     "test-client-id",
		ClientSecret: "test-client-secret",
	}
	p, err := NewAzureProvider(cfg)
	if err != nil {
		t.Fatalf("NewAzureProvider() error = %v", err)
	}
	defer p.Close()

	ctx := context.Background()

	t.Run("nil request returns error", func(t *testing.T) {
		err := p.RemovePermission(ctx, nil)
		if err == nil {
			t.Error("RemovePermission(nil) should return error")
		}
	})

	t.Run("empty account ID returns error", func(t *testing.T) {
		req := &provider.PermissionRequest{
			AccountID:      "",
			UserEmail:      "user@example.com",
			PermissionSets: []string{"Reader"},
		}
		err := p.RemovePermission(ctx, req)
		if err == nil {
			t.Error("RemovePermission with empty AccountID should return error")
		}
	})

	t.Run("empty user email returns error", func(t *testing.T) {
		req := &provider.PermissionRequest{
			AccountID:      "test-subscription-id",
			UserEmail:      "",
			PermissionSets: []string{"Reader"},
		}
		err := p.RemovePermission(ctx, req)
		if err == nil {
			t.Error("RemovePermission with empty UserEmail should return error")
		}
	})

	t.Run("empty permission sets returns error", func(t *testing.T) {
		req := &provider.PermissionRequest{
			AccountID:      "test-subscription-id",
			UserEmail:      "user@example.com",
			PermissionSets: []string{},
		}
		err := p.RemovePermission(ctx, req)
		if err == nil {
			t.Error("RemovePermission with empty PermissionSets should return error")
		}
	})
}

func TestAzureProvider_GetUserID_Validation(t *testing.T) {
	cfg := &config.AzureConfig{
		TenantID:     "test-tenant-id",
		ClientID:     "test-client-id",
		ClientSecret: "test-client-secret",
	}
	p, err := NewAzureProvider(cfg)
	if err != nil {
		t.Fatalf("NewAzureProvider() error = %v", err)
	}
	defer p.Close()

	ctx := context.Background()

	t.Run("empty email returns error", func(t *testing.T) {
		_, err := p.GetUserID(ctx, "")
		if err == nil {
			t.Error("GetUserID with empty email should return error")
		}
	})
}

func TestAzureProvider_ClosedProvider(t *testing.T) {
	cfg := &config.AzureConfig{
		TenantID:     "test-tenant-id",
		ClientID:     "test-client-id",
		ClientSecret: "test-client-secret",
	}
	p, err := NewAzureProvider(cfg)
	if err != nil {
		t.Fatalf("NewAzureProvider() error = %v", err)
	}

	// Close the provider
	p.Close()

	ctx := context.Background()

	t.Run("AddPermission on closed provider returns error", func(t *testing.T) {
		req := &provider.PermissionRequest{
			AccountID:      "test-subscription-id",
			UserEmail:      "user@example.com",
			PermissionSets: []string{"Reader"},
		}
		err := p.AddPermission(ctx, req)
		if err == nil {
			t.Error("AddPermission on closed provider should return error")
		}
	})

	t.Run("RemovePermission on closed provider returns error", func(t *testing.T) {
		req := &provider.PermissionRequest{
			AccountID:      "test-subscription-id",
			UserEmail:      "user@example.com",
			PermissionSets: []string{"Reader"},
		}
		err := p.RemovePermission(ctx, req)
		if err == nil {
			t.Error("RemovePermission on closed provider should return error")
		}
	})

	t.Run("GetUserID on closed provider returns error", func(t *testing.T) {
		_, err := p.GetUserID(ctx, "user@example.com")
		if err == nil {
			t.Error("GetUserID on closed provider should return error")
		}
	})

	t.Run("ValidateCredentials on closed provider returns error", func(t *testing.T) {
		err := p.ValidateCredentials(ctx)
		if err == nil {
			t.Error("ValidateCredentials on closed provider should return error")
		}
	})

	t.Run("AssignRoleDirect on closed provider returns error", func(t *testing.T) {
		err := p.AssignRoleDirect(ctx, "user@example.com", "Reader", "test-subscription-id")
		if err == nil {
			t.Error("AssignRoleDirect on closed provider should return error")
		}
	})

	t.Run("RemoveRoleDirect on closed provider returns error", func(t *testing.T) {
		err := p.RemoveRoleDirect(ctx, "user@example.com", "Reader", "test-subscription-id")
		if err == nil {
			t.Error("RemoveRoleDirect on closed provider should return error")
		}
	})
}

func TestAzureProvider_ImplementsInterface(t *testing.T) {
	cfg := &config.AzureConfig{
		TenantID:     "test-tenant-id",
		ClientID:     "test-client-id",
		ClientSecret: "test-client-secret",
	}
	p, err := NewAzureProvider(cfg)
	if err != nil {
		t.Fatalf("NewAzureProvider() error = %v", err)
	}
	defer p.Close()

	// Verify that AzureProvider implements the Provider interface
	var _ provider.Provider = p
}

func TestAzureProvider_ResolveSubscription(t *testing.T) {
	cfg := &config.AzureConfig{
		TenantID:     "test-tenant-id",
		ClientID:     "test-client-id",
		ClientSecret: "test-client-secret",
		Subscriptions: []config.CloudAccount{
			{
				ID:         "ad881159-0593-4502-96e4-e2698b790a3d",
				Name:       "tidb-dedicated-prod-controlplane",
				Type:       "prod",
				RoleSuffix: "",
			},
			{
				ID:         "efe59dff-e62f-4356-b93d-de124c967a1f",
				Name:       "tidb-dedicated-prod-dataplane-000",
				Type:       "prod",
				RoleSuffix: "-0",
			},
			{
				ID:         "01f5d24b-a99c-45e1-a3e2-5ddc1e5bb71d",
				Name:       "tidb-o11y",
				Type:       "prod",
				RoleSuffix: "-o11y",
			},
		},
	}
	p, err := NewAzureProvider(cfg)
	if err != nil {
		t.Fatalf("NewAzureProvider() error = %v", err)
	}
	defer p.Close()

	t.Run("resolve by subscription name", func(t *testing.T) {
		subID, roleSuffix, err := p.resolveSubscription("tidb-dedicated-prod-controlplane")
		if err != nil {
			t.Fatalf("resolveSubscription() error = %v", err)
		}
		if subID != "ad881159-0593-4502-96e4-e2698b790a3d" {
			t.Errorf("subscriptionID = %v, want ad881159-0593-4502-96e4-e2698b790a3d", subID)
		}
		if roleSuffix != "" {
			t.Errorf("roleSuffix = %v, want empty string", roleSuffix)
		}
	})

	t.Run("resolve by subscription ID", func(t *testing.T) {
		subID, roleSuffix, err := p.resolveSubscription("efe59dff-e62f-4356-b93d-de124c967a1f")
		if err != nil {
			t.Fatalf("resolveSubscription() error = %v", err)
		}
		if subID != "efe59dff-e62f-4356-b93d-de124c967a1f" {
			t.Errorf("subscriptionID = %v, want efe59dff-e62f-4356-b93d-de124c967a1f", subID)
		}
		if roleSuffix != "-0" {
			t.Errorf("roleSuffix = %v, want -0", roleSuffix)
		}
	})

	t.Run("resolve with role suffix -o11y", func(t *testing.T) {
		subID, roleSuffix, err := p.resolveSubscription("tidb-o11y")
		if err != nil {
			t.Fatalf("resolveSubscription() error = %v", err)
		}
		if subID != "01f5d24b-a99c-45e1-a3e2-5ddc1e5bb71d" {
			t.Errorf("subscriptionID = %v, want 01f5d24b-a99c-45e1-a3e2-5ddc1e5bb71d", subID)
		}
		if roleSuffix != "-o11y" {
			t.Errorf("roleSuffix = %v, want -o11y", roleSuffix)
		}
	})

	t.Run("resolve unknown subscription returns as-is", func(t *testing.T) {
		subID, roleSuffix, err := p.resolveSubscription("unknown-subscription-id")
		if err != nil {
			t.Fatalf("resolveSubscription() error = %v", err)
		}
		if subID != "unknown-subscription-id" {
			t.Errorf("subscriptionID = %v, want unknown-subscription-id", subID)
		}
		if roleSuffix != "" {
			t.Errorf("roleSuffix = %v, want empty string", roleSuffix)
		}
	})

	t.Run("resolve with whitespace trimming", func(t *testing.T) {
		subID, _, err := p.resolveSubscription("  tidb-o11y  ")
		if err != nil {
			t.Fatalf("resolveSubscription() error = %v", err)
		}
		if subID != "01f5d24b-a99c-45e1-a3e2-5ddc1e5bb71d" {
			t.Errorf("subscriptionID = %v, want 01f5d24b-a99c-45e1-a3e2-5ddc1e5bb71d", subID)
		}
	})
}

func TestAzureProvider_ApplyRoleSuffix(t *testing.T) {
	cfg := &config.AzureConfig{
		TenantID:     "test-tenant-id",
		ClientID:     "test-client-id",
		ClientSecret: "test-client-secret",
	}
	p, err := NewAzureProvider(cfg)
	if err != nil {
		t.Fatalf("NewAzureProvider() error = %v", err)
	}
	defer p.Close()

	t.Run("apply empty suffix", func(t *testing.T) {
		result := p.applyRoleSuffix("DBaaS-Prod-DevUser-Role", "")
		if result != "DBaaS-Prod-DevUser-Role" {
			t.Errorf("applyRoleSuffix() = %v, want DBaaS-Prod-DevUser-Role", result)
		}
	})

	t.Run("apply -0 suffix", func(t *testing.T) {
		result := p.applyRoleSuffix("DBaaS-Prod-DevUser-Role", "-0")
		if result != "DBaaS-Prod-DevUser-Role-0" {
			t.Errorf("applyRoleSuffix() = %v, want DBaaS-Prod-DevUser-Role-0", result)
		}
	})

	t.Run("apply -o11y suffix", func(t *testing.T) {
		result := p.applyRoleSuffix("DBaaS-Prod-DevUser-Role", "-o11y")
		if result != "DBaaS-Prod-DevUser-Role-o11y" {
			t.Errorf("applyRoleSuffix() = %v, want DBaaS-Prod-DevUser-Role-o11y", result)
		}
	})
}
