package api

import (
	"encoding/json"
	"net/http"
	"testing"
	"time"
)

var longAgoAPI = time.Now().Add(-365 * 24 * time.Hour)

// Part D (API half) — the full pull-based round-trip on /report:
//  1. no pending request → the ACK omits log_tail_requests entirely
//  2. operator requests a tail → the ACK advertises it
//  3. the controller's next report carries log_tails → stored + request cleared
//  4. the FOLLOWING report's ACK omits the field again (consume-once)
func TestReportACK_LogTailRoundTrip(t *testing.T) {
	h, st, _ := newTestHandler(t)

	reportBody := `{"customer_id":"cust-a"}`

	// 1. baseline ACK — no field
	rr := do(h, http.MethodPost, "/report", globalKey, reportBody)
	if rr.Code != http.StatusOK {
		t.Fatalf("report POST = %d (%s)", rr.Code, rr.Body.String())
	}
	var ack map[string]json.RawMessage
	json.Unmarshal(rr.Body.Bytes(), &ack)
	if _, present := ack["log_tail_requests"]; present {
		t.Fatalf("baseline ACK must omit log_tail_requests: %s", rr.Body.String())
	}

	// 2. operator requests a tail → advertised
	if err := st.RequestLogTail("cust-a", "gokapi"); err != nil {
		t.Fatalf("RequestLogTail: %v", err)
	}
	rr = do(h, http.MethodPost, "/report", globalKey, reportBody)
	ack = map[string]json.RawMessage{}
	json.Unmarshal(rr.Body.Bytes(), &ack)
	var apps []string
	json.Unmarshal(ack["log_tail_requests"], &apps)
	if len(apps) != 1 || apps[0] != "gokapi" {
		t.Fatalf("ACK log_tail_requests = %v, want [gokapi] (%s)", apps, rr.Body.String())
	}

	// 3. the controller ships the tail on its next report → stored + consumed
	tailReport := `{"customer_id":"cust-a","log_tails":[{"app":"gokapi","collected_at":"2026-07-10T10:00:00Z","lines":["l1","l2 password=[REDACTED]","l3"]}]}`
	rr = do(h, http.MethodPost, "/report", globalKey, tailReport)
	if rr.Code != http.StatusOK {
		t.Fatalf("tail report POST = %d", rr.Code)
	}
	tails, err := st.GetCustomerLogTails("cust-a")
	if err != nil || len(tails) != 1 {
		t.Fatalf("stored tails = %d/%v, want 1", len(tails), err)
	}
	if len(tails[0].Lines) != 3 || tails[0].Lines[1] != "l2 password=[REDACTED]" {
		t.Fatalf("tail lines not stored ordered/verbatim: %#v", tails[0].Lines)
	}

	// 4. consume-once: the FOLLOWING ACK omits the field
	rr = do(h, http.MethodPost, "/report", globalKey, reportBody)
	ack = map[string]json.RawMessage{}
	json.Unmarshal(rr.Body.Bytes(), &ack)
	if _, present := ack["log_tail_requests"]; present {
		t.Fatalf("request survived fulfillment — the controller would ship tails every cycle: %s", rr.Body.String())
	}
}

// A report whose telemetry issues carry context lands it in app_log_issues (the C wire leg).
func TestReport_IssueContextStored(t *testing.T) {
	h, st, _ := newTestHandler(t)

	body := `{"customer_id":"cust-a","app_telemetry":[{"app_name":"cwa","issues":[
		{"severity":"error","message":"ERROR: nfs gone","count":2,"last_seen":"2026-07-10T09:00:00Z",
		 "context":["before","ERROR: nfs gone","after"]}]}]}`
	rr := do(h, http.MethodPost, "/report", globalKey, body)
	if rr.Code != http.StatusOK {
		t.Fatalf("report POST = %d (%s)", rr.Code, rr.Body.String())
	}
	issues, err := st.GetAppIssues("cwa", longAgoAPI, false, 10)
	if err != nil || len(issues) != 1 {
		t.Fatalf("issues = %d/%v, want 1", len(issues), err)
	}
	is := issues[0]
	if len(is.Context) != 3 || is.Context[1] != "ERROR: nfs gone" {
		t.Fatalf("context not stored from the wire: %#v", is.Context)
	}
	if is.ContextCustomer != "cust-a" {
		t.Fatalf("provenance = %q, want cust-a", is.ContextCustomer)
	}
}
