package segments

import (
	"encoding/json"
	"fmt"
	"path/filepath"
	"strings"

	"slices"

	"github.com/jandedobbeleer/oh-my-posh/src/segments/options"
)

const (
	FetchContext  options.Option = "fetch_context"
	DockerCommand options.Option = "docker_command"
	// Filter applies to docker ps results in environment mode, see https://docs.docker.com/reference/cli/docker/container/ls/#filter
	Filter options.Option = "filter"
)

type DockerConfig struct {
	CurrentContext string `json:"currentContext"`
}

type Docker struct {
	Base
	command    *cmd
	Context    string
	Containers []Container
}

type Container struct {
	ID      string
	Image   string
	Command string
	Created string
	Status  string
	Ports   string
	Names   string
}

func (d *Docker) Template() string {
	return " \uf308 {{ .Context }} "
}

func (d *Docker) envVars() []string {
	return []string{"DOCKER_MACHINE_NAME", "DOCKER_HOST", "DOCKER_CONTEXT"}
}

func (d *Docker) configFiles() []string {
	files := []string{
		filepath.Join(d.env.Home(), ".docker/config.json"),
	}

	dockerConfig := d.env.Getenv("DOCKER_CONFIG")
	if len(dockerConfig) > 0 {
		files = append(files, filepath.Join(dockerConfig, "config.json"))
	}

	return files
}

func (d *Docker) Enabled() bool {
	extensions := []string{
		"compose.yml",
		"compose.yaml",
		"docker-compose.yml",
		"docker-compose.yaml",
		"Dockerfile",
	}

	extensions = d.options.StringArray(LanguageExtensions, extensions)

	displayMode := d.options.String(DisplayMode, DisplayModeContext)

	switch displayMode {
	case DisplayModeContext:
		return d.fetchContext()
	case DisplayModeFiles:
		if !slices.ContainsFunc(extensions, d.env.HasFiles) {
			return false
		}

		// always respect the context fetching
		if d.options.Bool(FetchContext, true) {
			_ = d.fetchContext()
		}

		return true
	case DisplayModeEnvironment:
		// always fetch context first
		_ = d.fetchContext()

		if d.Context == "" {
			d.Context = defaultStr
		}

		dockerCommand := d.options.String(DockerCommand, "docker")
		if !d.env.HasCommand(dockerCommand) {
			return false
		}

		filter := d.options.String(Filter, "")
		// Use Go template formatting with tab separation
		format := `{{.ID}}\t{{.Image}}\t{{.Command}}\t{{.CreatedAt}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}`
		args := []string{"ps", "--format", format}
		if len(filter) > 0 {
			args = append(args, "--filter", filter)
		}

		d.command = &cmd{
			executable: dockerCommand,
			args:       args,
		}

		containers, err := d.fetchContainers()
		if err != nil {
			return false
		}

		d.Containers = containers

		return len(d.Containers) > 0
	}

	return false
}

func (d *Docker) fetchContext() bool {
	// Check if there is a non-empty environment variable named `DOCKER_HOST` or `DOCKER_CONTEXT`
	// These variables are set by the docker CLI and override the config file
	// Return the current context if it is not empty and not `default`
	for _, v := range d.envVars() {
		context := d.env.Getenv(v)
		if len(context) > 0 && context != defaultStr {
			d.Context = context
			return true
		}
	}

	// Check if there is a file named `$HOME/.docker/config.json` or `$DOCKER_CONFIG/config.json`
	// Return the current context if it is not empty and not `default`
	for _, f := range d.configFiles() {
		data := d.env.FileContent(f)
		if data == "" {
			continue
		}

		var cfg DockerConfig
		if err := json.Unmarshal([]byte(data), &cfg); err != nil {
			continue
		}

		if len(cfg.CurrentContext) > 0 && cfg.CurrentContext != defaultStr {
			d.Context = cfg.CurrentContext
			return true
		}
	}

	return false
}

func (d *Docker) fetchContainers() ([]Container, error) {
	if d.command == nil {
		return nil, nil
	}

	output, err := d.env.RunCommand(d.command.executable, d.command.args...)
	if err != nil {
		return nil, err
	}

	lines := strings.Split(strings.TrimSpace(output), "\n")
	if len(lines) == 0 || (len(lines) == 1 && lines[0] == "") {
		return nil, nil
	}

	containers := make([]Container, 0, len(lines))

	for i, line := range lines {
		line = strings.TrimRight(line, "\r")
		fields := strings.Split(line, "\t")

		if len(fields) != 7 {
			return nil, fmt.Errorf("invalid docker ps output on line %d: expected 7 fields, got %d", i+1, len(fields))
		}

		containers = append(containers, Container{
			ID:      fields[0],
			Image:   fields[1],
			Command: fields[2],
			Created: fields[3],
			Status:  fields[4],
			Ports:   fields[5],
			Names:   fields[6],
		})
	}

	return containers, nil
}
