// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package service

import (
	"context"
	"io/ioutil"
	"net"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"

	"yunion.io/x/log"
	"yunion.io/x/pkg/utils"
	_ "yunion.io/x/sqlchemy/backends"

	compute_api "yunion.io/x/onecloud/pkg/apis/scheduler"
	"yunion.io/x/onecloud/pkg/appsrv"
	"yunion.io/x/onecloud/pkg/cloudcommon"
	app_common "yunion.io/x/onecloud/pkg/cloudcommon/app"
	"yunion.io/x/onecloud/pkg/cloudcommon/db"
	common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
	_ "yunion.io/x/onecloud/pkg/compute/guestdrivers"
	_ "yunion.io/x/onecloud/pkg/compute/hostdrivers"
	computemodels "yunion.io/x/onecloud/pkg/compute/models"
	_ "yunion.io/x/onecloud/pkg/scheduler/algorithmprovider"
	"yunion.io/x/onecloud/pkg/scheduler/data_manager/cloudaccount"
	"yunion.io/x/onecloud/pkg/scheduler/data_manager/cloudprovider"
	"yunion.io/x/onecloud/pkg/scheduler/data_manager/cloudregion"
	"yunion.io/x/onecloud/pkg/scheduler/data_manager/netinterface"
	"yunion.io/x/onecloud/pkg/scheduler/data_manager/network"
	"yunion.io/x/onecloud/pkg/scheduler/data_manager/network_additional_wire"
	"yunion.io/x/onecloud/pkg/scheduler/data_manager/schedtag"
	skuman "yunion.io/x/onecloud/pkg/scheduler/data_manager/sku"
	"yunion.io/x/onecloud/pkg/scheduler/data_manager/wire"
	"yunion.io/x/onecloud/pkg/scheduler/data_manager/zone"
	schedhandler "yunion.io/x/onecloud/pkg/scheduler/handler"
	schedman "yunion.io/x/onecloud/pkg/scheduler/manager"
	o "yunion.io/x/onecloud/pkg/scheduler/options"
	"yunion.io/x/onecloud/pkg/util/gin/middleware"
)

func EnsureDBSync(opt *common_options.DBOptions) {
	checkDBSyncRetries := 5
	count := 1
	for {
		if count == checkDBSyncRetries {
			log.Fatalf("database schema not in sync!!!")
		}
		if !db.CheckSync(false, false, true) {
			log.Errorf("database schema not in sync, wait region sync database")
			time.Sleep(2 * time.Second)
		} else {
			break
		}
		count++
	}
}

func StartServiceWrapper(
	dbOpts *common_options.DBOptions,
	commonOpts *common_options.CommonOptions,
	sf func(app *appsrv.Application) error) error {
	// init region compute models
	cloudcommon.InitDB(dbOpts)
	defer cloudcommon.CloseDB()

	db.InitAllManagers()

	EnsureDBSync(dbOpts)

	app_common.InitAuth(commonOpts, func() {
		log.Infof("Auth complete!!")
	})

	if err := computemodels.InitDB(); err != nil {
		log.Fatalf("InitDB fail: %s", err)
	}

	app := app_common.InitApp(&commonOpts.BaseOptions, true)
	db.AppDBInit(app)

	return sf(app)
}

func StartService() error {
	o.Init()
	dbOpts := o.Options.DBOptions
	commonOpts := &o.Options.CommonOptions
	o.Options.Port = o.Options.SchedulerPort
	o.Options.AutoSyncTable = false
	o.Options.EnableDBChecksumTables = false
	o.Options.DBChecksumSkipInit = true

	return StartServiceWrapper(&dbOpts, commonOpts, func(app *appsrv.Application) error {
		common_options.StartOptionManager(&o.Options, o.Options.ConfigSyncPeriodSeconds, compute_api.SERVICE_TYPE, compute_api.SERVICE_VERSION, o.OnOptionsChange)

		// gin http framework mode configuration
		ginMode := "release"
		if o.Options.LogLevel == "debug" {
			ginMode = "debug"
		}
		gin.SetMode(ginMode)

		startSched := func() {
			stopEverything := make(chan struct{})
			ctx := context.Background()
			go skuman.Start(ctx, utils.ToDuration(o.Options.SkuRefreshInterval))
			go schedtag.Start(ctx, utils.ToDuration("30s"))
			go network_additional_wire.Start(ctx, utils.ToDuration("30m"))

			for _, f := range []func(ctx context.Context){
				cloudregion.Manager.Start,
				zone.Manager.Start,
				cloudprovider.GetManager().Start,
				cloudaccount.Manager.Start,
				wire.Manager.Start,
				network.Manager.Start,
				netinterface.GetManager().Start,
			} {
				f(ctx)
			}

			time.Sleep(5 * time.Second)

			schedman.InitAndStart(stopEverything)
		}
		startSched()
		//InitHandlers(app)
		return startHTTP(app, &o.Options)
	})
}

func startHTTP(app *appsrv.Application, opt *o.SchedulerOptions) error {
	gin.DefaultWriter = ioutil.Discard

	router := gin.Default()
	router.Use(middleware.Logger())
	router.Use(middleware.ErrorHandler)
	router.Use(middleware.KeystoneTokenVerifyMiddleware())

	// prometheus.InstallHandler(router)
	schedhandler.InstallHandler(router, opt.EnableAppProfiling)

	server := appsrv.InitHTTPServer(app, net.JoinHostPort(opt.Address, strconv.Itoa(int(opt.Port))))
	server.Handler = router

	log.Infof("Start server on: %s:%d", opt.Address, opt.Port)

	if opt.EnableSsl {
		return server.ListenAndServeTLS(opt.SslCertfile, opt.SslKeyfile)
	} else {
		return server.ListenAndServe()
	}
}
