package resourcepool

import (
	"net"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/gorm"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

// Mock/Minimal Schema definitions for testing purposes.
type MockSchemaProvider struct {
	ID   uint `gorm:"primaryKey"`
	Name string
}

type MockSchemaRegion struct {
	ID         uint `gorm:"primaryKey"`
	RegionName string
}

// MockSchemaResourcePool mirrors fields accessed in resourcepool.go and cvt.go
type MockSchemaResourcePool struct {
	ID         uint64
	Name       string
	ProviderId uint64
	RegionId   uint64
	OwnerType  int
	OwnerId    uint64
	CreatedAt  time.Time
	UpdatedAt  time.Time
	DeletedAt  gorm.DeletedAt

	Provider MockSchemaProvider `gorm:"foreignKey:ProviderId"`
	Region   MockSchemaRegion   `gorm:"foreignKey:RegionId"`
}

// MockSchemaResourcePoolStrategy mirrors fields accessed in resourcepoolstrategy.go and cvt.go
type MockSchemaResourcePoolStrategy struct {
	gorm.Model

	OrganizationId        uint64
	DefaultResourcePoolId uint64
	ProviderId            uint64
	RegionId              uint64

	Provider MockSchemaProvider `gorm:"foreignKey:ProviderId"`
	Region   MockSchemaRegion   `gorm:"foreignKey:RegionId"`
}

// MockResourcePool is a minimal entity mock for testing.
type MockResourcePool struct {
	*globalngv1.ResourcePool

	RegionId        uint64                            // ID in the DB
	CloudProviderId uint64                            // ID in the DB
	ProviderId      globalcommonv1.CloudProvider_Name // Enum name
}

// MockResourcePoolStrategy is a minimal entity mock for testing.
type MockResourcePoolStrategy struct {
	*globalngv1.ResourcePoolStrategy

	RegionId        uint64                            // ID in the DB
	CloudProviderId uint64                            // ID in the DB
	ProviderId      globalcommonv1.CloudProvider_Name // Enum name
}

// TestEnToModel validates the conversion from entity.ResourcePool to schema.ResourcePool, covering nil input and validation errors.
func TestEnToModel(t *testing.T) {
	// Test case 1: nil input
	model, err := enToModel(nil)
	assert.Nil(t, err, "Should not return error for nil input")
	assert.Nil(t, model, "Should return nil model for nil input")

	// Test case 2: Unspecified OwnerType error
	enUnspecified := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			OwnerType: globalngv1.ResourcePool_OWNER_TYPE_UNSPECIFIED,
		},
	}
	_, err = enToModel(enUnspecified)
	assert.NotNil(t, err, "Should return error for unspecified owner type")
	assert.Equal(t, bizerr.ErrCodeParams, err.ErrCode(), "Should be a bizerr.ErrCodeParams")

	// Test case 3: Valid conversion
	enValid := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			Id:                    100,
			Name:                  "test-pool",
			OwnerType:             globalngv1.ResourcePool_ORGANIZATION,
			OwnerId:               200,
			VcpusLimit:            500,
			PcaArn:                "arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/abc",
			PlacementGroupEnabled: true,
			HighAvailabilityType:  globalngv1.ResourcePool_ZONAL,
		},
		CloudProviderId: 1,
		RegionId:        2,
	}
	model, err = enToModel(enValid)
	assert.Nil(t, err, "Should not return error for valid entity")
	assert.Equal(t, uint64(100), model.ID)
	assert.Equal(t, int(globalngv1.ResourcePool_ORGANIZATION), model.OwnerType)
	assert.Equal(t, uint64(500), model.VcpusLimit, "VcpusLimit should be converted correctly")
	assert.Equal(t, "arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/abc", model.PcaArn, "PcaArn should be converted correctly")
	assert.True(t, model.PlacementGroupEnabled, "PlacementGroupEnabled should be converted correctly")
	assert.Equal(t, globalngv1.ResourcePool_ZONAL.String(), model.HighAvailabilityType, "HighAvailabilityType should be converted correctly")

	// Test case 4: PlacementGroupEnabled=false round-trip
	enFalse := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			Id:        101,
			Name:      "test-pool-2",
			OwnerType: globalngv1.ResourcePool_SHARED,
			OwnerId:   200,
		},
		CloudProviderId: 1,
		RegionId:        2,
	}
	model, err = enToModel(enFalse)
	assert.Nil(t, err, "Should not return error for valid entity with PlacementGroupEnabled=false")
	assert.False(t, model.PlacementGroupEnabled, "PlacementGroupEnabled false should round-trip correctly")
}

