// Copyright 2021 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 ovhcloud

import (
	"context"
	"fmt"
	"net/netip"
	"net/url"
	"path"
	"strconv"

	"github.com/go-kit/log"
	"github.com/go-kit/log/level"
	"github.com/ovh/go-ovh/ovh"
	"github.com/prometheus/common/model"

	"github.com/prometheus/prometheus/discovery/refresh"
	"github.com/prometheus/prometheus/discovery/targetgroup"
)

const (
	dedicatedServerAPIPath     = "/dedicated/server"
	dedicatedServerLabelPrefix = metaLabelPrefix + "dedicated_server_"
)

// dedicatedServer struct from API. Also contains IP addresses that are fetched
// independently.
type dedicatedServer struct {
	State           string `json:"state"`
	ips             []netip.Addr
	CommercialRange string `json:"commercialRange"`
	LinkSpeed       int    `json:"linkSpeed"`
	Rack            string `json:"rack"`
	NoIntervention  bool   `json:"noIntervention"`
	Os              string `json:"os"`
	SupportLevel    string `json:"supportLevel"`
	ServerID        int64  `json:"serverId"`
	Reverse         string `json:"reverse"`
	Datacenter      string `json:"datacenter"`
	Name            string `json:"name"`
}

type dedicatedServerDiscovery struct {
	*refresh.Discovery
	config *SDConfig
	logger log.Logger
}

func newDedicatedServerDiscovery(conf *SDConfig, logger log.Logger) *dedicatedServerDiscovery {
	return &dedicatedServerDiscovery{config: conf, logger: logger}
}

func getDedicatedServerList(client *ovh.Client) ([]string, error) {
	var dedicatedListName []string
	err := client.Get(dedicatedServerAPIPath, &dedicatedListName)
	if err != nil {
		return nil, err
	}

	return dedicatedListName, nil
}

func getDedicatedServerDetails(client *ovh.Client, serverName string) (*dedicatedServer, error) {
	var dedicatedServerDetails dedicatedServer
	err := client.Get(path.Join(dedicatedServerAPIPath, url.QueryEscape(serverName)), &dedicatedServerDetails)
	if err != nil {
		return nil, err
	}

	var ips []string
	err = client.Get(path.Join(dedicatedServerAPIPath, url.QueryEscape(serverName), "ips"), &ips)
	if err != nil {
		return nil, err
	}

	parsedIPs, err := parseIPList(ips)
	if err != nil {
		return nil, err
	}

	dedicatedServerDetails.ips = parsedIPs
	return &dedicatedServerDetails, nil
}

func (d *dedicatedServerDiscovery) getService() string {
	return "dedicated_server"
}

func (d *dedicatedServerDiscovery) getSource() string {
	return fmt.Sprintf("%s_%s", d.config.Name(), d.getService())
}

func (d *dedicatedServerDiscovery) refresh(context.Context) ([]*targetgroup.Group, error) {
	client, err := createClient(d.config)
	if err != nil {
		return nil, err
	}
	var dedicatedServerDetailedList []dedicatedServer
	dedicatedServerList, err := getDedicatedServerList(client)
	if err != nil {
		return nil, err
	}
	for _, dedicatedServerName := range dedicatedServerList {
		dedicatedServer, err := getDedicatedServerDetails(client, dedicatedServerName)
		if err != nil {
			err := level.Warn(d.logger).Log("msg", fmt.Sprintf("%s: Could not get details of %s", d.getSource(), dedicatedServerName), "err", err.Error())
			if err != nil {
				return nil, err
			}
			continue
		}
		dedicatedServerDetailedList = append(dedicatedServerDetailedList, *dedicatedServer)
	}
	var targets []model.LabelSet

	for _, server := range dedicatedServerDetailedList {
		var ipv4, ipv6 string
		for _, ip := range server.ips {
			if ip.Is4() {
				ipv4 = ip.String()
			}
			if ip.Is6() {
				ipv6 = ip.String()
			}
		}
		defaultIP := ipv4
		if defaultIP == "" {
			defaultIP = ipv6
		}
		labels := model.LabelSet{
			model.AddressLabel:                              model.LabelValue(defaultIP),
			model.InstanceLabel:                             model.LabelValue(server.Name),
			dedicatedServerLabelPrefix + "state":            model.LabelValue(server.State),
			dedicatedServerLabelPrefix + "commercial_range": model.LabelValue(server.CommercialRange),
			dedicatedServerLabelPrefix + "link_speed":       model.LabelValue(fmt.Sprintf("%d", server.LinkSpeed)),
			dedicatedServerLabelPrefix + "rack":             model.LabelValue(server.Rack),
			dedicatedServerLabelPrefix + "no_intervention":  model.LabelValue(strconv.FormatBool(server.NoIntervention)),
			dedicatedServerLabelPrefix + "os":               model.LabelValue(server.Os),
			dedicatedServerLabelPrefix + "support_level":    model.LabelValue(server.SupportLevel),
			dedicatedServerLabelPrefix + "server_id":        model.LabelValue(fmt.Sprintf("%d", server.ServerID)),
			dedicatedServerLabelPrefix + "reverse":          model.LabelValue(server.Reverse),
			dedicatedServerLabelPrefix + "datacenter":       model.LabelValue(server.Datacenter),
			dedicatedServerLabelPrefix + "name":             model.LabelValue(server.Name),
			dedicatedServerLabelPrefix + "ipv4":             model.LabelValue(ipv4),
			dedicatedServerLabelPrefix + "ipv6":             model.LabelValue(ipv6),
		}
		targets = append(targets, labels)
	}

	return []*targetgroup.Group{{Source: d.getSource(), Targets: targets}}, nil
}