package main

import (
	"fmt"
	"os"
	"runtime"
	"time"
	"mocksnow/db"
	"mocksnow/internal/settings"
	utils "mocksnow/internal/utils"
	"mocksnow/log"
	"mocksnow/server"
	"mocksnow/server/router"

	"github.com/go-co-op/gocron/v2"
	"github.com/joho/godotenv"
)

var (
	bindDisableTls   bool
	sha1ver          string // sha1 revision used to build the program
	buildTime        string // when the executable was built
	cronFrequency    time.Duration
	cronInvFrequency time.Duration
	encryptionKey    = []byte("5L1l3B5KvwOCzUHMAlCgsgUTRAYMfSpa")
)

func main() {
	// Load data from environment file
	envFilename := utils.GetFilePath(".env")
	err := godotenv.Load(envFilename)
	if err != nil {
		panic("Error loading .env file")
	}

	logger := log.New(
		log.GetLevel(),
		log.GetOutput(),
	)

	//ctx, cancel := context.WithCancel(context.Background())

	// Configure database
	database, err := db.New(logger, utils.GetFilePath("db.sqlite3"))
	if err != nil {
		logger.Error("Failed to create database", "error", err)
		os.Exit(1)
	}
	defer database.Close()
	//defer database.DB().Close()

	if err = db.Migrate(database); err != nil {
		logger.Error("failed to migrate database", "error", err)
		os.Exit(1)
	}

	// Load settings from yaml
	settingsFile := os.Getenv("SETTINGS_FILE")
	if settingsFile == "" {
		settingsFile = "settings.yaml"
	}

	// TODO - how to pass this to the other packages that will need this info?
	s := settings.New(logger, settingsFile)
	err = s.ReadYMLSettings()
	//s, err := settings.ReadYMLSettings(logger, settingsFile)
	if err != nil {
		logger.Error("failed to open yaml settings file", "error", err, "filename", settingsFile)
		//os.Exit(1)
	} else {
		logger.Debug("Loaded yaml settings", "contents", s)
	}

	// Determine bind IP
	bindIP := os.Getenv("BIND_IP")
	if bindIP == "" {
		bindIP = utils.GetOutboundIP().String()
	}
	// Determine bind port
	bindPort := os.Getenv("BIND_PORT")
	if bindPort == "" {
		bindPort = "9443"
	}
	bindAddress := fmt.Sprint(bindIP, ":", bindPort)
	//logger.Info("Will listen on address", "ip", bindIP, "port", bindPort)

	// Determine bind disable TLS
	bindDisableTlsEnv := os.Getenv("BIND_DISABLE_TLS")
	if bindDisableTlsEnv == "true" {
		bindDisableTls = true
	}

	// Get file names for TLS cert/key
	tlsCertFilename := os.Getenv("TLS_CERT_FILE")
	if tlsCertFilename != "" {
		tlsCertFilename = utils.GetFilePath(tlsCertFilename)
	} else {
		tlsCertFilename = "./cert.pem"
	}

	tlsKeyFilename := os.Getenv("TLS_KEY_FILE")
	if tlsKeyFilename != "" {
		tlsKeyFilename = utils.GetFilePath(tlsKeyFilename)
	} else {
		tlsKeyFilename = "./privkey.pem"
	}

	// Generate certificate if required
	if !(utils.FileExists(tlsCertFilename) && utils.FileExists(tlsKeyFilename)) {
		logger.Warn("Specified TLS certificate or private key do not exist", "certificate", tlsCertFilename, "tls-key", tlsKeyFilename)
		utils.GenerateCerts(tlsCertFilename, tlsKeyFilename)
	}

	// Prepare the task scheduler
	c, err := gocron.NewScheduler()
	if err != nil {
		logger.Error("failed to create scheduler", "error", err)
		os.Exit(1)
	}

	/*
		// Load vcenter credentials from .env
		a := secrets.New(logger, encryptionKey)
		vcEp := os.Getenv("VCENTER_PASSWORD")
		if len(vcEp) == 0 {
			logger.Error("No vcenter password configured")
			os.Exit(1)
		}
		vcPass, err := a.Decrypt(vcEp)
		if err != nil {
			logger.Error("failed to decrypt vcenter credentials. Assuming un-encrypted", "error", err)
			vcPass = []byte(vcEp)
			//os.Exit(1)
		}

		creds := vcenter.VcenterLogin{
			//insecureString := os.Getenv("VCENTER_INSECURE")
			Username: os.Getenv("VCENTER_USERNAME"),
			Password: string(vcPass),
		}

		// Prepare the task scheduler
		c, err := gocron.NewScheduler()
		if err != nil {
			logger.Error("failed to create scheduler", "error", err)
			os.Exit(1)
		}

		// Pass useful information to the cron jobs
		ct := &tasks.CronTask{
			Logger:   logger,
			Database: database,
			Settings: s,
			VcCreds:  &creds,
		}

		cronFrequencyString := os.Getenv("VCENTER_EVENT_POLLING_SECONDS")
		if cronFrequencyString != "" {
			cronFrequency, err = time.ParseDuration(cronFrequencyString)
			if err != nil {
				slog.Error("Can't convert VCENTER_EVENT_POLLING_SECONDS value to time duration. Defaulting to 60s", "value", cronFrequencyString, "error", err)
				cronFrequency = time.Second * 60
			}
		} else {
			cronFrequency = time.Second * 60
		}
		logger.Debug("Setting VM event polling cronjob frequency to", "frequency", cronFrequency)

		cronInventoryFrequencyString := os.Getenv("VCENTER_INVENTORY_POLLING_SECONDS")
		if cronInventoryFrequencyString != "" {
			cronInvFrequency, err = time.ParseDuration(cronInventoryFrequencyString)
			if err != nil {
				slog.Error("Can't convert VCENTER_INVENTORY_POLLING_SECONDS value to time duration. Defaulting to 7200", "value", cronInventoryFrequencyString, "error", err)
				cronInvFrequency = time.Second * 7200
			}
		} else {
			cronInvFrequency = time.Second * 7200
		}
		logger.Debug("Setting VM inventory polling cronjob frequency to", "frequency", cronInvFrequency)

		// start background processing for events stored in events table
		startsAt := time.Now().Add(time.Second * 10)
		job, err := c.NewJob(
			gocron.DurationJob(cronFrequency),
			gocron.NewTask(func() {
				ct.RunVmCheck(ctx, logger)
			}), gocron.WithSingletonMode(gocron.LimitModeReschedule),
			gocron.WithStartAt(gocron.WithStartDateTime(startsAt)),
		)
		if err != nil {
			logger.Error("failed to start event processing cron job", "error", err)
			os.Exit(1)
		}
		logger.Debug("Created event processing cron job", "job", job.ID(), "starting_at", startsAt)

		// start background checks of vcenter inventory
		startsAt2 := time.Now().Add(cronInvFrequency)
		job2, err := c.NewJob(
			gocron.DurationJob(cronInvFrequency),
			gocron.NewTask(func() {
				ct.RunVcenterPoll(ctx, logger)
			}), gocron.WithSingletonMode(gocron.LimitModeReschedule),
			gocron.WithStartAt(gocron.WithStartDateTime(startsAt2)),
		)
		if err != nil {
			logger.Error("failed to start vcenter inventory cron job", "error", err)
			os.Exit(1)
		}
		logger.Debug("Created vcenter inventory cron job", "job", job2.ID(), "starting_at", startsAt2)

		// start cron scheduler
		c.Start()
	*/

	// Start server
	r := router.New(logger, database, buildTime, sha1ver, runtime.Version(), s)
	svr := server.New(
		logger,
		c,
		//cancel,
		bindAddress,
		server.WithRouter(r),
		server.SetTls(bindDisableTls),
		server.SetCertificate(tlsCertFilename),
		server.SetPrivateKey(tlsKeyFilename),
	)
	//logger.Debug("Server configured", "object", svr)

	svr.StartAndWait()

	os.Exit(0)
}
