package infra

import (
	"encoding/json"
	"net"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"
)

// dockerAPIResponse is the minimal shape that /containers/json returns.
type dockerAPIResponse struct {
	ID    string   `json:"Id"`
	Names []string `json:"Names"`
	Image string   `json:"Image"`
	State string   `json:"State"`
	// Status intentionally omitted to test zero-value handling
	Labels map[string]string `json:"Labels"`
	Ports  []struct {
		IP          string `json:"IP"`
		PrivatePort uint16 `json:"PrivatePort"`
		PublicPort  uint16 `json:"PublicPort"`
		Type        string `json:"Type"`
	} `json:"Ports"`
	NetworkSettings struct {
		Networks map[string]json.RawMessage `json:"Networks"`
	} `json:"NetworkSettings"`
}

// startMockDockerUnix starts an HTTP server listening on a temporary unix
// socket and returns the DockerHost string and a cleanup func.
func startMockDockerUnix(t *testing.T, handler http.Handler) (dockerHost string, cleanup func()) {
	t.Helper()

	tmp := filepath.Join(t.TempDir(), "docker.sock")

	ln, err := net.Listen("unix", tmp)
	if err != nil {
		t.Fatalf("listen unix %s: %v", tmp, err)
	}

	srv := httptest.NewUnstartedServer(handler)
	srv.Listener = ln
	srv.Start()

	return "unix://" + tmp, func() {
		srv.Close()
		os.Remove(tmp)
	}
}

func TestDockerContainerList(t *testing.T) {
	running := dockerAPIResponse{
		ID:    "abc123def456gh",
		Names: []string{"/my-app"},
		Image: "nginx:latest",
		State: "running",
		Ports: []struct {
			IP          string `json:"IP"`
			PrivatePort uint16 `json:"PrivatePort"`
			PublicPort  uint16 `json:"PublicPort"`
			Type        string `json:"Type"`
		}{{IP: "0.0.0.0", PrivatePort: 80, PublicPort: 8080, Type: "tcp"}},
		Labels: map[string]string{"app": "my-app"},
	}
	running.NetworkSettings.Networks = map[string]json.RawMessage{
		"bridge": json.RawMessage(`{}`),
	}

	exited := dockerAPIResponse{
		ID:    "deadbeef1234ab",
		Names: []string{"/old-app"},
		Image: "redis:7",
		State: "exited",
	}

	labeled := dockerAPIResponse{
		ID:    "cafe00112233ab",
		Names: []string{"/agents-app"},
		Image: "agents:latest",
		State: "running",
		Labels: map[string]string{
			"app":  "agents_and_robots",
			"team": "device",
		},
	}

	t.Run("lista solo running", func(t *testing.T) {
		// Serve only running containers (All=false → daemon omits exited).
		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.URL.Path != "/containers/json" {
				http.NotFound(w, r)
				return
			}
			// Verify All param NOT set
			if r.URL.Query().Get("all") != "" {
				t.Errorf("expected no 'all' param, got %q", r.URL.Query().Get("all"))
			}
			w.Header().Set("Content-Type", "application/json")
			json.NewEncoder(w).Encode([]dockerAPIResponse{running})
		})

		dockerHost, cleanup := startMockDockerUnix(t, handler)
		defer cleanup()

		containers, err := DockerContainerList(DockerContainerListOpts{
			All:        false,
			DockerHost: dockerHost,
		})
		if err != nil {
			t.Fatalf("DockerContainerList: %v", err)
		}
		if len(containers) != 1 {
			t.Fatalf("expected 1 container, got %d", len(containers))
		}
		c := containers[0]
		if c.ID != "abc123def456" {
			t.Errorf("ID: got %q, want %q", c.ID, "abc123def456")
		}
		if len(c.Names) == 0 || c.Names[0] != "/my-app" {
			t.Errorf("Names: got %v, want [\"/my-app\"]", c.Names)
		}
		if c.State != "running" {
			t.Errorf("State: got %q, want \"running\"", c.State)
		}
		if len(c.Ports) == 0 {
			t.Errorf("expected ports, got empty")
		}
		if len(c.Networks) == 0 || c.Networks[0] != "bridge" {
			t.Errorf("Networks: got %v, want [\"bridge\"]", c.Networks)
		}
	})

	t.Run("All=true incluye exited", func(t *testing.T) {
		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.URL.Path != "/containers/json" {
				http.NotFound(w, r)
				return
			}
			if r.URL.Query().Get("all") != "1" {
				t.Errorf("expected all=1, got %q", r.URL.Query().Get("all"))
			}
			w.Header().Set("Content-Type", "application/json")
			json.NewEncoder(w).Encode([]dockerAPIResponse{running, exited})
		})

		dockerHost, cleanup := startMockDockerUnix(t, handler)
		defer cleanup()

		containers, err := DockerContainerList(DockerContainerListOpts{
			All:        true,
			DockerHost: dockerHost,
		})
		if err != nil {
			t.Fatalf("DockerContainerList: %v", err)
		}
		if len(containers) != 2 {
			t.Fatalf("expected 2 containers, got %d", len(containers))
		}

		states := map[string]bool{}
		for _, c := range containers {
			states[c.State] = true
		}
		if !states["running"] {
			t.Error("missing running container")
		}
		if !states["exited"] {
			t.Error("missing exited container")
		}
	})

	t.Run("filter label aplica", func(t *testing.T) {
		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.URL.Path != "/containers/json" {
				http.NotFound(w, r)
				return
			}
			filtersRaw := r.URL.Query().Get("filters")
			if filtersRaw == "" {
				t.Error("expected filters param, got empty")
			}
			// Verify the filter map includes label key
			var fm map[string][]string
			if err := json.Unmarshal([]byte(filtersRaw), &fm); err != nil {
				t.Errorf("parse filters: %v", err)
			}
			labelFilters := fm["label"]
			found := false
			for _, lf := range labelFilters {
				if lf == "app=agents_and_robots" {
					found = true
				}
			}
			if !found {
				t.Errorf("filter label=app=agents_and_robots not found in %v", fm)
			}
			w.Header().Set("Content-Type", "application/json")
			json.NewEncoder(w).Encode([]dockerAPIResponse{labeled})
		})

		dockerHost, cleanup := startMockDockerUnix(t, handler)
		defer cleanup()

		containers, err := DockerContainerList(DockerContainerListOpts{
			All:        true,
			Filters:    []string{"label=app=agents_and_robots"},
			DockerHost: dockerHost,
		})
		if err != nil {
			t.Fatalf("DockerContainerList: %v", err)
		}
		if len(containers) != 1 {
			t.Fatalf("expected 1 container, got %d", len(containers))
		}
		c := containers[0]
		if c.Labels["app"] != "agents_and_robots" {
			t.Errorf("Labels[app]: got %q, want %q", c.Labels["app"], "agents_and_robots")
		}
	})
}
