package qps_report

import (
	"context"
	"io"
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/pingcap-inc/clinic/pkg/config"
	"github.com/pingcap-inc/clinic/pkg/db"
	httpwrapper "github.com/pingcap-inc/clinic/pkg/http_wrapper"

	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/auth/session"
	_const "github.com/pingcap-inc/clinic/pkg/const"
	"github.com/pingcap-inc/clinic/pkg/datasource"
)

const (
	DefaultRequestLimit = 100
	DefaultConcurrency  = 50
	DefaultDateRange    = 1
)

var DefaultQpsThreshold = []int{1, 100, 10000}

type QpsReportManager struct {
	// CronPlan              string
	ComputeEngineEndPoint string
	RequestLimit          int
	Concurrency           int
	DateRange             int
	QpsThreshold          []int
}

type QpsReportRequest struct {
	Args QpsReportArgs     `json:"args"`
	Tags map[string]string `json:"tags"`
}

type QpsReportArgs struct {
	Date      string        `json:"date"`
	Threshold []int         `json:"threshold"`
	Clusters  []ClusterInfo `json:"clusters"`
}

type ClusterInfo struct {
	ClusterID string `json:"cluster_id"`
	OrgID     string `json:"org_id"`
}

func NewQpsReprotManager(cfg config.ReportConfig, computeEngineEndPoint string) *QpsReportManager {
	m := &QpsReportManager{
		// CronPlan:              DefaultCronPlan,
		RequestLimit:          DefaultRequestLimit,
		QpsThreshold:          DefaultQpsThreshold,
		DateRange:             DefaultDateRange,
		ComputeEngineEndPoint: computeEngineEndPoint,
	}

	if cfg.QpsReportConcurrency > 0 {
		m.Concurrency = cfg.QpsReportConcurrency
	}

	if len(cfg.QpsReportThreshold) > 0 {
		m.QpsThreshold = cfg.QpsReportThreshold
	}

	if cfg.QpsReportRequestLimit > 0 {
		m.RequestLimit = cfg.QpsReportRequestLimit
	}

	if cfg.QpsReportDateRange > 0 {
		m.DateRange = cfg.QpsReportDateRange
	}
	return m
}

// Run: execution qpsreport trigger
func (m *QpsReportManager) Run() error {
	wg := sync.WaitGroup{}
	workerPool := make(chan struct{}, m.Concurrency)

	ctx := context.Background()
	// cluster count
	c, err := datasource.CountCloudClusters(&session.CtxUser{
		Internal: true,
		Email:    "clinic@pingcap.com",
	})
	log.Info("execution qps report trigger", zap.Int64("cluster count", c))
	if c == 0 || err != nil {
		return nil
	}
	pageNum := int(c) / m.RequestLimit
	if int(c)%m.RequestLimit != 0 {
		pageNum++
	}

	for i := 1; i <= pageNum; i++ {
		wg.Add(1)
		workerPool <- struct{}{}
		taskId := i
		go func() {
			defer func() {
				wg.Done()
				<-workerPool
			}()
			for d := 1; d <= m.DateRange; d++ {
				err := m.execution(ctx, taskId, d)
				if err != nil {
					log.Error("execution qps report trigger failed",
						zap.Error(err), zap.Int("task id", taskId),
						zap.Int("task count", pageNum), zap.String("date",
							time.Now().Add(-24*time.Duration(d)*time.Hour).Format("2006-01-02")))
				}
			}
		}()
	}
	wg.Wait()
	log.Info("execution qps report trigger success")
	return nil
}

func (m *QpsReportManager) execution(ctx context.Context, page, date int) error {
	clusters, err := datasource.ListCloudClusters(&session.CtxUser{
		Internal: true,
		Email:    "clinic@pingcap.com",
	}, page, m.RequestLimit)
	if err != nil {
		return errors.Annotate(err, "failed to list clusters form core svc")
	}

	httpClient, err := httpwrapper.New(httpwrapper.TimeoutOption(15 * time.Second))
	if err != nil {
		return errors.Annotate(err, "failed to create http client")
	}
	reqBody := m.generateRequest(clusters, date)

	if len(reqBody.Args.Clusters) == 0 {
		return nil
	}
	resp, err := httpClient.DoRequestWithJSON(ctx, http.MethodPost,
		m.ComputeEngineEndPoint+_const.ReportActivePath, reqBody)
	if err != nil {
		return errors.Annotate(err, "failed to do request")
	}
	defer resp.Body.Close()
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK {
		return errors.Errorf("failed to do request, status=%d ,msg=%s",
			resp.StatusCode, string(data))
	}

	return nil
}

func (m *QpsReportManager) generateRequest(clusters []db.Cluster, date int) QpsReportRequest {
	req := QpsReportRequest{
		Args: QpsReportArgs{
			Date:      time.Now().Add(-24 * time.Duration(date) * time.Hour).Format("2006-01-02"),
			Threshold: m.QpsThreshold,
			Clusters:  []ClusterInfo{},
		},
		Tags: map[string]string{
			"source": "clinic-cron",
		},
	}

	for _, c := range clusters {
		deleteTime := c.DeletedAt.Time.Unix()
		// There are cases where there was data yesterday, but it has been deleted
		if time.Unix(deleteTime, 0).Before(time.Now().Add(-24 * time.Duration(date+1) * time.Hour)) {
			continue
		}

		req.Args.Clusters = append(req.Args.Clusters, ClusterInfo{
			ClusterID: c.ClusterID,
			OrgID:     strconv.FormatUint(uint64(c.OrgID), 10),
		})
	}

	return req
}
