package clusterupgradeoperation

import (
	"context"

	o11yv1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"

	"github.com/tidbcloud/observability/pkg/resourceworker/controllers/clusterupgradeoperation/providers"
)

// MockCloudProvider is a mock implementation of CloudProviderInterface for testing
type MockCloudProvider struct {
	NodeGroups                []providers.NodeGroup
	GetNodeGroupsErr          error
	GetNodeGroupByNameErr     map[string]error             // when set for nodeGroupName, GetNodeGroupByName returns this error
	ControlPlaneVersion       string                       // returned by GetControlPlaneVersion when set
	GetControlPlaneVersionErr error                        // when set, GetControlPlaneVersion returns this error
	ControlPlaneStatus        providers.ControlPlaneStatus // returned by GetControlPlaneStatus (default: Active)
	ClusterAddonsStatus       []providers.AddonStatusInfo  // when set, GetClusterAddonsStatus returns it; else default
	GetClusterAddonsStatusErr error                        // when set, GetClusterAddonsStatus returns this error

	// BlueGreen testing: when true, CreateNodeGroup adds the created nodegroup to NodeGroups
	CreateNodeGroupAddsToNodeGroups bool
	// GetNodeGroupStatusOverride: when set, overrides default status for a given nodeGroupID
	GetNodeGroupStatusOverride map[string]providers.NodeGroupStatus
	// CreateNodeGroupErr: when set, CreateNodeGroup returns this error
	CreateNodeGroupErr error
	// CreatedNodeGroupSpecs records specs passed to CreateNodeGroup.
	CreatedNodeGroupSpecs []providers.NodeGroup
	// UpdateNodeGroupMinSizeErr: when set, UpdateNodeGroupMinSize returns this error
	UpdateNodeGroupMinSizeErr error
	// UpdatedNodeGroupMinSizes records min size updates by nodegroup ID.
	UpdatedNodeGroupMinSizes map[string]int
	// DeleteNodeGroupErr: when set, DeleteNodeGroup returns this error
	DeleteNodeGroupErr error

	// GetNodeGroupUpgradeOperationStatusErr: when set, returns this error
	GetNodeGroupUpgradeOperationStatusErr error
	// GetNodeGroupUpgradeOperationStatusInProgress: when true, returns (false, nil) for in-progress
	GetNodeGroupUpgradeOperationStatusInProgress bool
	// UpgradeNodeGroupOperationID: when set, UpgradeNodeGroup returns this as operationID
	UpgradeNodeGroupOperationID string
	// UpgradeNodeGroupErr: when set, UpgradeNodeGroup returns this error
	UpgradeNodeGroupErr error
	// GetNodeGroupStatusErr: when set for nodeGroupID, GetNodeGroupStatus returns this error
	GetNodeGroupStatusErr map[string]error
	// GetControlPlaneStatusErr: when set, GetControlPlaneStatus returns this error
	GetControlPlaneStatusErr error
	// GetInstanceTypeByTemplateVersionResult: when set, GetInstanceTypeByTemplateVersion returns this
	GetInstanceTypeByTemplateVersionResult string
	// GetInstanceTypeByTemplateVersionErr: when set, GetInstanceTypeByTemplateVersion returns this error
	GetInstanceTypeByTemplateVersionErr error
	// GetNodeGroupLabelSelectorErr: when set, GetNodeGroupLabelSelector returns this error
	GetNodeGroupLabelSelectorErr error
	// UpgradeControlPlaneErr: when set, UpgradeControlPlane returns this error
	UpgradeControlPlaneErr error
	// UpgradeClusterAddonsErr: when set, UpgradeClusterAddons returns this error
	UpgradeClusterAddonsErr error
}

func (m *MockCloudProvider) UpgradeControlPlane(ctx context.Context, clusterName, targetVersion string) error {
	if m.UpgradeControlPlaneErr != nil {
		return m.UpgradeControlPlaneErr
	}
	return nil
}

