package main

import (
	"context"
	"log/slog"
	"net/http"
	"os"

	"xugu_exporter/collector"

	_ "gitee.com/XuguDB/go-xugu-driver"
	kingpin "github.com/alecthomas/kingpin/v2"
	"github.com/go-kit/log/level"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/collectors"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/prometheus/common/promlog"
	"github.com/prometheus/common/promlog/flag"
	"github.com/prometheus/common/version"
	"github.com/prometheus/exporter-toolkit/web"
	webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag"
	// Required for debugging
	// _ "net/http/pprof"
)

var (
	// Version will be set at build time.
	Version = "0.0.1.dev" // 版本号,通常在构建时设置。这里初始化为一个开发版本号。

	// 使用 Kingpin 库定义命令行标志,并为每个标志提供帮助信息和默认值。

	// metricPath 定义了暴露指标的路径。默认值来自环境变量 "TELEMETRY_PATH",若未设置则默认为 "/metrics"。
	metricPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics. (env: TELEMETRY_PATH)").Default(getEnv("TELEMETRY_PATH", "/metrics")).String()

	// dsn 定义了数据库的连接字符串。默认值来自环境变量 "DATA_SOURCE_NAME",若未设置则默认为空字符串。
	dsn = kingpin.Flag("database.dsn",
		"Connection string to a data source. (env: DATA_SOURCE_NAME)",
	).Default(getEnv("DATA_SOURCE_NAME", "")).String()

	// dsnFile 定义了从文件读取数据库连接字符串的路径。默认值来自环境变量 "DATA_SOURCE_NAME_FILE",若未设置则默认为空字符串。
	dsnFile = kingpin.Flag("database.dsnFile",
		"File to read a string to a data source from. (env: DATA_SOURCE_NAME_FILE)",
	).Default(getEnv("DATA_SOURCE_NAME_FILE", "")).String()

	// defaultFileMetrics 定义了存储默认指标的文件路径。该文件支持 TOML 或 YAML 格式。默认值来自环境变量 "DEFAULT_METRICS",若未设置则默认为 "default-metrics.toml"。
	defaultFileMetrics = kingpin.Flag(
		"default.metrics",
		"File with default metrics in a toml  format. (env: DEFAULT_METRICS)",
	).Default(getEnv("DEFAULT_METRICS", "default-metrics.toml")).String()

	// customMetrics 定义了包含自定义指标的文件路径。该文件支持 TOML 或 YAML 格式。默认值来自环境变量 "CUSTOM_METRICS",若未设置则默认为空字符串。
	customMetrics = kingpin.Flag(
		"custom.metrics",
		"File that may contain various custom metrics in a toml format. (env: CUSTOM_METRICS)",
	).Default(getEnv("CUSTOM_METRICS", "")).String()

	// queryTimeout 定义了查询的超时时间,单位为秒。默认值来自环境变量 "QUERY_TIMEOUT",若未设置则默认为 5 秒。
	queryTimeout = kingpin.Flag(
		"query.timeout",
		"Query timeout (in seconds). (env: QUERY_TIMEOUT)",
	).Default(getEnv("QUERY_TIMEOUT", "5")).Int()

	// maxIdleConns 定义了连接池中最大空闲连接数。默认值来自环境变量 "DATABASE_MAXIDLECONNS",若未设置则默认为 0。
	maxIdleConns = kingpin.Flag(
		"database.maxIdleConns",
		"Number of maximum idle connections in the connection pool. (env: DATABASE_MAXIDLECONNS)",
	).Default(getEnv("DATABASE_MAXIDLECONNS", "0")).Int()

	// maxOpenConns 定义了连接池中最大打开连接数。默认值来自环境变量 "DATABASE_MAXOPENCONNS",若未设置则默认为 10。
	maxOpenConns = kingpin.Flag(
		"database.maxOpenConns",
		"Number of maximum open connections in the connection pool. (env: DATABASE_MAXOPENCONNS)",
	).Default(getEnv("DATABASE_MAXOPENCONNS", "10")).Int()

	// scrapeInterval 定义了每次抓取数据的间隔时间。默认值为 0,表示在每次收集请求时进行抓取。
	scrapeInterval = kingpin.Flag(
		"scrape.interval",
		"Interval between each scrape. Default is to scrape on collect requests",
	).Default("0s").Duration()

	// toolkitFlags 是与 Web 相关的附加标志,添加到 `kingpin.CommandLine` 上,默认端口为 ":9161"。
	toolkitFlags = webflag.AddFlags(kingpin.CommandLine, ":9161")
)

