package remotequery

import (
	"context"
	"encoding/json"
	"net/http"
	"strconv"
	"time"

	"github.com/go-logr/logr"
	"github.com/tidbcloud/aws-shared-provider/pkg/tidbworker/admin"
	"github.com/tidbcloud/aws-shared-provider/pkg/tidbworker/job"
	"github.com/tidbcloud/aws-shared-provider/pkg/tidbworker/types"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const handleQueryPath = "/scaler/api/v1/remote-query/query"

type handler struct {
	clusterCli       client.Client
	namespace        string
	logger           logr.Logger
	jobBuilder       *job.Builder
	adminConfig      *admin.ConfigReconciler
	workerCertSecret string
}

func (h *handler) NeedLeaderElection() bool {
	return true
}

func (h *handler) Start(ctx context.Context) error {
	h.logger.Info("starting handler")

	// TODO: initMetrics

	return nil
}

type Query struct {
	QueryID      string `json:"query_id"`
	TiDBPool     string `json:"tidb_pool"`
	KeyspaceName string `json:"keyspace_name"`
	QueryAddr    string `json:"query_addr"` // http://host:port/remote-query/{query-id}
}

func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h.logger.Info("received request", "path", r.URL.Path)

	switch r.URL.Path {
	case handleQueryPath:
		h.handleQuery(w, r)
	default:
		http.NotFound(w, r)
	}
}

func (h *handler) handleQuery(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close() //nolint: errcheck // don't matter
	var q Query
	if err := json.NewDecoder(r.Body).Decode(&q); err != nil {
		http.Error(w, "failed to decode request", http.StatusBadRequest)
		return
	}
	h.logger.Info("received query", "keyspace_id", q.KeyspaceName, "query_id", q.QueryID, "query_addr", q.QueryAddr)

	if h.adminConfig != nil && h.adminConfig.IsPaused("remote-query") {
		h.logger.Info("remote-query is paused")
		w.WriteHeader(http.StatusServiceUnavailable)
		return
	}

	job, err := h.jobBuilder.Build(r.Context(), job.Options{
		TiDBPool:         q.TiDBPool,
		WorkerImage:      h.adminConfig.GetWorkerImage(),
		KeyspaceName:     q.KeyspaceName,
		JobType:          job.TypeRemoteQuery,
		JobName:          "remote-query-" + strconv.FormatInt(time.Now().UnixMilli(), 10),
		ExecID:           q.QueryAddr,
		WorkerCertSecret: h.workerCertSecret,
	})
	if err != nil {
		h.logger.Error(err, "failed to build job", "keyspace_name", q.KeyspaceName, "query_id", q.QueryID)
		http.Error(w, "failed to build job", http.StatusInternalServerError)
		return
	}

	err = h.clusterCli.Create(r.Context(), job)

	if err != nil && !apierrors.IsAlreadyExists(err) {
		h.logger.Error(err, "failed to create job", "keyspace_name", q.KeyspaceName, "query_id", q.QueryID)
		http.Error(w, "failed to create job", http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusAccepted)
}

func NewHandler(namespace string, mgr ctrl.Manager, opts ...option) types.Handler {
	h := &handler{
		clusterCli: mgr.GetClient(),
		namespace:  namespace,
		logger:     mgr.GetLogger().WithName("remotequery"),
		jobBuilder: job.NewBuilder(namespace, mgr.GetClient()),
	}
	for _, opt := range opts {
		opt(h)
	}
	return h
}

type option func(*handler)

func WithAdminConfig(adminConfig *admin.ConfigReconciler) option {
	return func(h *handler) {
		h.adminConfig = adminConfig
	}
}

func WithWorkerCertSecret(workerCertSecret string) option {
	return func(h *handler) {
		h.workerCertSecret = workerCertSecret
	}
}
