// Copyright (C) 2014-2018 Goodrain Co., Ltd.
// RAINBOND, Application Management Platform

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. For any non-GPL usage of Rainbond,
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
// must be obtained first.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package api

import (
	"fmt"
	"net"
	"net/http"
	"time"

	"github.com/goodrain/rainbond/cmd/mq/option"
	"github.com/goodrain/rainbond/mq/api/controller"
	"github.com/goodrain/rainbond/mq/api/mq"
	"github.com/goodrain/rainbond/mq/monitor"

	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"

	"golang.org/x/net/context"

	_ "net/http/pprof"

	restful "github.com/emicklei/go-restful"
	swagger "github.com/emicklei/go-restful-swagger12"
	grpcserver "github.com/goodrain/rainbond/mq/api/grpc/server"
	httputil "github.com/goodrain/rainbond/util/http"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/prometheus/common/version"
	"github.com/sirupsen/logrus"
)

type Manager struct {
	container *restful.Container
	ctx       context.Context
	cancel    context.CancelFunc
	conf      option.Config
	server    Server
	actionMQ  mq.ActionMQ
}
type Server interface {
	Server() error
	Close() error
}

type httpServer struct {
	server *http.Server
}

func (h *httpServer) Server() error {
	if err := h.server.ListenAndServe(); err != nil {
		logrus.Error("mq api http listen error.", err.Error())
		return err
	}
	return nil
}
func (h *httpServer) Close() error {
	if h.server != nil {
		ctx, _ := context.WithTimeout(context.Background(), time.Second*5)
		return h.server.Shutdown(ctx)
	}
	return nil
}

type grpcServer struct {
	server *grpc.Server
	lis    net.Listener
}

func (h *grpcServer) Server() error {
	if err := h.server.Serve(h.lis); err != nil {
		logrus.Error("mq api grpc listen error.", err.Error())
		return err
	}
	return nil
}
func (h *grpcServer) Close() error {
	return h.lis.Close()
}

//NewManager api manager
func NewManager(c option.Config) (*Manager, error) {
	ctx, cancel := context.WithCancel(context.Background())
	actionMQ := mq.NewActionMQ(ctx, c)
	manager := &Manager{
		ctx:      ctx,
		cancel:   cancel,
		conf:     c,
		actionMQ: actionMQ,
	}
	go func() {
		manager.Prometheus()
		health()
		if err := http.ListenAndServe(":6301", nil); err != nil {
			logrus.Error("mq pprof listen error.", err.Error())
		}
	}()
	if c.RunMode == "http" {
		wsContainer := restful.NewContainer()
		server := &http.Server{Addr: fmt.Sprintf(":%d", c.APIPort), Handler: wsContainer}
		controller.Register(wsContainer, actionMQ)
		manager.container = wsContainer
		manager.server = &httpServer{server}
		manager.doc()
		logrus.Info("mq server api run with http")
	} else {
		lis, err := net.Listen("tcp", fmt.Sprintf(":%d", c.APIPort))
		if err != nil {
			logrus.Errorf("failed to listen: %v", err)
			return nil, err
		}
		s := grpc.NewServer()
		grpcserver.RegisterServer(s, actionMQ)
		// Register reflection service on gRPC server.
		reflection.Register(s)
		manager.server = &grpcServer{
			server: s,
			lis:    lis,
		}
		logrus.Info("mq server api run with gRPC")
	}

	return manager, nil
}

//Start 启动
func (m *Manager) Start(errChan chan error) {
	logrus.Infof("api server start listening on 0.0.0.0:%d", m.conf.APIPort)
	err := m.actionMQ.Start()
	if err != nil {
		errChan <- err
	}
	go func() {
		if err := m.server.Server(); err != nil {
			logrus.Error("mq api listen error.", err.Error())
			errChan <- err
		}
	}()
}

func (m *Manager) doc() {
	// Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
	// You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
	// Open http://localhost:8080/apidocs and enter http://localhost:8080/swagger.json in the api input field.
	config := swagger.Config{
		WebServices: m.container.RegisteredWebServices(), // you control what services are visible
		ApiPath:     "/swagger.json",

		// Optionally, specify where the UI is located
		SwaggerPath: "/apidocs/",
		Info: swagger.Info{
			Title: "goodrain mq api doc.",
		},
		ApiVersion:      "1.0",
		SwaggerFilePath: "./dist"}
	swagger.RegisterSwaggerService(config, m.container)

}

//Stop 停止
func (m *Manager) Stop() error {
	logrus.Info("api server is stoping.")
	m.cancel()
	//m.server.Close()
	return m.actionMQ.Stop()
}

//Prometheus prometheus init
func (m *Manager) Prometheus() {
	prometheus.MustRegister(version.NewCollector("acp_mq"))
	exporter := monitor.NewExporter(m.actionMQ)
	prometheus.MustRegister(exporter)
	http.Handle("/metrics", promhttp.Handler())
}

func health() {
	http.HandleFunc("/health", checkHalth)
}

func checkHalth(w http.ResponseWriter, r *http.Request) {
	httputil.ReturnSuccess(r, w, map[string]string{"status": "health", "info": "mq service health"})
}