// TestModelToEn validates the conversion from schema.ResourcePool to entity.ResourcePool, covering nil input and enum conversion failures.
func TestModelToEn(t *testing.T) {
	// Test case 1: nil input
	en, err := modelToEn(nil)
	assert.Nil(t, err, "Should not return error for nil input")
	assert.Nil(t, en, "Should return nil entity for nil input")

	// Base valid model for reuse
	modelValid := &schema.ResourcePool{
		ID:                    101,
		OwnerType:             int(globalngv1.ResourcePool_SHARED),
		VcpusLimit:            300,
		PcaArn:                "arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/abc",
		PlacementGroupEnabled: true,
		HighAvailabilityType:  globalngv1.ResourcePool_REGIONAL.String(),
		Provider: &schema.Provider{
			Model: gorm.Model{
				ID: 1,
			},
			Name: globalcommonv1.CloudProvider_AWS.String(),
		},
		Region: &schema.Region{
			Model: gorm.Model{
				ID: 2,
			},
			RegionName: "us-east-1",
		},
	}

	// Test case 2: Valid conversion
	en, err = modelToEn(modelValid)
	assert.Nil(t, err, "Should not return error for valid model")
	assert.Equal(t, uint64(101), en.Id)
	assert.Equal(t, globalcommonv1.CloudProvider_AWS, en.ProviderId)
	assert.Equal(t, uint64(300), en.VcpusLimit, "VcpusLimit should be converted correctly")
	assert.Equal(t, "arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/abc", en.PcaArn, "PcaArn should be converted correctly")
	assert.True(t, en.GetPlacementGroupEnabled(), "PlacementGroupEnabled should be converted correctly")
	assert.Equal(t, globalngv1.ResourcePool_REGIONAL, en.GetHighAvailabilityType(), "HighAvailabilityType should be converted correctly")

	// Test case 3: Invalid Provider Name
	modelInvalidProvider := *modelValid
	modelInvalidProvider.Provider.Name = "INVALID_PROVIDER"
	_, err = modelToEn(&modelInvalidProvider)
	assert.NotNil(t, err, "Should return error for invalid provider name")

	// Test case 4: Invalid Region Name
	modelInvalidRegion := *modelValid
	modelInvalidRegion.Region.RegionName = "INVALID_REGION"
	_, err = modelToEn(&modelInvalidRegion)
	assert.NotNil(t, err, "Should return error for invalid region name")

	// Test case 5: PlacementGroupEnabled=false round-trip (fresh pointers to avoid mutation from cases 3/4)
	modelFalse := &schema.ResourcePool{
		ID:                    102,
		OwnerType:             int(globalngv1.ResourcePool_SHARED),
		VcpusLimit:            300,
		PlacementGroupEnabled: false,
		Provider: &schema.Provider{
			Model: gorm.Model{ID: 1},
			Name:  globalcommonv1.CloudProvider_AWS.String(),
		},
		Region: &schema.Region{
			Model:      gorm.Model{ID: 2},
			RegionName: "us-east-1",
		},
	}
	en, err = modelToEn(modelFalse)
	assert.Nil(t, err, "Should not return error for valid model with PlacementGroupEnabled=false")
	assert.False(t, en.GetPlacementGroupEnabled(), "PlacementGroupEnabled false should round-trip correctly")
	assert.Equal(t, globalngv1.ResourcePool_HIGH_AVAILABILITY_TYPE_UNSPECIFIED, en.GetHighAvailabilityType())
}

