package executor

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

	"changkun.de/x/wallfacer/internal/pkg/uuidutil"
)

// containerJSON is the union of fields emitted by `podman ps --format json`
// and `docker ps --format json`. Fields like Names and Created have different
// JSON types between the two runtimes, so they use json.RawMessage / any to
// defer decoding until the runtime-specific accessor methods.
type containerJSON struct {
	ID        string            `json:"Id"`
	Names     json.RawMessage   `json:"Names"` // string (Docker) or []string (Podman)
	Image     string            `json:"Image"`
	State     string            `json:"State"`
	Status    string            `json:"Status"`
	Created   any               `json:"Created"`   // float64 (Podman unix ts) or json.Number
	CreatedAt string            `json:"CreatedAt"` // human-readable, not always present
	Labels    map[string]string `json:"Labels"`
}

// name extracts the container name from the Names JSON field.
// Podman returns Names as a JSON array of strings, while Docker returns it as
// a plain string (sometimes with a leading "/" prefix). This method tries both
// decodings and strips the "/" prefix that Docker adds.
func (c *containerJSON) name() (string, error) {
	if c.Names == nil {
		return "", nil
	}
	// Try Podman format first: JSON array of strings.
	var names []string
	if err := json.Unmarshal(c.Names, &names); err == nil && len(names) > 0 {
		return strings.TrimPrefix(names[0], "/"), nil
	}
	// Fall back to Docker format: bare JSON string.
	var name string
	if err := json.Unmarshal(c.Names, &name); err == nil {
		return strings.TrimPrefix(name, "/"), nil
	}
	return "", fmt.Errorf("containerJSON.name: cannot decode Names field: %s", c.Names)
}

// createdUnix extracts the container creation time as a Unix timestamp.
// The Created field varies by runtime: Podman emits a numeric Unix timestamp
// (decoded as float64 by encoding/json), while Docker may emit a json.Number.
// Returns 0 if the field is absent or has an unexpected type.
func (c *containerJSON) createdUnix() int64 {
	if c.Created != nil {
		switch v := c.Created.(type) {
		case float64:
			return int64(v)
		case json.Number:
			if n, err := v.Int64(); err == nil {
				return n
			}
		}
	}
	return 0
}

// ParseContainerList parses the JSON output of `ps --format json`, handling
// both Podman (JSON array) and Docker (NDJSON, one object per line) formats.
// Returns parsed entries that can be converted to ContainerInfo via
// ContainerInfoFromParsed.
func ParseContainerList(out []byte) ([]ContainerInfo, error) {
	trimmed := strings.TrimSpace(string(out))
	if trimmed == "" || trimmed == "null" {
		return nil, nil
	}

	var raw []containerJSON
	if trimmed[0] == '[' {
		if err := json.Unmarshal(out, &raw); err != nil {
			return nil, fmt.Errorf("parse container list (array): %w", err)
		}
	} else {
		for line := range strings.SplitSeq(trimmed, "\n") {
			line = strings.TrimSpace(line)
			if line == "" || line[0] != '{' {
				continue
			}
			var c containerJSON
			if err := json.Unmarshal([]byte(line), &c); err != nil {
				return nil, fmt.Errorf("parse container list (ndjson line): %w", err)
			}
			raw = append(raw, c)
		}
	}

	result := make([]ContainerInfo, 0, len(raw))
	for _, c := range raw {
		name, nameErr := c.name()
		if nameErr != nil {
			continue
		}

		// Extract task ID: prefer the explicit label, fall back to parsing
		// the UUID suffix from the container name (legacy naming convention).
		taskID := ""
		if c.Labels != nil {
			taskID = c.Labels["wallfacer.task.id"]
		}
		if taskID == "" {
			candidate := strings.TrimPrefix(name, "wallfacer-")
			if candidate != name && uuidutil.IsValid(candidate) {
				taskID = candidate
			}
		}

		result = append(result, ContainerInfo{
			ID:        c.ID,
			Name:      name,
			TaskID:    taskID,
			Image:     c.Image,
			State:     c.State,
			Status:    c.Status,
			CreatedAt: c.createdUnix(),
		})
	}
	return result, nil
}