func main() {
	// 创建 Prometheus 日志配置对象
	promLogConfig := &promlog.Config{}

	// 添加命令行标志以配置 Prometheus 日志
	flag.AddFlags(kingpin.CommandLine, promLogConfig)
	// 设置帮助标志的快捷键
	kingpin.HelpFlag.Short('\n')
	// 设置程序版本信息
	kingpin.Version(version.Print("xugudb_exporter"))
	// 解析命令行输入的参数
	kingpin.Parse()
	// 创建新的日志记录器
	logger := promlog.New(promLogConfig)

	// 如果提供了 dsnFile 参数,读取文件中的数据库连接信息
	if dsnFile != nil && *dsnFile != "" {
		dsnFileContent, err := os.ReadFile(*dsnFile)
		if err != nil {
			// 如果文件读取失败,记录错误并退出程序
			level.Error(logger).Log("msg", "Unable to read DATA_SOURCE_NAME_FILE", "file", dsnFile, "error", err)
			os.Exit(1)
		}
		// 将文件内容作为数据库连接字符串
		*dsn = string(dsnFileContent)
	}

	// 初始化配置对象,将从命令行解析的参数赋值给配置
	config := &collector.Config{
		DSN:                *dsn,                // 数据库连接字符串
		MaxOpenConns:       *maxOpenConns,       // 最大打开连接数
		MaxIdleConns:       *maxIdleConns,       // 最大空闲连接数
		CustomMetrics:      *customMetrics,      // 自定义指标文件路径
		QueryTimeout:       *queryTimeout,       // 查询超时时间
		DefaultMetricsFile: *defaultFileMetrics, // 默认指标文件路径
	}

	// 创建一个新的 Exporter 实例,用于收集 xugu 数据库的指标
	exporter, err := collector.NewExporter(logger, config)
	if err != nil {
		// 如果创建 Exporter 失败,记录错误并退出程序
		level.Error(logger).Log("unable to connect to DB", err)
	}

	// 如果设置了抓取间隔(scrapeInterval),则启动定时抓取指标的任务
	if *scrapeInterval != 0 {
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()
		// 启动一个 goroutine,定期从数据库抓取指标
		go exporter.RunScheduledScrapes(ctx, *scrapeInterval)
	}

	// 将 Exporter 注册到 Prometheus 中,供 Prometheus 进行指标抓取
	prometheus.MustRegister(exporter)
	// 注册构建信息收集器,用于报告程序的版本信息等元数据
	prometheus.MustRegister(collectors.NewBuildInfoCollector())

	// 记录启动信息
	level.Info(logger).Log("msg", "Starting xugudb_exporter", "version", version.Info())
	level.Info(logger).Log("msg", "Build context", "build", version.BuildContext())
	level.Info(logger).Log("msg", "Collect from: ", "metricPath", *metricPath)

	// 设置 Prometheus HTTP handler 的选项
	opts := promhttp.HandlerOpts{
		ErrorHandling: promhttp.ContinueOnError, // 遇到错误时继续处理其他请求
	}
	// 配置 /metrics 路径用于暴露 Prometheus 格式的指标
	http.Handle(*metricPath, promhttp.HandlerFor(prometheus.DefaultGatherer, opts))

	// 配置根路径(/),返回一个简单的 HTML 页面,包含版本信息和指标链接
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("<html><head><title>xugu DB Exporter " + Version + "</title></head><body><h1>xugu DB Exporter " + Version + "</h1><p><a href='" + *metricPath + "'>Metrics</a></p></body></html>"))
	})

	// 创建一个日志处理器,输出到标准输出(os.Stdout)
	handlerS := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelInfo, // 设置最低日志级别为 Info
	})

	// 创建一个新的 logger
	loggerS := slog.New(handlerS)
	// 创建一个新的 logger
	//loggerS := slog.New(slog.NewTextHandler(slog.Default()))

	// 使用 logger 记录日志
	loggerS.Info("This is an info message")
	loggerS.Warn("This is a warning message")
	loggerS.Error("This is an error message")

	// 如果需要,可以添加更多的上下文信息
	loggerS.With("context", "example").Info("This is a context-aware info message")
	// 创建一个 HTTP 服务器实例
	server := &http.Server{}
	// 启动 HTTP 服务器,监听并提供 Prometheus 指标
	if err := web.ListenAndServe(server, toolkitFlags, loggerS); err != nil {
		// 如果服务器启动失败,记录错误并退出程序
		level.Error(logger).Log("msg", "Listening error", "reason", err)
		os.Exit(1)
	}
}

// getEnv 返回指定环境变量的值,如果环境变量未设置,则返回提供的备用值。
func getEnv(key, fallback string) string {
	// os.LookupEnv 用于查找环境变量的值。如果环境变量存在,返回值和一个布尔值(true/false);
	// 如果环境变量不存在,返回空字符串和 false。
	if value, ok := os.LookupEnv(key); ok {
		// 如果环境变量存在,返回其值
		return value
	}
	// 如果环境变量不存在,返回备用值
	return fallback
}