//
// DISCLAIMER
//
// Copyright 2017 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//

package driver

import (
	"context"
)

type serverModeResponse struct {
	Mode ServerMode `json:"mode"`
	ArangoError
}

type serverModeRequest struct {
	Mode ServerMode `json:"mode"`
}

// ShutdownInfo stores information about shutdown of the coordinator.
type ShutdownInfo struct {
	// AQLCursors stores a number of AQL cursors that are still active.
	AQLCursors int `json:"AQLcursors"`
	// Transactions stores a number of ongoing transactions.
	Transactions int `json:"transactions"`
	// PendingJobs stores a number of ongoing asynchronous requests.
	PendingJobs int `json:"pendingJobs"`
	// DoneJobs stores a number of finished asynchronous requests, whose result has not yet been collected.
	DoneJobs int `json:"doneJobs"`
	// PregelConductors stores a number of ongoing Pregel jobs.
	PregelConductors int `json:"pregelConductors"`
	// LowPrioOngoingRequests stores a number of ongoing low priority requests.
	LowPrioOngoingRequests int `json:"lowPrioOngoingRequests"`
	// LowPrioQueuedRequests stores a number of queued low priority requests.
	LowPrioQueuedRequests int `json:"lowPrioQueuedRequests"`
	// AllClear is set if all operations are closed.
	AllClear bool `json:"allClear"`
	// SoftShutdownOngoing describes whether a soft shutdown of the Coordinator is in progress.
	SoftShutdownOngoing bool `json:"softShutdownOngoing"`
}

// ServerMode returns the current mode in which the server/cluster is operating.
// This call needs ArangoDB 3.3 and up.
func (c *client) ServerMode(ctx context.Context) (ServerMode, error) {
	req, err := c.conn.NewRequest("GET", "_admin/server/mode")
	if err != nil {
		return "", WithStack(err)
	}
	resp, err := c.conn.Do(ctx, req)
	if err != nil {
		return "", WithStack(err)
	}
	if err := resp.CheckStatus(200); err != nil {
		return "", WithStack(err)
	}
	var result serverModeResponse
	if err := resp.ParseBody("", &result); err != nil {
		return "", WithStack(err)
	}
	return result.Mode, nil
}

// SetServerMode changes the current mode in which the server/cluster is operating.
// This call needs a client that uses JWT authentication.
// This call needs ArangoDB 3.3 and up.
func (c *client) SetServerMode(ctx context.Context, mode ServerMode) error {
	req, err := c.conn.NewRequest("PUT", "_admin/server/mode")
	if err != nil {
		return WithStack(err)
	}
	input := serverModeRequest{
		Mode: mode,
	}
	req, err = req.SetBody(input)
	if err != nil {
		return WithStack(err)
	}
	resp, err := c.conn.Do(ctx, req)
	if err != nil {
		return WithStack(err)
	}
	if err := resp.CheckStatus(200); err != nil {
		return WithStack(err)
	}
	return nil
}

// Logs retrieve logs from server in ArangoDB 3.8.0+ format
func (c *client) Logs(ctx context.Context) (ServerLogs, error) {
	req, err := c.conn.NewRequest("GET", "_admin/log/entries")
	if err != nil {
		return ServerLogs{}, WithStack(err)
	}
	resp, err := c.conn.Do(ctx, req)
	if err != nil {
		return ServerLogs{}, WithStack(err)
	}
	if err := resp.CheckStatus(200); err != nil {
		return ServerLogs{}, WithStack(err)
	}
	var data ServerLogs
	if err := resp.ParseBody("", &data); err != nil {
		return ServerLogs{}, WithStack(err)
	}
	return data, nil
}