func (m *MockCloudProvider) UpgradeClusterAddons(ctx context.Context, clusterName string, addons []o11yv1.AddonUpgrade) error {
	if m.UpgradeClusterAddonsErr != nil {
		return m.UpgradeClusterAddonsErr
	}
	return nil
}

func (m *MockCloudProvider) GetClusterAddonsStatus(ctx context.Context, clusterName string, addons []o11yv1.AddonUpgrade) ([]providers.AddonStatusInfo, error) {
	if m.GetClusterAddonsStatusErr != nil {
		return nil, m.GetClusterAddonsStatusErr
	}
	if len(m.ClusterAddonsStatus) > 0 {
		return m.ClusterAddonsStatus, nil
	}
	out := make([]providers.AddonStatusInfo, len(addons))
	for i, a := range addons {
		out[i] = providers.AddonStatusInfo{Name: a.Name, Version: a.Version, Status: "ACTIVE"}
	}
	return out, nil
}

func (m *MockCloudProvider) ListClusterAddons(_ context.Context, _ string) ([]providers.AddonStatusInfo, error) {
	return nil, nil
}

func (m *MockCloudProvider) GetNodeGroupsByLabels(ctx context.Context, clusterName string, labelSelector map[string]string) ([]providers.NodeGroup, error) {
	if m.GetNodeGroupsErr != nil {
		return nil, m.GetNodeGroupsErr
	}
	// Subset match (consistent with GKE/EKS/AKS/ACK): for each key in labelSelector,
	// nodeGroup.Labels[key] must equal labelSelector[key]; nodeGroup may have extra labels.
	nodeGroups := []providers.NodeGroup{}
	for _, nodeGroup := range m.NodeGroups {
		matches := true
		for key, wantVal := range labelSelector {
			if nodeGroup.Labels[key] != wantVal {
				matches = false
				break
			}
		}
		if matches {
			nodeGroups = append(nodeGroups, nodeGroup)
		}
	}
	return nodeGroups, nil
}

func (m *MockCloudProvider) GetNodeGroupByName(ctx context.Context, clusterId string, nodeGroupName string) (*providers.NodeGroup, error) {
	if m.GetNodeGroupByNameErr != nil {
		if err, ok := m.GetNodeGroupByNameErr[nodeGroupName]; ok && err != nil {
			return nil, err
		}
	}
	if m.GetNodeGroupsErr != nil {
		return nil, m.GetNodeGroupsErr
	}
	for _, nodeGroup := range m.NodeGroups {
		if nodeGroup.Name == nodeGroupName {
			return &nodeGroup, nil
		}
	}
	return nil, providers.ErrNodeGroupNotFound
}

func (m *MockCloudProvider) CreateNodeGroup(ctx context.Context, clusterName string, spec providers.NodeGroup) (string, error) {
	if m.CreateNodeGroupErr != nil {
		return "", m.CreateNodeGroupErr
	}
	m.CreatedNodeGroupSpecs = append(m.CreatedNodeGroupSpecs, spec)
	if m.CreateNodeGroupAddsToNodeGroups {
		newGroup := spec
		if newGroup.ID == "" {
			newGroup.ID = spec.Name
		}
		if newGroup.Status == "" {
			newGroup.Status = providers.NodeGroupStatusActive
		}
		m.NodeGroups = append(m.NodeGroups, newGroup)
		return spec.Name, nil
	}
	return "", nil
}

func (m *MockCloudProvider) UpdateNodeGroupMinSize(ctx context.Context, clusterName, nodeGroupID string, minSize int) error {
	if m.UpdateNodeGroupMinSizeErr != nil {
		return m.UpdateNodeGroupMinSizeErr
	}
	if m.UpdatedNodeGroupMinSizes == nil {
		m.UpdatedNodeGroupMinSizes = map[string]int{}
	}
	m.UpdatedNodeGroupMinSizes[nodeGroupID] = minSize
	for i := range m.NodeGroups {
		if m.NodeGroups[i].ID == nodeGroupID || m.NodeGroups[i].Name == nodeGroupID {
			m.NodeGroups[i].MinSize = minSize
		}
	}
	return nil
}

