package testutils

import (
	"context"
	"io"
	"net/http"
	"net/url"
	"sync"

	"github.com/tidbcloud/aws-shared-provider/pkg/tidbworker/resourcemanager/poolmanager/factory"
	rm "github.com/tidbcloud/aws-shared-provider/pkg/tidbworker/resourcemanager/types"
	"github.com/tidbcloud/aws-shared-provider/pkg/utils/tidb"
	v1 "k8s.io/api/core/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type MockManager interface {
	rm.PoolManager
	// GetConfig returns the current cluster config.
	GetConfig() *ClusterConfig
	// UpdateCluster updates the cluster with the given config.
	UpdateCluster(ctx context.Context, cfg *ClusterConfig) error
	// GetExpectedServices constructs the expected services from the current cluster config.
	GetExpectedServices() map[rm.WorkerPool]map[rm.Worker]*v1.Service
	// GetExpectedPods constructs the expected pods from the current cluster config.
	GetExpectedPods() (map[rm.WorkerPool]map[rm.Worker]*v1.Pod, error)
}

type mockManager struct {
	mu        sync.RWMutex
	cluster   *mockCluster
	instances *mockTiDBPools
	config    *ClusterConfig
}

func NewMockManager(cfg *ClusterConfig) (MockManager, error) {
	cluster, err := newMockCluster(cfg)
	if err != nil {
		return nil, err
	}
	return &mockManager{
		cluster:   cluster,
		instances: newMockTiDBPools(cfg.Clone()),
		config:    cfg.Clone(),
	}, nil
}

func (m *mockManager) Start(_ context.Context) error {
	return nil
}

func (m *mockManager) NeedLeaderElection() bool {
	return true
}

func (m *mockManager) GetPools() (map[rm.WorkerPool]*rm.PoolConfig, error) {
	cfg := m.snapshotConfig()
	resultPools := make(map[rm.WorkerPool]*rm.PoolConfig)
	for pool, pc := range cfg.PoolsConfig.Pools {
		podSpec := factory.WorkerSpec(
			cfg.WorkerConfig.TiDBImage,
			&pc.Resources,
			cfg.WorkerConfig.Name,
			cfg.SecretName,
		)
		revision, err := factory.GenRevision(
			context.Background(),
			cfg.Namespace,
			m.cluster,
			podSpec,
			cfg.WorkerConfig.Name,
			cfg.PoolsConfig.Name,
			cfg.SecretName,
		)
		if err != nil {
			return nil, err
		}
		resultPools[pool] = &rm.PoolConfig{
			Size:          pc.Size,
			Revision:      revision,
			WorkerPodSpec: podSpec,
		}
	}
	return resultPools, nil
}

func (m *mockManager) GetStandbyWorkers(pool rm.WorkerPool) ([]rm.Worker, error) {
	return m.instances.getStandbyWorkers(pool), nil
}

func (m *mockManager) ActivateWorker(
	_ context.Context,
	_ rm.WorkerPool,
	worker rm.Worker,
	parameter tidb.ActivateRequest,
) error {
	return m.instances.activate(worker, parameter)
}

func (m *mockManager) RecycleWorker(_ context.Context, _ rm.WorkerPool, worker rm.Worker, _ string) error {
	return m.instances.recycle(worker)
}

func (m *mockManager) NewRequest(
	_ context.Context,
	method string,
	host string,
	path string,
	body io.Reader,
) (*http.Request, error) {
	return &http.Request{
		Method: method,
		URL:    &url.URL{Host: host, Path: path},
		Body:   io.NopCloser(body),
		Host:   host,
	}, nil
}

func (m *mockManager) Do(req *http.Request) (*http.Response, error) {
	return m.instances.doRequest(req)
}

func (m *mockManager) GetNamespace() string {
	return m.config.Namespace
}

func (m *mockManager) GetClusterClient() client.Client {
	return m.cluster
}

func (m *mockManager) GetConfig() *ClusterConfig {
	return m.snapshotConfig()
}

func (m *mockManager) UpdateCluster(ctx context.Context, cfg *ClusterConfig) error {
	cloned := cfg.Clone()
	// Update k8s cluster.
	if err := m.cluster.updateCluster(ctx, cloned); err != nil {
		return err
	}
	// Update TiDB instances.
	m.instances.updateCluster(cloned)
	m.mu.Lock()
	m.config = cloned
	m.mu.Unlock()
	return nil
}

func (m *mockManager) GetExpectedServices() map[rm.WorkerPool]map[rm.Worker]*v1.Service {
	cfg := m.snapshotConfig()
	resultServices := make(map[rm.WorkerPool]map[rm.Worker]*v1.Service)
	for pool, pc := range cfg.PoolsConfig.Pools {
		resultServices[pool] = make(map[rm.Worker]*v1.Service, pc.Size)
		for i := 0; i < pc.Size; i++ {
			worker := factory.WorkerHandle(pool, i)
			resultServices[pool][worker] = factory.WorkerSvc(
				worker,
				pool,
				cfg.Namespace,
			)
		}
	}
	return resultServices
}

func (m *mockManager) GetExpectedPods() (map[rm.WorkerPool]map[rm.Worker]*v1.Pod, error) {
	cfg := m.snapshotConfig()
	resultPods := make(map[rm.WorkerPool]map[rm.Worker]*v1.Pod)
	poolsConfig, err := m.GetPools()
	if err != nil {
		return nil, err
	}
	for pool, pc := range poolsConfig {
		resultPods[pool] = make(map[rm.Worker]*v1.Pod, pc.Size)
		for i := 0; i < pc.Size; i++ {
			worker := factory.WorkerHandle(pool, i)
			resultPods[pool][worker] = factory.WorkerPod(
				worker,
				pool,
				cfg.Namespace,
				pc.Revision,
				pc.WorkerPodSpec,
			)
		}
	}
	return resultPods, nil
}

func (m *mockManager) snapshotConfig() *ClusterConfig {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.config.Clone()
}