func TestResourcePoolMetadataRoundTrip(t *testing.T) {
	t.Parallel()

	tags := map[string]string{
		"cost-center": "database",
		"environment": "production",
	}
	original := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			Id:                701,
			Name:              "  Straße-Pool  ",
			OwnerType:         globalngv1.ResourcePool_ORGANIZATION,
			OwnerId:           99,
			Cidr:              "10.24.7.19/24",
			ServicePlan:       globalcommonv1.ServicePlan_BYOC,
			LifecycleState:    globalngv1.ResourcePool_MODIFYING,
			StateMessageCode:  "RESOURCE_POOL_SCALING",
			VcpuLimitMode:     globalngv1.ResourcePool_LIMITED,
			CloudResourceTags: tags,
			CreatedBy:         "operator@example.com",
		},
		CloudProviderId: 1,
		RegionId:        2,
	}

	model, err := enToModel(original)
	require.NoError(t, err)
	assert.Equal(t, "strasse-pool", model.NormalizedName)
	assert.Equal(t, []byte(net.IPv4(10, 24, 7, 0).To4()), model.CidrStartIPv4)
	assert.Equal(t, []byte(net.IPv4(10, 24, 7, 255).To4()), model.CidrEndIPv4)
	assert.Equal(t, globalngv1.ResourcePool_MODIFYING.String(), model.LifecycleState)
	assert.Equal(t, "RESOURCE_POOL_SCALING", model.StateMessageCode)
	assert.Equal(t, globalngv1.ResourcePool_LIMITED.String(), model.VcpuLimitMode)
	assert.Equal(t, tags, model.CloudResourceTags)
	require.NotNil(t, model.CreatedBy)
	assert.Equal(t, "operator@example.com", *model.CreatedBy)

	// Conversion owns its map and byte slices rather than aliasing caller data.
	tags["environment"] = "mutated"
	assert.Equal(t, "production", model.CloudResourceTags["environment"])
	assert.Equal(t, []byte(net.IPv4(10, 24, 7, 0).To4()), model.CidrStartIPv4)

	model.Provider = &schema.Provider{
		Model: gorm.Model{ID: 1},
		Name:  globalcommonv1.CloudProvider_AWS.String(),
	}
	model.Region = &schema.Region{
		Model:      gorm.Model{ID: 2},
		RegionName: "us-east-1",
	}

	roundTripped, err := modelToEn(model)
	require.NoError(t, err)
	assert.Equal(t, "strasse-pool", roundTripped.NormalizedName)
	assert.Equal(t, globalngv1.ResourcePool_MODIFYING, roundTripped.GetLifecycleState())
	assert.Equal(t, "RESOURCE_POOL_SCALING", roundTripped.GetStateMessageCode())
	assert.Equal(t, globalngv1.ResourcePool_LIMITED, roundTripped.GetVcpuLimitMode())
	assert.Equal(t, model.CloudResourceTags, roundTripped.GetCloudResourceTags())
	assert.Equal(t, "operator@example.com", roundTripped.GetCreatedBy())

	model.CloudResourceTags["environment"] = "changed-again"
	assert.Equal(t, "production", roundTripped.GetCloudResourceTags()["environment"])
}

func TestResourcePoolMetadataRoundTripForNonBYOCServicePlans(t *testing.T) {
	t.Parallel()

	servicePlans := []globalcommonv1.ServicePlan{
		globalcommonv1.ServicePlan_Essential,
		globalcommonv1.ServicePlan_Premium,
		globalcommonv1.ServicePlan_Premium_Reserved,
		globalcommonv1.ServicePlan_Essential_V2,
	}
	for _, servicePlan := range servicePlans {
		t.Run(servicePlan.String(), func(t *testing.T) {
			t.Parallel()

			original := &entity.ResourcePool{
				ResourcePool: &globalngv1.ResourcePool{
					Id:             800,
					Name:           "pool-" + servicePlan.String(),
					OwnerType:      globalngv1.ResourcePool_ORGANIZATION,
					OwnerId:        99,
					Cidr:           "10.24.7.19/24",
					ServicePlan:    servicePlan,
					LifecycleState: globalngv1.ResourcePool_ACTIVE,
				},
				CloudProviderId: 1,
				RegionId:        2,
			}

			model, err := enToModel(original)
			require.NoError(t, err)
			require.Equal(t, servicePlan.String(), model.ServicePlan)
			require.Equal(t, globalngv1.ResourcePool_ACTIVE.String(), model.LifecycleState)
			require.Len(t, model.CidrStartIPv4, 4)
			require.Len(t, model.CidrEndIPv4, 4)

			model.Provider = &schema.Provider{
				Model: gorm.Model{ID: 1},
				Name:  globalcommonv1.CloudProvider_AWS.String(),
			}
			model.Region = &schema.Region{
				Model:      gorm.Model{ID: 2},
				RegionName: "us-east-1",
			}
			converted, err := modelToEn(model)
			require.NoError(t, err)
			require.Equal(t, servicePlan, converted.GetServicePlan())
			require.Equal(t, globalngv1.ResourcePool_ACTIVE, converted.GetLifecycleState())
			require.NotNil(t, converted.CanonicalCIDR)
		})
	}
}

