package web

import (
	"io"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"gitea.dooplex.hu/admin/felhom-controller/internal/config"
	"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
	"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
)

// C6B-F2 guard (v0.130.0, scenario F): removing a network share while a DEPLOYED app's HDD_PATH
// lives on it is REFUSED up front — the agent's unit teardown never starts, so the busy-mount →
// tolerated-stop → unit-files-deleted-anyway → orphaned-autofs chain (the observed C6B-F2) cannot
// be triggered through the product flow. RED-PROOF: drop the deployedAppsOnPath guard from
// handleNetStorageRemove → the 409/zero-agent-call/still-registered assertions fail (the pre-fix
// behavior: removed:true while sonarr ran on the share).

// testRemoveGuardServer builds a Server with a REAL stacks.Manager over a temp stacks dir holding
// one app (deployed flag per arg) whose HDD_PATH sits ON the share, plus the share registered.
func testRemoveGuardServer(t *testing.T, deployed bool) (*Server, *fakeNetAgent, string) {
	t.Helper()
	lg := log.New(io.Discard, "", 0)
	dir := t.TempDir()
	cfg := &config.Config{}
	cfg.Paths.StacksDir = filepath.Join(dir, "stacks")
	cfg.Paths.DataDir = filepath.Join(dir, "data")
	cfg.Stacks.ComposeCommand = "docker compose"

	share := settings.NetworkMountRoot + "/campaign6"
	stackDir := filepath.Join(cfg.Paths.StacksDir, "sonarr")
	if err := os.MkdirAll(stackDir, 0o755); err != nil {
		t.Fatal(err)
	}
	os.WriteFile(filepath.Join(stackDir, ".felhom.yml"), []byte("display_name: Sonarr\n"), 0o644)
	os.WriteFile(filepath.Join(stackDir, "docker-compose.yml"), []byte("services: {}\n"), 0o644)
	appYAML := "deployed: false\n"
	if deployed {
		// the C6B live shape: HDD_PATH is a SUBPATH of the share root (<share>/<app>)
		appYAML = "deployed: true\nenv:\n    HDD_PATH: " + share + "/sonarr\n"
	}
	os.WriteFile(filepath.Join(stackDir, "app.yaml"), []byte(appYAML), 0o644)

	sett, err := settings.Load(filepath.Join(dir, "settings.json"), lg)
	if err != nil {
		t.Fatal(err)
	}
	if err := sett.AddStoragePath(settings.StoragePath{
		Path: share, Label: "Kampány 6 teszt", Schedulable: true, Kind: settings.StorageKindNetwork,
	}); err != nil {
		t.Fatal(err)
	}

	mgr, err := stacks.NewManager(cfg, lg)
	if err != nil {
		t.Fatal(err)
	}
	_ = mgr.ScanStacks() // container-status refresh may fail on docker-less hosts — discovery is enough
	if _, ok := mgr.GetStack("sonarr"); !ok {
		t.Fatal("sonarr not discovered by ScanStacks")
	}

	agent := &fakeNetAgent{}
	s := &Server{cfg: cfg, settings: sett, stackMgr: mgr, logger: lg}
	s.netAgentFn = func() (netAgent, error) { return agent, nil }
	return s, agent, share
}

func postNetRemove(t *testing.T, s *Server, name string) *httptest.ResponseRecorder {
	t.Helper()
	r := httptest.NewRequest(http.MethodPost, "/api/storage/netstorage/remove",
		strings.NewReader(`{"name":"`+name+`"}`))
	w := httptest.NewRecorder()
	s.handleNetStorageRemove(w, r)
	return w
}

func TestNetStorageRemove_RefusedWhileAppDeployedOnShare(t *testing.T) {
	s, agent, share := testRemoveGuardServer(t, true)

	w := postNetRemove(t, s, "campaign6")
	if w.Code != http.StatusConflict {
		t.Fatalf("remove with a deployed app on the share: got %d want 409 (%s)", w.Code, w.Body.String())
	}
	if !strings.Contains(w.Body.String(), "Sonarr") {
		t.Errorf("the refusal must NAME the blocking app, got: %s", w.Body.String())
	}
	if !strings.Contains(w.Body.String(), "nem távolítható el") {
		t.Errorf("expected the Hungarian refusal, got: %s", w.Body.String())
	}
	// The agent teardown must NEVER have started — that is what orphans the automount.
	if got := agent.removed(); len(got) != 0 {
		t.Fatalf("agent RemoveNetStorage ran despite the refusal: %v", got)
	}
	// The share stays registered (nothing half-removed).
	found := false
	for _, sp := range s.settings.GetStoragePaths() {
		if sp.Path == share {
			found = true
		}
	}
	if !found {
		t.Fatal("the share was deregistered despite the refusal")
	}
}

func TestNetStorageRemove_ProceedsWithoutDeployedApps(t *testing.T) {
	s, agent, share := testRemoveGuardServer(t, false)

	w := postNetRemove(t, s, "campaign6")
	if w.Code != http.StatusOK {
		t.Fatalf("remove with no deployed apps: got %d want 200 (%s)", w.Code, w.Body.String())
	}
	if got := agent.removed(); len(got) != 1 || got[0] != "campaign6" {
		t.Fatalf("agent RemoveNetStorage calls = %v, want [campaign6]", got)
	}
	for _, sp := range s.settings.GetStoragePaths() {
		if sp.Path == share {
			t.Fatal("the share must be deregistered after a successful remove")
		}
	}
}
