package probe

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

	"github.com/go-logr/logr"
	"github.com/tidbcloud/aws-shared-provider/pkg/gateway/client"
	ctrl "sigs.k8s.io/controller-runtime"
)

const (
	checkConcurrency     = 10
	checkChannelCapacity = 20
)

type CheckBackend struct {
	Token      string
	Backend    string
	TidbPoolNs string
}

func (c *CheckBackend) String() string {
	return fmt.Sprintf("%+v", *c)
}

type CheckBackendServer struct {
	logger logr.Logger

	ctx        context.Context
	checkChan  chan CheckBackend
	httpClient *client.ManagerServerClient
	wg         sync.WaitGroup

	mu          sync.Mutex
	checkingMap map[CheckBackend]struct{}
}

func NewCheckBackendServer(ctx context.Context, httpClient *client.ManagerServerClient) *CheckBackendServer {
	return &CheckBackendServer{
		logger: ctrl.Log.WithName("checkBackendServer"),

		ctx:        ctx,
		checkChan:  make(chan CheckBackend, checkChannelCapacity),
		httpClient: httpClient,

		checkingMap: map[CheckBackend]struct{}{},
	}
}

func (s *CheckBackendServer) Start() {
	for i := 0; i < checkConcurrency; i++ {
		s.wg.Add(1)
		go s.check()
	}
	s.logger.Info("check backend server started",
		"checkConcurrency", checkConcurrency, "checkChannelCapacity", checkChannelCapacity)
}

func (s *CheckBackendServer) Stop() {
	s.wg.Wait()
	s.logger.Info("check backend server stopped")
}

func (s *CheckBackendServer) InsertTask(checkTask CheckBackend) {
	var taskChecking bool

	s.mu.Lock()
	if _, ok := s.checkingMap[checkTask]; ok {
		taskChecking = true
	} else {
		s.checkingMap[checkTask] = struct{}{}
	}
	s.mu.Unlock()

	if taskChecking {
		s.logger.V(1).Info("the task is checking, skip", "checkTask", &checkTask)
		return
	}

	select {
	case s.checkChan <- checkTask:
	default:
		s.logger.Info("check backend queue is full, drop checkTask", "checkTask", &checkTask)
	}
}

func (s *CheckBackendServer) check() {
	defer s.wg.Done()

	for {
		select {
		case <-s.ctx.Done():
			return
		case check := <-s.checkChan:
			s.logger.Info("check backend", "check", &check)
			url := fmt.Sprintf("https://manager-server:8000/api/tidb/check?token=%s&ip=%s&ns=%s",
				check.Token, check.Backend, check.TidbPoolNs)
			req, err := http.NewRequestWithContext(s.ctx, http.MethodGet, url, nil)
			if err != nil {
				s.logger.Error(err, "NewRequestWithContext failed", "check", &check)
				continue
			}
			resp, err := s.httpClient.Do(req)
			if err != nil {
				s.logger.Error(err, "check request failed", "check", &check)
				continue
			}
			if resp.StatusCode != http.StatusOK {
				s.logger.Info("check backend failed", "check", &check, "resp status", resp.Status)
			}
			resp.Body.Close() //nolint:errcheck // no need check error

			s.mu.Lock()
			delete(s.checkingMap, check)
			s.mu.Unlock()
		}
	}
}
