// Copyright The Prometheus Authors
// 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 moby

import (
	"context"
	"fmt"
	"net"
	"strconv"

	"github.com/docker/docker/api/types/swarm"
	"github.com/prometheus/common/model"

	"github.com/prometheus/prometheus/discovery/targetgroup"
	"github.com/prometheus/prometheus/util/strutil"
)

const (
	swarmLabelNodePrefix               = swarmLabel + "node_"
	swarmLabelNodeAddress              = swarmLabelNodePrefix + "address"
	swarmLabelNodeAvailability         = swarmLabelNodePrefix + "availability"
	swarmLabelNodeEngineVersion        = swarmLabelNodePrefix + "engine_version"
	swarmLabelNodeHostname             = swarmLabelNodePrefix + "hostname"
	swarmLabelNodeID                   = swarmLabelNodePrefix + "id"
	swarmLabelNodeLabelPrefix          = swarmLabelNodePrefix + "label_"
	swarmLabelNodeManagerAddr          = swarmLabelNodePrefix + "manager_address"
	swarmLabelNodeManagerLeader        = swarmLabelNodePrefix + "manager_leader"
	swarmLabelNodeManagerReachability  = swarmLabelNodePrefix + "manager_reachability"
	swarmLabelNodePlatformArchitecture = swarmLabelNodePrefix + "platform_architecture"
	swarmLabelNodePlatformOS           = swarmLabelNodePrefix + "platform_os"
	swarmLabelNodeRole                 = swarmLabelNodePrefix + "role"
	swarmLabelNodeStatus               = swarmLabelNodePrefix + "status"
)

func (d *Discovery) refreshNodes(ctx context.Context) ([]*targetgroup.Group, error) {
	tg := &targetgroup.Group{
		Source: "DockerSwarm",
	}

	nodes, err := d.client.NodeList(ctx, swarm.NodeListOptions{Filters: d.filters})
	if err != nil {
		return nil, fmt.Errorf("error while listing swarm nodes: %w", err)
	}

	for _, n := range nodes {
		labels := model.LabelSet{
			swarmLabelNodeID:                   model.LabelValue(n.ID),
			swarmLabelNodeRole:                 model.LabelValue(n.Spec.Role),
			swarmLabelNodeAvailability:         model.LabelValue(n.Spec.Availability),
			swarmLabelNodeHostname:             model.LabelValue(n.Description.Hostname),
			swarmLabelNodePlatformArchitecture: model.LabelValue(n.Description.Platform.Architecture),
			swarmLabelNodePlatformOS:           model.LabelValue(n.Description.Platform.OS),
			swarmLabelNodeEngineVersion:        model.LabelValue(n.Description.Engine.EngineVersion),
			swarmLabelNodeStatus:               model.LabelValue(n.Status.State),
			swarmLabelNodeAddress:              model.LabelValue(n.Status.Addr),
		}
		if n.ManagerStatus != nil {
			labels[swarmLabelNodeManagerLeader] = model.LabelValue(strconv.FormatBool(n.ManagerStatus.Leader))
			labels[swarmLabelNodeManagerReachability] = model.LabelValue(n.ManagerStatus.Reachability)
			labels[swarmLabelNodeManagerAddr] = model.LabelValue(n.ManagerStatus.Addr)
		}

		for k, v := range n.Spec.Labels {
			ln := strutil.SanitizeLabelName(k)
			labels[model.LabelName(swarmLabelNodeLabelPrefix+ln)] = model.LabelValue(v)
		}

		addr := net.JoinHostPort(n.Status.Addr, strconv.FormatUint(uint64(d.port), 10))
		labels[model.AddressLabel] = model.LabelValue(addr)

		tg.Targets = append(tg.Targets, labels)
	}
	return []*targetgroup.Group{tg}, nil
}

func (d *Discovery) getNodesLabels(ctx context.Context) (map[string]map[string]string, error) {
	nodes, err := d.client.NodeList(ctx, swarm.NodeListOptions{})
	if err != nil {
		return nil, fmt.Errorf("error while listing swarm nodes: %w", err)
	}
	labels := make(map[string]map[string]string, len(nodes))
	for _, n := range nodes {
		labels[n.ID] = map[string]string{
			swarmLabelNodeID:                   n.ID,
			swarmLabelNodeRole:                 string(n.Spec.Role),
			swarmLabelNodeAddress:              n.Status.Addr,
			swarmLabelNodeAvailability:         string(n.Spec.Availability),
			swarmLabelNodeHostname:             n.Description.Hostname,
			swarmLabelNodePlatformArchitecture: n.Description.Platform.Architecture,
			swarmLabelNodePlatformOS:           n.Description.Platform.OS,
			swarmLabelNodeStatus:               string(n.Status.State),
		}
		for k, v := range n.Spec.Labels {
			ln := strutil.SanitizeLabelName(k)
			labels[n.ID][swarmLabelNodeLabelPrefix+ln] = v
		}
	}
	return labels, nil
}