func TestResourcePoolConverterRejectsInvalidCIDRForAllServicePlans(t *testing.T) {
	t.Parallel()

	servicePlans := []globalcommonv1.ServicePlan{
		globalcommonv1.ServicePlan_BYOC,
		globalcommonv1.ServicePlan_Premium,
		globalcommonv1.ServicePlan_Premium_Reserved,
		globalcommonv1.ServicePlan_Essential,
		globalcommonv1.ServicePlan_Essential_V2,
	}
	for _, servicePlan := range servicePlans {
		t.Run(servicePlan.String(), func(t *testing.T) {
			t.Parallel()

			_, err := enToModel(&entity.ResourcePool{
				ResourcePool: &globalngv1.ResourcePool{
					Name:        "invalid-cidr-pool",
					OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
					OwnerId:     99,
					Cidr:        "not-a-cidr",
					ServicePlan: servicePlan,
				},
			})

			require.Error(t, err)
			require.Equal(t, bizerr.ErrCodeParams, err.ErrCode())
		})
	}
}

func TestResourcePoolMetadataCompatibilityDefaults(t *testing.T) {
	t.Parallel()

	model := &schema.ResourcePool{
		ID:        702,
		OwnerType: int(globalngv1.ResourcePool_ORGANIZATION),
		Provider: &schema.Provider{
			Model: gorm.Model{ID: 1},
			Name:  globalcommonv1.CloudProvider_AWS.String(),
		},
		Region: &schema.Region{
			Model:      gorm.Model{ID: 2},
			RegionName: "us-east-1",
		},
	}

	converted, err := modelToEn(model)
	require.NoError(t, err)
	assert.Equal(t, globalngv1.ResourcePool_LIFECYCLE_STATE_UNSPECIFIED, converted.GetLifecycleState())
	assert.Equal(t, globalngv1.ResourcePool_INHERITED, converted.GetVcpuLimitMode())
	assert.Empty(t, converted.GetCreatedBy())
	assert.Nil(t, converted.GetCloudResourceTags())
}

func TestResourcePoolMetadataRejectsInvalidPersistedEnums(t *testing.T) {
	t.Parallel()

	base := func() *schema.ResourcePool {
		return &schema.ResourcePool{
			ID:        703,
			OwnerType: int(globalngv1.ResourcePool_ORGANIZATION),
			Provider: &schema.Provider{
				Model: gorm.Model{ID: 1},
				Name:  globalcommonv1.CloudProvider_AWS.String(),
			},
			Region: &schema.Region{
				Model:      gorm.Model{ID: 2},
				RegionName: "us-east-1",
			},
		}
	}

	t.Run("lifecycle state", func(t *testing.T) {
		model := base()
		model.LifecycleState = "RAW_REGIONAL_ERROR"
		_, err := modelToEn(model)
		assert.Error(t, err)
	})

	t.Run("vCPU limit mode", func(t *testing.T) {
		model := base()
		model.VcpuLimitMode = "MAYBE_UNLIMITED"
		_, err := modelToEn(model)
		assert.Error(t, err)
	})
}