func (m *MockCloudProvider) UpgradeNodeGroup(ctx context.Context, clusterName, nodeGroupID, targetVersion string, inPlaceOpts *o11yv1.NodeGroupUpgradeOptions) (string, error) {
	if m.UpgradeNodeGroupErr != nil {
		return "", m.UpgradeNodeGroupErr
	}
	return m.UpgradeNodeGroupOperationID, nil
}

func (m *MockCloudProvider) GetNodeGroupUpgradeOperationStatus(_ context.Context, _, _, _ string) (bool, error) {
	if m.GetNodeGroupUpgradeOperationStatusErr != nil {
		return false, m.GetNodeGroupUpgradeOperationStatusErr
	}
	if m.GetNodeGroupUpgradeOperationStatusInProgress {
		return false, nil
	}
	return true, nil
}

func (m *MockCloudProvider) DeleteNodeGroup(ctx context.Context, clusterName, nodeGroupID string) error {
	if m.DeleteNodeGroupErr != nil {
		return m.DeleteNodeGroupErr
	}
	return nil
}

func (m *MockCloudProvider) DescribeCluster(ctx context.Context, clusterName string) (any, error) {
	return nil, nil
}

func (m *MockCloudProvider) GetNodeGroupStatus(ctx context.Context, clusterName, nodeGroupID string) (providers.NodeGroupStatus, error) {
	if m.GetNodeGroupStatusErr != nil {
		if err, ok := m.GetNodeGroupStatusErr[nodeGroupID]; ok && err != nil {
			return "", err
		}
	}
	if m.GetNodeGroupStatusOverride != nil {
		if status, ok := m.GetNodeGroupStatusOverride[nodeGroupID]; ok {
			return status, nil
		}
	}
	return providers.NodeGroupStatusActive, nil
}

func (m *MockCloudProvider) GetNodeGroupLabelSelector(ctx context.Context, clusterName, nodeGroupID string) (map[string]string, error) {
	if m.GetNodeGroupLabelSelectorErr != nil {
		return nil, m.GetNodeGroupLabelSelectorErr
	}
	return map[string]string{"node-group": nodeGroupID}, nil
}

func (m *MockCloudProvider) GetControlPlaneVersion(ctx context.Context, clusterName string) (string, error) {
	if m.GetControlPlaneVersionErr != nil {
		return "", m.GetControlPlaneVersionErr
	}
	return m.ControlPlaneVersion, nil
}

func (m *MockCloudProvider) GetControlPlaneStatus(ctx context.Context, clusterName string) (providers.ControlPlaneStatus, error) {
	if m.GetControlPlaneStatusErr != nil {
		return "", m.GetControlPlaneStatusErr
	}
	if m.ControlPlaneStatus != "" {
		return m.ControlPlaneStatus, nil
	}
	return providers.ControlPlaneStatusActive, nil
}

func (m *MockCloudProvider) GetInstanceTypeByTemplateVersion(_ context.Context, _, _, _ string) (string, error) {
	if m.GetInstanceTypeByTemplateVersionErr != nil {
		return "", m.GetInstanceTypeByTemplateVersionErr
	}
	return m.GetInstanceTypeByTemplateVersionResult, nil
}

// MockCloudProviderRegistry is a mock registry that allows us to inject a mock provider
type MockCloudProviderRegistry struct {
	Provider providers.CloudProviderInterface
	GetErr   error
}

func (m *MockCloudProviderRegistry) Get(provider o11yv1.CloudProvider) (providers.CloudProviderInterface, error) {
	if m.GetErr != nil {
		return nil, m.GetErr
	}
	return m.Provider, nil
}

func (m *MockCloudProviderRegistry) Register(provider o11yv1.CloudProvider, impl providers.CloudProviderInterface) {
	m.Provider = impl
}
