package config

import (
	"crypto/rsa"
	"crypto/tls"
	"fmt"
	"os"
	"strings"
	"sync"
	"time"

	"gitee.com/xuyiping_admin/pkg/di"
)

var (
	Module   = di.Provide(Options)
	options  *AppConfig
	appEnv   string
	initOnce sync.Once
)

// AppConfig store all configuration options
type AppConfig struct {
	FarmName       string `yaml:"farm_name"`
	AppName        string `yaml:"app_name"`
	AppEnv         string `yaml:"app_environment"`
	Debug          bool   `yaml:"debug"`
	HTTPServerAddr string `yaml:"http_server_addr"`
	NeckRingLimit  int32  `yaml:"neck_ring_limit"`

	// 数据库配置 额外加载文件部分 database.yaml
	StoreSetting StoreSetting `json:"storeSetting" yaml:"store"`
	// redis 配置
	RedisSetting RedisSetting `json:"RedisSetting" yaml:"redis_setting"`
	JwtSecret    string       `json:"jwtSecret" yaml:"jwt_secret"`

	ExcelSetting ExcelSetting `json:"excelSetting" yaml:"excel_setting"`

	WechatSetting     WechatSetting     `json:"wechatSetting" yaml:"wechat_setting"`
	JwtTokenKeyConfig JwtTokenKeyConfig `json:"jwtTokenKeyConfig"`
	JwtExpireTime     int               `json:"jwtExpireTime" yaml:"jwt_expire_time"`

	// asynq 相关配置
	SideWorkSetting SideWorkSetting `yaml:"side_work_setting"`
	CronSetting     CronSetting     `json:"cron_setting" yaml:"cron"`
	Mqtt            MqttSetting     `json:"mqtt"`
}

type CronSetting struct {
	// 是否启动任务时先跑一次
	CrontabStartRun bool `yaml:"crontab_start_run"`
	// CRONTAB 表达式
	UpdateCowInfo      string `yaml:"update_cow_info"`      //  更新牛只信息
	GenerateWorkOrder  string `yaml:"generate_work_order"`  //  生成工作单
	ImmunizationPlan   string `yaml:"immunization_plan"`    //  免疫计划
	SameTimePlan       string `yaml:"same_time_plan"`       //  同期
	UpdateSameTime     string `yaml:"update_same_time"`     //  更新同期
	SystemBasicCrontab string `yaml:"system_basic_crontab"` //  系统基础定时任务
	CowPregnant        string `yaml:"cow_pregnant"`         //  月度牛只怀孕清单
	NeckRing           string `yaml:"neck_ring"`            //  脖环数据更新
}

type JwtTokenKeyConfig struct {
	PrivateKey *rsa.PrivateKey `json:"privateKey"`
	PublicKey  *rsa.PublicKey  `json:"publicKey"`
}

type WechatSetting struct {
	Appid  string `yaml:"appid"`
	Secret string `yaml:"secret"`
}

type ExcelSetting struct {
	SheetName string  `yaml:"sheet_name"` // = "Sheet1" //默认Sheet名称
	Height    float64 `yaml:"height"`     // = 25.0     //默认行高度
}

// StoreSetting 数据库配置
type StoreSetting struct {
	// 开启 SyDb SQL 记录
	DriverName string `yaml:"driver_name" json:"driver_name"`
	ShowSQL    bool   `yaml:"show_sql" json:"show_sql"`
	KptRW      string `yaml:"kpt_rw" json:"kpt_rw"`
	KptMigr    string `yaml:"kpt_migr" json:"kpt_migr"`
}

type RedisSetting struct {
	// sso 配置
	CacheRedis CacheRedisDB `json:"cache_redis" yaml:"cache_redis"`
}

type CacheRedisDB struct {
	Addr        string `json:"addr" yaml:"addr"`
	DB          int    `json:"db" yaml:"db"`
	Requirepass string `json:"requirepass" yaml:"requirepass"`
	Expiry      int    `json:"expiry" yaml:"expiry"`
}

type SideWorkSetting struct {
	// Asynq 配置
	AsynqSetting AsynqSetting `json:"asynq_setting,omitempty" yaml:"asynq_setting"`
}

type AsynqSetting struct {
	Redis       AsynqRedisSetting `json:"redis" yaml:"redis"`
	Queues      map[string]int    `json:"queues,omitempty" yaml:"queues"`
	Concurrency int               `json:"concurrency,omitempty" yaml:"concurrency"`
	LogLevel    int32             `json:"log_level,omitempty" yaml:"log_level"`
}

