package main

import (
	"database/sql"
	"encoding/json"
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

	"redisdog/model"
	_ "github.com/mattn/go-sqlite3"
)

type Config struct {
	Host         string
	Port         string
	Database     string
	RootPwd      string
	LogDir       string
	TmplDir      string
	ResourcesDir string
	SessionTTL   int64
}

var (
	Cfg         *Config
	Db          *sql.DB
	SessPoll    *SessionPoll
	TmplFuncMap template.FuncMap
	SysCfg      *SysCfgCache
	Srv         *http.Server
	Sender      *MailSender
	Monitor     *RedisMonitor
	SysLogger   *model.SysLog
)

func changeWordDir() {
	if binDir, err := filepath.Abs(filepath.Dir(os.Args[0])); err != nil {
		log.Fatalf("%s\n", err.Error())
	} else {
		rootDir := filepath.Dir(binDir)
		os.Chdir(rootDir)
	}
}

func loadConfig() *Config {
	var (
		cfgfile string
		cfg     *Config = &Config{}
	)
	flag.StringVar(&cfgfile, "c", "", "config file")
	flag.Parse()
	if cfgfile == "" {
		log.Fatalf("Usage: %s -c <config file>\n", os.Args[0])
	}

	wd, err := os.Getwd()
	if err != nil {
		log.Fatalf("Get work directory failed: %s\n", err.Error())
	}
	file, err := os.OpenFile(cfgfile, os.O_RDONLY, 0644)
	if err != nil {
		log.Fatal(err)
	}
	decoder := json.NewDecoder(file)
	if err = decoder.Decode(cfg); err != nil {
		log.Fatal(err)
	}
	if cfg.Port == "" {
		cfg.Port = "8080"
	}
	if cfg.Database == "" {
		cfg.Database = filepath.Join(wd, "db", "db.sqlite")
	}
	if cfg.RootPwd == "" {
		cfg.RootPwd = "88888888"
	}
	if cfg.LogDir == "" {
		cfg.LogDir = filepath.Join(wd, "log")
	}
	if cfg.TmplDir == "" {
		cfg.TmplDir = filepath.Join(wd, "tmpl")
	}
	if cfg.ResourcesDir == "" {
		cfg.ResourcesDir = filepath.Join(wd, "resources")
	}
	if cfg.SessionTTL == 0 {
		cfg.SessionTTL = 900
	}
	return cfg
}

func waitSignal() {
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGUSR1)
	signal.Notify(ch, syscall.SIGTERM)
	for {
		sig := <-ch
		switch sig {
		//重启
		case syscall.SIGUSR1:
			close(ch)
			if Srv != nil {
				Srv.Close()
			}
			SYSLOG("WARN", "服务因重启而停止")
			syscall.Exec(os.Args[0], os.Args[1:], nil)
		//退出
		case syscall.SIGTERM:
			close(ch)
			if Srv != nil {
				Srv.Close()
			}
			SYSLOG("WARN", "服务停止")
			os.Exit(0)
		}
	}
}

func openDatabase(dbfile string) *sql.DB {
	db, err := sql.Open("sqlite3", dbfile)
	if err != nil {
		log.Fatalf("sql.Open() Error: %s\n", err.Error())
	}

	_, err = db.Exec(Sql_Table_Syscfg)
	if err != nil {
		log.Fatalf("sql.Exec() Error: %s\n", err.Error())
	}
	_, err = db.Exec(Sql_Table_Accounts)
	if err != nil {
		log.Fatalf("sql.Exec() Error: %s\n", err.Error())
	}
	_, err = db.Exec(Sql_Table_Rediscfg)
	if err != nil {
		log.Fatalf("sql.Exec() Error: %s\n", err.Error())
	}
	_, err = db.Exec(Sql_Table_Syslog)
	if err != nil {
		log.Fatalf("sql.Exec() Error: %s\n", err.Error())
	}
	_, err = db.Exec(Sql_Index_Syslog)
	if err != nil {
		log.Fatalf("sql.Exec() Error: %s\n", err.Error())
	}
	_, err = db.Exec(Sql_Table_Monitorlog)
	if err != nil {
		log.Fatalf("sql.Exec() Error: %s\n", err.Error())
	}
	_, err = db.Exec(Sql_Table_Warnlog)
	if err != nil {
		log.Fatalf("sql.Exec() Error: %s\n", err.Error())
	}
	_, err = db.Exec(Sql_Index_Warnlog1)
	if err != nil {
		log.Fatalf("sql.Exec() Error: %s\n", err.Error())
	}
	_, err = db.Exec(Sql_Index_Warnlog2)
	if err != nil {
		log.Fatalf("sql.Exec() Error: %s\n", err.Error())
	}
	_, err = db.Exec(Sql_Table_Processlog)
	if err != nil {
		log.Fatalf("sql.Exec() Error: %s\n", err.Error())
	}
	_, err = db.Exec("INSERT OR IGNORE INTO accounts(account,name,password,is_super) VALUES(?,?,?,?)", "root", "超管", model.PasswordConvert(Cfg.RootPwd), 1)
	if err != nil {
		log.Fatalf("sql.Exec() Error: %s\n", err.Error())
	}
	_, err = db.Exec(Sql_Values_Syscfg)
	if err != nil {
		log.Fatalf("sql.Exec() Error: %s\n", err.Error())
	}

	return db
}