// Shutdown a specific server, optionally removing it from its cluster.
func (c *client) Shutdown(ctx context.Context, removeFromCluster bool) error {
	req, err := c.conn.NewRequest("DELETE", "_admin/shutdown")
	if err != nil {
		return WithStack(err)
	}
	if removeFromCluster {
		req.SetQuery("remove_from_cluster", "1")
	}
	resp, err := c.conn.Do(ctx, req)
	if err != nil {
		return WithStack(err)
	}
	if err := resp.CheckStatus(200); err != nil {
		return WithStack(err)
	}
	return nil
}

// Metrics returns the metrics of the server in Prometheus format.
func (c *client) Metrics(ctx context.Context) ([]byte, error) {
	return c.getMetrics(ctx, "")
}

// MetricsForSingleServer returns the metrics of the specific server in Prometheus format.
// This parameter 'serverID' is only meaningful on Coordinators.
func (c *client) MetricsForSingleServer(ctx context.Context, serverID string) ([]byte, error) {
	return c.getMetrics(ctx, serverID)
}

// Metrics returns the metrics of the server in Prometheus format.
func (c *client) getMetrics(ctx context.Context, serverID string) ([]byte, error) {
	var rawResponse []byte
	ctx = WithRawResponse(ctx, &rawResponse)

	req, err := c.conn.NewRequest("GET", "_admin/metrics/v2")
	if err != nil {
		return rawResponse, WithStack(err)
	}

	if serverID != "" {
		req.SetQuery("serverId", serverID)
	}

	resp, err := c.conn.Do(ctx, req)
	if err != nil {
		return rawResponse, WithStack(err)
	}
	if err := resp.CheckStatus(200); err != nil {
		return rawResponse, WithStack(err)
	}
	return rawResponse, nil
}

// Statistics queries statistics from a specific server.
func (c *client) Statistics(ctx context.Context) (ServerStatistics, error) {
	req, err := c.conn.NewRequest("GET", "_admin/statistics")
	if err != nil {
		return ServerStatistics{}, WithStack(err)
	}
	resp, err := c.conn.Do(ctx, req)
	if err != nil {
		return ServerStatistics{}, WithStack(err)
	}
	if err := resp.CheckStatus(200); err != nil {
		return ServerStatistics{}, WithStack(err)
	}
	var data ServerStatistics
	if err := resp.ParseBody("", &data); err != nil {
		return ServerStatistics{}, WithStack(err)
	}
	return data, nil
}

// ShutdownV2 shuts down a specific coordinator, optionally removing it from the cluster with a graceful manner.
// When `graceful` is true then run soft shutdown process and the `ShutdownInfoV2` can be used to check the progress.
// It is available since versions: v3.7.12, v3.8.1, v3.9.0.
func (c *client) ShutdownV2(ctx context.Context, removeFromCluster, graceful bool) error {
	req, err := c.conn.NewRequest("DELETE", "_admin/shutdown")
	if err != nil {
		return WithStack(err)
	}
	if removeFromCluster {
		req.SetQuery("remove_from_cluster", "1")
	}
	if graceful {
		req.SetQuery("soft", "true")
	}
	resp, err := c.conn.Do(ctx, req)
	if err != nil {
		return WithStack(err)
	}
	if err := resp.CheckStatus(200); err != nil {
		return WithStack(err)
	}
	return nil
}

// ShutdownInfoV2 returns information about shutdown progress.
// It is available since versions: v3.7.12, v3.8.1, v3.9.0.
func (c *client) ShutdownInfoV2(ctx context.Context) (ShutdownInfo, error) {
	req, err := c.conn.NewRequest("GET", "_admin/shutdown")
	if err != nil {
		return ShutdownInfo{}, WithStack(err)
	}
	resp, err := c.conn.Do(ctx, req)
	if err != nil {
		return ShutdownInfo{}, WithStack(err)
	}
	if err := resp.CheckStatus(200); err != nil {
		return ShutdownInfo{}, WithStack(err)
	}
	data := ShutdownInfo{}
	if err := resp.ParseBody("", &data); err != nil {
		return ShutdownInfo{}, WithStack(err)
	}
	return data, nil
}