package service

import (
	"context"
	"fmt"
	"strings"

	"coopcloud.tech/abra/pkg/formatter"
	"coopcloud.tech/abra/pkg/log"
	"github.com/AlecAivazis/survey/v2"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/api/types/swarm"
	"github.com/docker/docker/client"
)

// GetService retrieves a service container based on a label. If prompt is true
// and the retrievd count of service containers does not match 1, then a prompt
// is presented to let the user choose. An error is returned when no service is
// found.
func GetServiceByLabel(c context.Context, cl *client.Client, label string, prompt bool) (swarm.Service, error) {
	services, err := cl.ServiceList(c, types.ServiceListOptions{})
	if err != nil {
		return swarm.Service{}, err
	}

	if len(services) == 0 {
		return swarm.Service{}, fmt.Errorf("no services deployed?")
	}

	var matchingServices []swarm.Service
	for _, service := range services {
		if enabled, exists := service.Spec.Labels[label]; exists && enabled == "true" {
			matchingServices = append(matchingServices, service)
		}
	}

	if len(matchingServices) == 0 {
		return swarm.Service{}, fmt.Errorf("no services deployed matching label '%s'?", label)
	}

	if len(matchingServices) > 1 {
		var servicesRaw []string
		for _, service := range matchingServices {
			serviceName := service.Spec.Name
			created := formatter.HumanDuration(service.CreatedAt.Unix())
			servicesRaw = append(servicesRaw, fmt.Sprintf("%s (created %v)", serviceName, created))
		}

		if !prompt {
			err := fmt.Errorf("expected 1 service but found %v: %s", len(matchingServices), strings.Join(servicesRaw, " "))
			return swarm.Service{}, err
		}

		log.Warnf("ambiguous service list received, prompting for input")

		var response string
		prompt := &survey.Select{
			Message: "which service are you looking for?",
			Options: servicesRaw,
		}

		if err := survey.AskOne(prompt, &response); err != nil {
			return swarm.Service{}, err
		}

		chosenService := strings.TrimSpace(strings.Split(response, " ")[0])
		for _, service := range matchingServices {
			serviceName := strings.ToLower(service.Spec.Name)
			if serviceName == chosenService {
				return service, nil
			}
		}

		log.Fatal("failed to match chosen service")
	}

	return matchingServices[0], nil
}

// GetService retrieves a service container. If prompt is true and the retrievd
// count of service containers does not match 1, then a prompt is presented to
// let the user choose. A count of 0 is handled gracefully.
func GetService(c context.Context, cl *client.Client, filters filters.Args, prompt bool) (swarm.Service, error) {
	serviceOpts := types.ServiceListOptions{Filters: filters}
	services, err := cl.ServiceList(c, serviceOpts)
	if err != nil {
		return swarm.Service{}, err
	}

	if len(services) == 0 {
		filter := filters.Get("name")[0]
		return swarm.Service{}, fmt.Errorf("no services matching the %v filter found?", filter)
	}

	if len(services) != 1 {
		var servicesRaw []string
		for _, service := range services {
			serviceName := service.Spec.Name
			created := formatter.HumanDuration(service.CreatedAt.Unix())
			servicesRaw = append(servicesRaw, fmt.Sprintf("%s (created %v)", serviceName, created))
		}

		if !prompt {
			err := fmt.Errorf("expected 1 service but found %v: %s", len(services), strings.Join(servicesRaw, " "))
			return swarm.Service{}, err
		}

		log.Warnf("ambiguous service list received, prompting for input")

		var response string
		prompt := &survey.Select{
			Message: "which service are you looking for?",
			Options: servicesRaw,
		}

		if err := survey.AskOne(prompt, &response); err != nil {
			return swarm.Service{}, err
		}

		chosenService := strings.TrimSpace(strings.Split(response, " ")[0])
		for _, service := range services {
			serviceName := strings.ToLower(service.Spec.Name)
			if serviceName == chosenService {
				return service, nil
			}
		}

		log.Fatal("failed to match chosen service")
	}

	return services[0], nil
}

// ContainerToServiceName converts a container name to a service name.
func ContainerToServiceName(containerNames []string, stackName string) string {
	containerName := strings.Join(containerNames, "")
	trimmed := strings.TrimPrefix(containerName, "/")
	stackNameServiceName := strings.Split(trimmed, ".")[0]
	splitter := fmt.Sprintf("%s_", stackName)
	return strings.Split(stackNameServiceName, splitter)[1]
}