func startHttpServer() *http.Server {
	handle := http.NewServeMux()
	//登录
	handle.HandleFunc("/login", login_index)
	//首页
	handle.HandleFunc("/", index_default)
	handle.HandleFunc("/index/forbidden", index_forbidden)
	handle.HandleFunc("/index/stats", index_stats)
	handle.HandleFunc("/index/info", index_info)
	//用户操作
	handle.HandleFunc("/profile/logout", profile_logout)
	handle.HandleFunc("/profile/passwd", profile_passwd)
	//配置-redis
	handle.HandleFunc("/syscfg/redis", syscfg_redis)
	handle.HandleFunc("/syscfg/redis_list", syscfg_redis_list)
	handle.HandleFunc("/syscfg/redis_get", syscfg_redis_get)
	handle.HandleFunc("/syscfg/redis_set", syscfg_redis_set)
	handle.HandleFunc("/syscfg/redis_del", syscfg_redis_del)
	//配置-报警
	handle.HandleFunc("/syscfg/warn", syscfg_warn)
	handle.HandleFunc("/syscfg/warn_get", syscfg_warn_get)
	handle.HandleFunc("/syscfg/warn_set", syscfg_warn_set)
	//配置-账号
	handle.HandleFunc("/syscfg/account", syscfg_account)
	handle.HandleFunc("/syscfg/account_list", syscfg_account_list)
	handle.HandleFunc("/syscfg/account_get", syscfg_account_get)
	handle.HandleFunc("/syscfg/account_set", syscfg_account_set)
	handle.HandleFunc("/syscfg/account_del", syscfg_account_del)
	handle.HandleFunc("/syscfg/account_reset_pwd", syscfg_account_reset_pwd)
	//配置-杂项
	handle.HandleFunc("/syscfg/misc", syscfg_misc)
	handle.HandleFunc("/syscfg/misc_get", syscfg_misc_get)
	handle.HandleFunc("/syscfg/misc_set", syscfg_misc_set)
	//日志-Redis监控
	handle.HandleFunc("/log/monitor", log_monitor)
	handle.HandleFunc("/log/monitor_list", log_monitor_list)
	//日志-Redis报警
	handle.HandleFunc("/log/warn", log_warn)
	handle.HandleFunc("/log/warn_list", log_warn_list)
	//日志-Redis扩容
	handle.HandleFunc("/log/autoprocess", log_autoprocess)
	handle.HandleFunc("/log/autoprocess_list", log_autoprocess_list)
	//日志-系统错误
	handle.HandleFunc("/log/syslog", log_syslog)
	handle.HandleFunc("/log/syslog_list", log_syslog_list)
	//资源文件
	handle.Handle("/resources/", http.StripPrefix("/resources/", http.FileServer(http.Dir(Cfg.ResourcesDir))))
	//ico文件
	handle.HandleFunc("/favicon.ico", func(resp http.ResponseWriter, req *http.Request) {
		http.ServeFile(resp, req, filepath.Join(Cfg.ResourcesDir, "favicon.ico"))
	})
	//测试
	handle.HandleFunc("/debug/sendmail", debug_sendmail)
	//启动HTTP服务
	srv := &http.Server{
		Addr:    fmt.Sprintf("%s:%s", Cfg.Host, Cfg.Port),
		Handler: handle,
	}
	go func(s *http.Server) {
		if err := s.ListenAndServe(); err != nil {
			log.Fatal(err)
		}
	}(srv)
	return srv
}

func main() {
	//加载基础配置
	changeWordDir()
	Cfg = loadConfig()

	//连接DB
	Db = openDatabase(Cfg.Database)
	defer Db.Close()

	//系统日志表对象
	SysLogger = model.NewSysLog(Db)

	//创建HTTP会话池
	SessPoll = NewSessionPoll(Cfg.SessionTTL)

	//添加HTML模板函数
	TmplFuncMap = make(template.FuncMap)
	TmplFuncMap["has_prefix"] = strings.HasPrefix

	//从DB加载业务配置
	SysCfg = NewSysCfgCache()
	SysCfg.Load()

	//开始HTTP服务
	Srv = startHttpServer()

	//开始监控Redis状态
	Monitor := NewRedisMonitor()
	go Monitor.Loop()

	//开始处理邮件发送队列
	Sender = NewMailSender()
	go Sender.Loop()

	//开始定时清理日志表记录
	go func() {
		//取配置
		days := int(30)
		for {
			if str, ok := SysCfg.Get("log_kept_days"); ok {
				if num, err := strconv.Atoi(str); err == nil {
					days = num
				} else {
					SYSLOG("DEBUG", fmt.Sprintf("表syscfg记录cfg_key=log_kept_days的值不是数字:%s", err.Error()))
				}
			}
			ts := time.Now().Unix() - 86400*int64(days)
			SysLogger.CleanUntil(ts)
			mdlMonitorLog := model.NewMonitorLog(Db)
			mdlMonitorLog.CleanUntil(ts)
			mdlWarnLog := model.NewWarnLog(Db)
			mdlWarnLog.CleanUntil(ts)
			mdlProcessLog := model.NewProcessLog(Db)
			mdlProcessLog.CleanUntil(ts)
			time.Sleep(time.Hour * 1)
		}
	}()

	//记录启动日志
	SYSLOG("DEBUG", "服务启动")

	//监听信号
	waitSignal()
}