// TestEnToModelStrategy validates the conversion from entity.ResourcePoolStrategy to schema.ResourcePoolStrategy, covering nil input and all zero ID validation errors.
func TestEnToModelStrategy(t *testing.T) {
	// Test case 1: nil input
	model, err := enToModelStrategy(nil)
	assert.Nil(t, err, "Should not return error for nil input")
	assert.Empty(t, model.ID, "Should return empty model for nil input")

	// Test case 2: Empty OrganizationID error
	_, err = enToModelStrategy(&entity.ResourcePoolStrategy{ResourcePoolStrategy: &globalngv1.ResourcePoolStrategy{
		OrganizationId: 10,
	}})
	assert.NotNil(t, err, "Should return error for zero organization id")
	assert.Equal(t, bizerr.ErrCodeParams, err.ErrCode(), "Should be a bizerr.ErrCodeParams")

	// Test case 3: Empty CloudProviderId error
	_, err = enToModelStrategy(&entity.ResourcePoolStrategy{
		ResourcePoolStrategy: &globalngv1.ResourcePoolStrategy{
			OrganizationId: 10,
		},
		CloudProviderId: 0,
	})
	assert.NotNil(t, err, "Should return error for zero cloud provider id")
	assert.Equal(t, bizerr.ErrCodeParams, err.ErrCode(), "Should be a bizerr.ErrCodeParams")

	// Test case 4: Empty RegionId error
	_, err = enToModelStrategy(&entity.ResourcePoolStrategy{
		ResourcePoolStrategy: &globalngv1.ResourcePoolStrategy{
			OrganizationId: 10,
		},
		CloudProviderId: 1,
		RegionId:        0,
	})
	assert.NotNil(t, err, "Should return error for zero region id")
	assert.Equal(t, bizerr.ErrCodeParams, err.ErrCode(), "Should be a bizerr.ErrCodeParams")

	// Test case 5: Valid conversion
	enValid := &entity.ResourcePoolStrategy{
		ResourcePoolStrategy: &globalngv1.ResourcePoolStrategy{
			Id:             50,
			OrganizationId: 5,
			ServicePlan:    globalcommonv1.ServicePlan_Premium,
		},
		CloudProviderId: 1,
		RegionId:        2,
	}
	model, err = enToModelStrategy(enValid)
	assert.Nil(t, err, "Should not return error for valid entity")
	assert.Equal(t, uint(50), model.ID)
	assert.Equal(t, globalcommonv1.ServicePlan_Premium.String(), model.ServicePlan)
}

// TestModelToEnStrategy validates the conversion from schema.ResourcePoolStrategy to entity.ResourcePoolStrategy, covering nil input and enum conversion failures.
func TestModelToEnStrategy(t *testing.T) {
	// Test case 1: nil input
	en, err := modelToEnStrategy(nil)
	assert.Nil(t, err, "Should not return error for nil input")
	assert.Empty(t, en.GetId(), "Should return empty entity for nil input")

	// Base valid model for reuse
	modelValid := &schema.ResourcePoolStrategy{
		Model: gorm.Model{
			ID: 51,
		},
		ServicePlan: globalcommonv1.ServicePlan_Premium.String(),
		Provider: &schema.Provider{
			Model: gorm.Model{
				ID: 1,
			},
			Name: globalcommonv1.CloudProvider_AWS.String(),
		},
		Region: &schema.Region{
			Model: gorm.Model{
				ID: 2,
			},
			RegionName: "us-east-1",
		},
	}

	// Test case 2: Valid conversion
	en, err = modelToEnStrategy(modelValid)
	assert.Nil(t, err, "Should not return error for valid model")
	assert.Equal(t, uint64(51), en.Id)
	assert.Equal(t, globalcommonv1.CloudProvider_AWS, en.ProviderId)
	assert.Equal(t, globalcommonv1.ServicePlan_Premium, en.GetServicePlan())

	// Test case 3: Invalid Provider Name
	modelInvalidProvider := *modelValid
	modelInvalidProvider.Provider.Name = "INVALID_PROVIDER"
	_, err = modelToEnStrategy(&modelInvalidProvider)
	assert.NotNil(t, err, "Should return error for invalid provider name")

	// Test case 4: Invalid Region Name
	modelInvalidRegion := *modelValid
	modelInvalidRegion.Region.RegionName = "INVALID_REGION"
	_, err = modelToEnStrategy(&modelInvalidRegion)
	assert.NotNil(t, err, "Should return error for invalid region name")
}
