package docker

import (
	"errors"
	"fmt"

	"github.com/rs/zerolog/log"
	"github.com/traefik/traefik/v3/pkg/config/label"
)

const (
	labelDockerComposeProject = "com.docker.compose.project"
	labelDockerComposeService = "com.docker.compose.service"
)

// configuration contains information from the labels that are globals (not related to the dynamic configuration)
// or specific to the provider.
type configuration struct {
	Enable          bool
	Network         string
	LBSwarm         bool
	AllowNonRunning bool
}

type labelConfiguration struct {
	Enable bool
	Docker *dockerSpecificConfiguration
	Swarm  *specificConfiguration
}

type dockerSpecificConfiguration struct {
	Network         *string
	LBSwarm         bool
	AllowNonRunning bool
}

type specificConfiguration struct {
	Network *string
	LBSwarm bool
}

func (p *Shared) extractDockerLabels(container dockerData) (configuration, error) {
	conf := labelConfiguration{Enable: p.ExposedByDefault}
	if err := label.Decode(container.Labels, &conf, "traefik.docker.", "traefik.enable"); err != nil {
		return configuration{}, fmt.Errorf("decoding Docker labels: %w", err)
	}

	network := p.Network
	if conf.Docker != nil && conf.Docker.Network != nil {
		network = *conf.Docker.Network
	}

	var allowNonRunning bool
	if conf.Docker != nil {
		allowNonRunning = conf.Docker.AllowNonRunning
	}

	return configuration{
		Enable:          conf.Enable,
		Network:         network,
		AllowNonRunning: allowNonRunning,
	}, nil
}

func (p *Shared) extractSwarmLabels(container dockerData) (configuration, error) {
	labelConf := labelConfiguration{Enable: p.ExposedByDefault}
	if err := label.Decode(container.Labels, &labelConf, "traefik.enable", "traefik.docker.", "traefik.swarm."); err != nil {
		return configuration{}, fmt.Errorf("decoding Swarm labels: %w", err)
	}

	if labelConf.Docker != nil && labelConf.Swarm != nil {
		return configuration{}, errors.New("both Docker and Swarm labels are defined")
	}

	conf := configuration{
		Enable:  labelConf.Enable,
		Network: p.Network,
	}

	if labelConf.Docker != nil {
		log.Warn().Msg("Labels traefik.docker.* for Swarm provider are deprecated. Please use traefik.swarm.* labels instead")

		conf.LBSwarm = labelConf.Docker.LBSwarm

		if labelConf.Docker.Network != nil {
			conf.Network = *labelConf.Docker.Network
		}
	}

	if labelConf.Swarm != nil {
		conf.LBSwarm = labelConf.Swarm.LBSwarm

		if labelConf.Swarm.Network != nil {
			conf.Network = *labelConf.Swarm.Network
		}
	}

	return conf, nil
}

// getStringMultipleStrict get multiple string values associated to several labels.
// Fail if one label is missing.
func getStringMultipleStrict(labels map[string]string, labelNames ...string) (map[string]string, error) {
	foundLabels := map[string]string{}
	for _, name := range labelNames {
		value := getStringValue(labels, name, "")
		// Error out only if one of them is not defined.
		if len(value) == 0 {
			return nil, fmt.Errorf("label not found: %s", name)
		}
		foundLabels[name] = value
	}
	return foundLabels, nil
}

// getStringValue get string value associated to a label.
func getStringValue(labels map[string]string, labelName, defaultValue string) string {
	if value, ok := labels[labelName]; ok && len(value) > 0 {
		return value
	}
	return defaultValue
}
