// 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 esxi

import (
	"context"
	"fmt"
	"net"
	"net/http"

	"yunion.io/x/log"

	"yunion.io/x/onecloud/pkg/apis/compute"
	"yunion.io/x/onecloud/pkg/appsrv"
	"yunion.io/x/onecloud/pkg/cloudcommon/agent"
	"yunion.io/x/onecloud/pkg/cloudcommon/cronman"
	"yunion.io/x/onecloud/pkg/cloudcommon/workmanager"
	"yunion.io/x/onecloud/pkg/esxi/options"
	"yunion.io/x/onecloud/pkg/hostman/hostutils"
	"yunion.io/x/onecloud/pkg/hostman/storageman"
	"yunion.io/x/onecloud/pkg/httperrors"
	"yunion.io/x/onecloud/pkg/mcclient"
	"yunion.io/x/onecloud/pkg/mcclient/auth"
	"yunion.io/x/onecloud/pkg/util/netutils2"
)

const (
	_ = compute.AgentTypeEsxi
)

var (
	EsxiAgent *SEsxiAgent
)

type SEsxiAgent struct {
	agent.SBaseAgent

	agentImageCache *storageman.SAgentImageCacheManager
	AgentStorage    *storageman.SAgentStorage
	ListenNic       netutils2.SNetInterface
}

func NewEsxiAgent() (*SEsxiAgent, error) {
	agent := &SEsxiAgent{}
	err := agent.Init(agent, options.Options.ListenInterface, options.Options.ImageCachePath)
	if err != nil {
		return nil, err
	}
	return agent, nil
}

func (ea *SEsxiAgent) GetAgentType() string {
	return string(compute.AgentTypeEsxi)
}

func (ea *SEsxiAgent) GetAccessIP() (net.IP, error) {
	return ea.GetListenIP()
}

func (ea *SEsxiAgent) GetListenIP() (net.IP, error) {
	return ea.FindListenIP(options.Options.ListenAddress)
}

func (ea *SEsxiAgent) GetPort() int {
	return options.Options.Port
}

func (ea *SEsxiAgent) GetEnableSsl() bool {
	return options.Options.EnableSsl
}

func (ea *SEsxiAgent) GetZoneId() string {
	return options.Options.Zone
}

func (ea *SEsxiAgent) GetAdminSession() *mcclient.ClientSession {
	return auth.GetAdminSession(context.TODO(), options.Options.Region)
}

func (ea *SEsxiAgent) TuneSystem() error {
	return nil
}

func (ea *SEsxiAgent) StartService() error {

	ea.DoOnline(ea.GetAdminSession())
	return nil
}

func (ea *SEsxiAgent) StopService() error {
	return nil
}

func (ea *SEsxiAgent) Start() error {
	err := ea.SBaseAgent.Start()
	if err != nil {
		return err
	}
	// add agent image cache
	ea.agentImageCache = storageman.NewAgentImageCacheManager(ea.CacheManager)
	ea.AgentStorage = storageman.NewAgentStorage(&storageman.SStorageManager{LocalStorageImagecacheManager: ea.CacheManager},
		ea, options.Options.AgentTempPath)

	cronManager := cronman.InitCronJobManager(false, options.Options.CronJobWorkerCount, options.Options.TimeZone)
	err = cronManager.AddJobEveryFewDays(
		"CleanRecycleDiskFiles", 1, 3, 0, 0, storageman.CleanRecycleDiskfiles, false)
	if err != nil {
		return err
	}
	cronManager.Start()
	return nil
}

func Start(app *appsrv.Application) error {
	var err error
	if EsxiAgent != nil {
		log.Warningf("Global EsxiAgent already start")
		return nil
	}
	EsxiAgent, err = NewEsxiAgent()
	if err != nil {
		return err
	}
	err = EsxiAgent.Start()
	if err != nil {
		return err
	}
	EsxiAgent.AddImageCacheHandler("", app)
	return nil
}

func (agent *SEsxiAgent) AddImageCacheHandler(prefix string, app *appsrv.Application) {
	app.AddHandler("POST",
		fmt.Sprintf("%s/disks/image_cache", prefix),
		auth.Authenticate(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
			performImageCache(ctx, w, r, agent.agentImageCache.PrefetchImageCache)
		}))
	app.AddHandler("DELETE",
		fmt.Sprintf("%s/disks/image_cache", prefix),
		auth.Authenticate(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
			performImageCache(ctx, w, r, agent.agentImageCache.DeleteImageCache)
		}))
}

func performImageCache(
	ctx context.Context,
	w http.ResponseWriter,
	r *http.Request,
	performTask workmanager.DelayTaskFunc,
) {
	_, _, body := appsrv.FetchEnv(ctx, w, r)

	disk, err := body.Get("disk")
	if err != nil {
		httperrors.MissingParameterError(ctx, w, "disk")
		return
	}

	hostutils.DelayTask(ctx, performTask, disk)
	hostutils.ResponseOk(ctx, w)
}

func Stop() error {
	if EsxiAgent != nil {
		log.Infof("EsxiAgent stop...")
		tmpAgent := EsxiAgent
		EsxiAgent = nil
		tmpAgent.Stop()
	}
	return nil
}
