package output

import (
	"encoding/json"
	"strings"
	"testing"

	"github.com/pranshuparmar/witr/pkg/model"
)

func jsonFixture() model.Result {
	target := model.Process{
		PID:     1234,
		Command: "nginx",
		Cmdline: "/usr/sbin/nginx -g daemon off;",
		Env:     []string{"FOO=bar", "PATH=/usr/bin"},
	}
	return model.Result{
		Target:   model.Target{Type: model.TargetName, Value: "nginx"},
		Process:  target,
		Ancestry: []model.Process{{PID: 1, Command: "systemd"}, target},
		Children: []model.Process{{PID: 5678, Command: "worker"}},
		Source:   model.Source{Type: model.SourceSystemd, Name: "nginx.service"},
		Warnings: []string{"Process is listening on a public interface"},
	}
}

// TestToJSONIsValidAndIncludesKeyFields verifies the full result serializes
// cleanly and includes the top-level fields downstream consumers depend on.
func TestToJSONIsValidAndIncludesKeyFields(t *testing.T) {
	t.Parallel()

	s, err := ToJSON(jsonFixture())
	if err != nil {
		t.Fatalf("ToJSON returned error: %v", err)
	}

	var decoded map[string]any
	if err := json.Unmarshal([]byte(s), &decoded); err != nil {
		t.Fatalf("ToJSON output is not valid JSON: %v\n%s", err, s)
	}

	for _, key := range []string{"Target", "Process", "Ancestry", "Source", "Warnings"} {
		if _, ok := decoded[key]; !ok {
			t.Errorf("ToJSON output missing top-level field %q", key)
		}
	}
}

func TestToShortJSON(t *testing.T) {
	t.Parallel()

	s, err := ToShortJSON(jsonFixture())
	if err != nil {
		t.Fatalf("ToShortJSON: %v", err)
	}

	var got []struct {
		PID     int
		Command string
	}
	if err := json.Unmarshal([]byte(s), &got); err != nil {
		t.Fatalf("ToShortJSON not parseable as []shortProcess: %v\n%s", err, s)
	}
	if len(got) != 2 {
		t.Fatalf("ToShortJSON length = %d, want 2", len(got))
	}
	if got[0].Command != "systemd" || got[1].Command != "nginx" {
		t.Errorf("ToShortJSON unexpected ancestry: %+v", got)
	}
}

func TestToTreeJSONIncludesChildren(t *testing.T) {
	t.Parallel()

	s, err := ToTreeJSON(jsonFixture())
	if err != nil {
		t.Fatalf("ToTreeJSON: %v", err)
	}

	var got struct {
		Ancestry []struct{ PID int }
		Children []struct {
			PID     int
			Command string
		}
	}
	if err := json.Unmarshal([]byte(s), &got); err != nil {
		t.Fatalf("ToTreeJSON not parseable: %v\n%s", err, s)
	}
	if len(got.Children) != 1 || got.Children[0].PID != 5678 || got.Children[0].Command != "worker" {
		t.Errorf("ToTreeJSON children = %+v, want one worker pid 5678", got.Children)
	}
}

// TestToTreeJSONOmitsEmptyChildren pins the `omitempty` contract — callers
// scripting against the JSON shouldn't need to special-case "Children":[].
func TestToTreeJSONOmitsEmptyChildren(t *testing.T) {
	t.Parallel()

	fx := jsonFixture()
	fx.Children = nil

	s, err := ToTreeJSON(fx)
	if err != nil {
		t.Fatalf("ToTreeJSON: %v", err)
	}
	if strings.Contains(s, `"Children"`) {
		t.Errorf("ToTreeJSON should omit Children when empty; got:\n%s", s)
	}
}

func TestToWarningsJSON(t *testing.T) {
	t.Parallel()

	s, err := ToWarningsJSON(jsonFixture())
	if err != nil {
		t.Fatalf("ToWarningsJSON: %v", err)
	}

	var got struct {
		PID      int
		Process  string
		Command  string
		Warnings []string
	}
	if err := json.Unmarshal([]byte(s), &got); err != nil {
		t.Fatalf("ToWarningsJSON not parseable: %v\n%s", err, s)
	}
	if got.PID != 1234 || got.Process != "nginx" {
		t.Errorf("ToWarningsJSON identity wrong: %+v", got)
	}
	if len(got.Warnings) != 1 || !strings.Contains(got.Warnings[0], "public interface") {
		t.Errorf("ToWarningsJSON warnings = %v", got.Warnings)
	}
}

// TestToWarningsJSONNilWarningsBecomesEmptyArray ensures downstream
// consumers always see [] and never null — important for jq scripts that do
// `.Warnings | length`.
func TestToWarningsJSONNilWarningsBecomesEmptyArray(t *testing.T) {
	t.Parallel()

	fx := jsonFixture()
	fx.Warnings = nil

	s, err := ToWarningsJSON(fx)
	if err != nil {
		t.Fatalf("ToWarningsJSON: %v", err)
	}
	if !strings.Contains(s, `"Warnings": []`) {
		t.Errorf("ToWarningsJSON should emit Warnings:[] when nil; got:\n%s", s)
	}
}

func TestToEnvJSON(t *testing.T) {
	t.Parallel()

	s, err := ToEnvJSON(jsonFixture())
	if err != nil {
		t.Fatalf("ToEnvJSON: %v", err)
	}

	var got struct {
		PID     int
		Process string
		Command string
		Env     []string
	}
	if err := json.Unmarshal([]byte(s), &got); err != nil {
		t.Fatalf("ToEnvJSON not parseable: %v\n%s", err, s)
	}
	if got.PID != 1234 {
		t.Errorf("ToEnvJSON PID = %d, want 1234", got.PID)
	}
	if len(got.Env) != 2 || got.Env[0] != "FOO=bar" {
		t.Errorf("ToEnvJSON env = %v", got.Env)
	}
}
