package web

import (
	"context"
	"encoding/json"
	"log"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"gitea.dooplex.hu/admin/felhom-controller/internal/agentapi"
)

// S6 old-agent degradation: the Debug page agent tab against a pre-0.83 agent (typed
// 404 — the StatusError precedent) renders the Hungarian notice, ok:true, no error
// spam. Companion red-proof: match the error by string instead of errors.As →
// unsupported turns into an error payload → the notice assertion fails.
func TestDebugAgentLogs_Pre083AgentRendersNotice(t *testing.T) {
	s := testServer(t)
	s.agentLogsFn = func(context.Context) (agentapi.AgentLogsResponse, error) {
		return agentapi.AgentLogsResponse{}, &agentapi.StatusError{Path: "/debug/logs", Code: http.StatusNotFound}
	}
	w := httptest.NewRecorder()
	s.debugAgentLogs(w, httptest.NewRequest("GET", "/api/debug/agent-logs", nil))
	var resp struct {
		OK   bool `json:"ok"`
		Data struct {
			Unsupported bool   `json:"unsupported"`
			Notice      string `json:"notice"`
		} `json:"data"`
	}
	if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode: %v (%s)", err, w.Body.String())
	}
	if !resp.OK || !resp.Data.Unsupported {
		t.Fatalf("resp = %+v, want ok+unsupported (not an error)", resp)
	}
	if !strings.Contains(resp.Data.Notice, "az ügynök következő frissítése után") {
		t.Errorf("notice = %q, want the next-update text", resp.Data.Notice)
	}
}

// A working ≥0.83 agent's entries are proxied through verbatim.
func TestDebugAgentLogs_ProxiesEntries(t *testing.T) {
	s := testServer(t)
	s.agentLogsFn = func(context.Context) (agentapi.AgentLogsResponse, error) {
		return agentapi.AgentLogsResponse{
			Entries: []agentapi.AgentLogEntry{{Level: "DEBUG", Message: "netverify: job started"}},
			Total:   1,
		}, nil
	}
	w := httptest.NewRecorder()
	s.debugAgentLogs(w, httptest.NewRequest("GET", "/api/debug/agent-logs", nil))
	body := w.Body.String()
	if !strings.Contains(body, "netverify: job started") || !strings.Contains(body, `"total":1`) {
		t.Errorf("proxy body missing agent entries: %s", body)
	}
}

// A non-404 agent error (down, 5xx) is an honest error payload — never the notice.
func TestDebugAgentLogs_OtherErrorIsError(t *testing.T) {
	s := testServer(t)
	s.agentLogsFn = func(context.Context) (agentapi.AgentLogsResponse, error) {
		return agentapi.AgentLogsResponse{}, &agentapi.StatusError{Path: "/debug/logs", Code: 502}
	}
	w := httptest.NewRecorder()
	s.debugAgentLogs(w, httptest.NewRequest("GET", "/api/debug/agent-logs", nil))
	if strings.Contains(w.Body.String(), "unsupported") || !strings.Contains(w.Body.String(), `"ok":false`) {
		t.Errorf("non-404 must be an error, not the notice: %s", w.Body.String())
	}
}

// S7 sweep smoke (controller half): a full fake NAS add at logging.level=info must
// leave the EXPECTED ORDERED phase lines in the debug ring — the test that encodes
// "an operator can reconstruct the NAS flow from the debug view". Companion
// red-proof: drop one asserted phase line (e.g. the probe verdict Debug) → FAIL
// naming the missing marker.
func TestNetAdd_LogSequenceReconstructsFlow(t *testing.T) {
	s := testServer(t)
	lb := NewLogBuffer(200)
	// The production writer layout at logging.level=info: ring captures everything.
	s.logger = log.New(lb, "", log.LstdFlags)

	agent := &fakeNetAgent{addRes: okAddRes("media"), verify: agentapi.NetVerifyStatus{Phase: "done", JobID: "job-1"}}
	s.netProbeFn = func(context.Context, string) probeOutcome { return probeOutcome{OK: true} }
	if !s.startNetAdd(agent, netAddReq("media"), "NAS media") {
		t.Fatal("startNetAdd refused")
	}
	job := waitNetAdd(t, s)
	if job.Phase != netAddPhaseDone {
		t.Fatalf("phase = %s, want done", job.Phase)
	}

	lines := lb.Lines(0)
	sequence := []string{
		`netstorage add "media" started`,
		"agent add accepted",
		"phase agent_add -> verifying",
		"agent verify verdict: phase=done",
		"phase verifying -> probing",
		"probe verdict: ok=true",
		"phase probing -> registering",
		"network storage added + verified",
	}
	pos := -1
	for _, marker := range sequence {
		idx := -1
		for i := pos + 1; i < len(lines); i++ {
			if strings.Contains(lines[i], marker) {
				idx = i
				break
			}
		}
		if idx < 0 {
			t.Fatalf("phase line %q missing (or out of order) — flow not reconstructable.\nring:\n%s",
				marker, strings.Join(lines, "\n"))
		}
		pos = idx
	}
}