type AsynqRedisSetting struct {
	// Network type to use, either tcp or unix.
	// Default is tcp.
	Network string `json:"network,omitempty" yaml:"network"`

	// Redis server address in "host:port" format.
	Addr string `json:"addr,omitempty" yaml:"addr"`

	// Username to authenticate the current connection when Redis ACLs are used.
	// See: https://redis.io/commands/auth.
	Username string `json:"username,omitempty" yaml:"username"`

	// Password to authenticate the current connection.
	// See: https://redis.io/commands/auth.
	Password string `json:"password,omitempty" yaml:"password"`

	// Redis DB to select after connecting to a server.
	// See: https://redis.io/commands/select.
	DB int `json:"db,omitempty" yaml:"db"`

	// Dial timeout for establishing new connections.
	// Default is 5 seconds.
	DialTimeout time.Duration `json:"dialTimeout,omitempty" yaml:"dial_timeout"`

	// Timeout for socket reads.
	// If timeout is reached, read commands will fail with a timeout error
	// instead of blocking.
	//
	// Use value -1 for no timeout and 0 for default.
	// Default is 3 seconds.
	ReadTimeout time.Duration `json:"readTimeout,omitempty" yaml:"read_timeout"`

	// Timeout for socket writes.
	// If timeout is reached, write commands will fail with a timeout error
	// instead of blocking.
	//
	// Use value -1 for no timeout and 0 for default.
	// Default is ReadTimout.
	WriteTimeout time.Duration `json:"writeTimeout,omitempty" yaml:"write_timeout"`

	// Maximum number of socket connections.
	// Default is 10 connections per every CPU as reported by runtime.NumCPU.
	PoolSize int `json:"poolSize,omitempty" yaml:"pool_size"`

	// TLS Config used to connect to a server.
	// TLS will be negotiated only if this field is set.
	TLSConfig *tls.Config `json:"tlsConfig,omitempty" yaml:"tls_config"`
}

type MqttSetting struct {
	Broker            string `json:"broker" yaml:"broker"`
	Port              int    `json:"port" yaml:"port"`
	UserName          string `json:"username" yaml:"username"`
	Password          string `json:"password" yaml:"password"`
	ClientId          string `json:"client" yaml:"client_id"`
	Topic             string `json:"topic"  yaml:"topic"`
	Retain            bool   `json:"retain" yaml:"retain"`
	Qos               int    `json:"qos" yaml:"qos"`
	KeepAlive         int    `json:"keepAlive" yaml:"keep_alive"`
	ConnectTimeout    int    `json:"connectTimeout" yaml:"connect_timeout"`
	AutoReconnect     bool   `json:"autoReconnect" yaml:"auto_reconnect"`
	ReconnectInterval int    `json:"reconnectInterval" yaml:"reconnect_interval"`
	WorkNumber        int    `json:"workNumber" yaml:"work_number"`
}

func (a *AppConfig) Name() string {
	return fmt.Sprintf("%s-%s", a.AppName, a.AppEnv)
}

// CacheNameSpace 作为 Key 的前缀,用来区分不同环境,不同 APP 下的 Key,防止缓存干扰
// 踩坑记录:
//
//	如果使用 fmt.Sprintf("%s-%s-%s", a.AppName, a.AppRole, a.AppEnv),例如 sayam-http-production, sayam-consumer-production
//	会导致 从 role http 写入的 key,从 role consumer 中取不出来,反之亦然
//	支持更多的key空间, 可以更灵活的定义ns
func (a *AppConfig) CacheNameSpace() string {
	cacheKeySpace := ""
	if a.FarmName != "" {
		cacheKeySpace = fmt.Sprintf("%s-%s-%s", a.AppName, a.AppEnv, a.FarmName)
	} else {
		cacheKeySpace = fmt.Sprintf("%s-%s", a.AppName, a.AppEnv)
	}
	return cacheKeySpace
}
func Options() *AppConfig {
	return options
}

func init() {
	appEnv = strings.ToLower(os.Getenv("APP_ENVIRONMENT"))
	cfg := &AppConfig{}
	var err error
	initOnce.Do(func() {
		switch appEnv {
		case "test":
			err = Initialize("app.test.yaml", cfg)
		case "development":
			err = Initialize("app.develop.yaml", cfg)
		case "production":
			err = Initialize("app.production.yaml", cfg)
		default:
			panic("err confing")
		}
		if err != nil {
			panic(err)
		}
		cfg.JwtTokenKeyConfig = openPrivateKey()
		options = cfg
	})
}