package poolmanager

import (
	"context"
	"fmt"
	"io"
	"net/http"

	"github.com/pingcap/log"
	"github.com/tidbcloud/aws-shared-provider/pkg/tidbworker/resourcemanager/poolmanager/activator"
	"github.com/tidbcloud/aws-shared-provider/pkg/tidbworker/resourcemanager/poolmanager/configwatcher"
	"github.com/tidbcloud/aws-shared-provider/pkg/tidbworker/resourcemanager/poolmanager/reconciler"
	"github.com/tidbcloud/aws-shared-provider/pkg/tidbworker/resourcemanager/poolmanager/standbywatcher"
	"github.com/tidbcloud/aws-shared-provider/pkg/tidbworker/resourcemanager/server"
	rm "github.com/tidbcloud/aws-shared-provider/pkg/tidbworker/resourcemanager/types"
	"go.uber.org/zap"
	"golang.org/x/sync/errgroup"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const serverAddr = ":9292"

type manager struct {
	namespace  string
	clusterCli client.Client
	cfg        *rm.PoolManagerConfig

	scheme string
	// client is the HTTP client to send requests to the workers.
	client *http.Client
	// server is the debug server to provide some debug interfaces for manual inspections.
	server *server.Server

	rm.ConfigWatcher
	rm.Activator
	rm.StandbyWatcher
	rm.Reconciler
}

// NewManager creates a new PoolManager.
func NewManager(namespace string, clusterCli client.Client, cfg *rm.PoolManagerConfig) rm.PoolManager {
	m := &manager{
		namespace:  namespace,
		clusterCli: clusterCli,
		cfg:        cfg,
	}

	m.scheme = "http://"
	if cfg.TLSConfig != nil {
		m.scheme = "https://"
		cfg.TLSConfig = cfg.TLSConfig.Clone()
		cfg.TLSConfig.InsecureSkipVerify = true
	}
	m.client = &http.Client{
		Transport: &http.Transport{
			TLSClientConfig:   cfg.TLSConfig,
			ForceAttemptHTTP2: true,
		},
	}
	m.server = server.NewServer(m, serverAddr)
	m.ConfigWatcher = configwatcher.NewConfigWatcher(m, cfg.ConfigWatcherConfig)
	m.Activator = activator.NewActivator(m)
	m.StandbyWatcher = standbywatcher.NewStandbyWatcher(m, cfg.StandbyWatcherConfig)
	m.Reconciler = reconciler.NewReconciler(m, cfg.ReconcilerConfig)
	return m
}

func (m *manager) Start(ctx context.Context) error {
	log.Info("start pool manager",
		zap.Bool("use tls", m.cfg.TLSConfig != nil),
		zap.Any("config watcher config", m.cfg.ConfigWatcherConfig),
		zap.Any("standby watcher config", m.cfg.StandbyWatcherConfig),
		zap.Any("reconciler config", m.cfg.ReconcilerConfig))
	g, ctx := errgroup.WithContext(ctx)
	// Start watching config maps for pool configurations.
	g.Go(func() error {
		return m.ConfigWatcher.Start(ctx)
	})
	// Start watching the standby workers.
	g.Go(func() error {
		return m.StandbyWatcher.Start(ctx)
	})
	// Start reconciling the desired config and the actual pool state.
	g.Go(func() error {
		return m.Reconciler.Start(ctx)
	})
	// Start the debug server, if failed, the manager will still work.
	if err := m.server.Start(ctx); err != nil {
		log.Error("failed to start debug server", zap.Error(err))
	}
	return g.Wait()
}

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

func (m *manager) GetNamespace() string {
	return m.namespace
}

func (m *manager) GetClusterClient() client.Client {
	return m.clusterCli
}

func (m *manager) NewRequest(
	ctx context.Context,
	method string,
	host string,
	path string,
	body io.Reader,
) (*http.Request, error) {
	req, err := http.NewRequestWithContext(ctx, method, m.scheme+host+path, body)
	if err != nil {
		return nil, fmt.Errorf("create http request error: %w", err)
	}
	return req, nil
}

func (m *manager) Do(req *http.Request) (*http.Response, error) {
	return m.client.Do(req)
}
