package controller

import (
	"context"
	"crypto/tls"
	"dataservices/commons/config"
	"dataservices/commons/utils"
	service "dataservices/internal/service/dataapi_gateway"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/go-redis/redis/v8"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

const (
	ConstMetricsNs     = "dataservice"
	ConstMetricsSubSys = "custommetrics"
)

var (
	// custom collector
	reg              = prometheus.NewRegistry()
	latencyHistogram = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Namespace: ConstMetricsNs,
			Subsystem: ConstMetricsSubSys,
			Name:      "latency_ms",
			Buckets:   []float64{10, 25, 50, 75, 100, 200, 300, 400, 600, 800, 1000, 2000, 5000, 7500, 10000, 20000, 30000, 60000},
		}, []string{"dataapp_external_id", "endpoint_id"})
	errorCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: ConstMetricsNs,
			Subsystem: ConstMetricsSubSys,
			Name:      "error_counter",
		}, []string{"dataapp_external_id", "endpoint_id"})
	unauthCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: ConstMetricsNs,
			Subsystem: ConstMetricsSubSys,
			Name:      "unauth_req_counter",
		}, []string{"dataapp_external_id"})
)

func init() {
	// register metrics to my collector
	reg.MustRegister(latencyHistogram, errorCounter, unauthCounter)
}

func GetCustomMetricsPromHandler() http.Handler {
	return promhttp.HandlerFor(reg, promhttp.HandlerOpts{})
}

func StartUnauthMetricsLoop() error {
	cli, err := initUnauthRedis()
	if err != nil {
		return err
	}
	utils.GOWithName("UnauthMetricsLoop", func() {
		interval := time.Second * time.Duration(config.Global().DataAppMetricsInstrConf.UnauthMetricsFetchInterval)
		for range time.Tick(interval) {
			utils.GO(func() {
				fetchUnauthMetrics(cli)
			})
		}
	})
	return nil
}

var (
	endpointParseRe = regexp.MustCompile(`/api/v1beta/app/([0-9A-Za-z_-]+)/endpoint`)
)

func handleCustomDomainUnauth(host string, path string, cnt float64) {
	domains, err := service.ListCustomDomains(context.Background(), host)
	if err != nil {
		return
	}

	for _, domain := range domains {
		if !strings.HasPrefix(domain.DefaultDomainName, config.Global().AwsConf.Region) {
			continue
		}
		if !strings.HasPrefix(path, domain.CustomPath) {
			continue
		}
		if domain.Expired {
			continue
		}
		unauthCounter.WithLabelValues(domain.ExternalAppID).Add(cnt)
		break
	}

}

func fetchUnauthMetrics(rds *redis.Client) {
	pipe := rds.TxPipeline()
	to := time.Second * time.Duration(config.Global().DataAppMetricsInstrConf.UnauthMetricsFetchInterval)
	ctx, cancel := context.WithTimeout(context.Background(), to)
	defer cancel()
	cmd := pipe.HGetAll(ctx, config.Global().DataAppMetricsInstrConf.UnauthMetricsFetchKey)
	pipe.Del(ctx, config.Global().DataAppMetricsInstrConf.UnauthMetricsFetchKey)
	_, err := pipe.Exec(ctx)
	if err != nil {
		tilog.Error(ctx, "fetchUnauthMetrics failed", zap.Error(err))
		return
	}
	records, err := cmd.Result()
	if err != nil {
		tilog.Error(ctx, "fetchUnauthMetrics fetch result failed", zap.Error(err))
		return
	}
	for record, cntStr := range records {
		tilog.Info(ctx, "got unauth", zap.String("record", record), zap.String("cnt", cntStr))
		cnt, err := strconv.ParseFloat(cntStr, 64)
		if err != nil {
			continue
		}
		seg := strings.SplitN(record, "/", 2)
		if len(seg) != 2 {
			continue
		}
		host := seg[0]
		path := seg[1]
		if !strings.HasSuffix(host, ".tidbcloud.com") {
			tilog.Info(ctx, "got unauth, handleCustomDomainUnauth", zap.String("host", host), zap.String("path", path))
			handleCustomDomainUnauth(host, path, cnt)
			continue
		}
		tilog.Info(ctx, "got unauth, normal", zap.String("host", host), zap.String("path", path))
		m := endpointParseRe.FindStringSubmatch(path)
		if len(m) != 2 {
			continue
		}
		unauthCounter.WithLabelValues(m[1]).Add(cnt)
	}
}

func initUnauthRedis() (*redis.Client, error) {
	redisConf := config.Global().DataAppMetricsInstrConf.UnauthReportRds
	//fixme: for debug
	if len(redisConf.MasterAddr) == 0 {
		redisConf = config.Global().RedisConf
		redisConf.DB = 0
	}

	option := &redis.Options{
		Password: redisConf.Password,
		DB:       redisConf.DB,
	}
	if redisConf.Tls {
		option.TLSConfig = &tls.Config{
			InsecureSkipVerify: true,
		}
	}

	masterOption := option
	masterOption.Addr = redisConf.MasterAddr
	client := redis.NewClient(masterOption)
	ctx := context.Background()
	if err := client.Ping(ctx).Err(); err != nil {
		return nil, err
	}
	return client, nil
}
