package e2e_test

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"slices"
	"strings"
	"testing"
)

// ── JSON response types ───────────────────────────────────────────────────────

type NodeResponse struct {
	ID        string              `json:"id"`
	Title     string              `json:"title"`
	Content   string              `json:"content"`
	DueDate   string              `json:"due_date"`
	CreatedAt string              `json:"created_at"`
	UpdatedAt string              `json:"updated_at"`
	Tags      []string            `json:"tags"`
	Relations map[string][]string `json:"relations"`
}

func (n NodeResponse) HasTag(tag string) bool { return slices.Contains(n.Tags, tag) }
func (n NodeResponse) Property(key string) string {
	prefix := "_" + key + "::"
	for _, t := range n.Tags {
		if strings.HasPrefix(t, prefix) {
			return strings.TrimPrefix(t, prefix)
		}
	}
	return ""
}
func (n NodeResponse) HasRelation(relType, targetID string) bool {
	return slices.Contains(n.Relations[relType], targetID)
}

// ── Test binary ───────────────────────────────────────────────────────────────

var axBinary string

func TestMain(m *testing.M) {
	if err := exec.Command("go", "build", "-o", "ax", "..").Run(); err != nil {
		fmt.Fprintln(os.Stderr, "build failed:", err)
		os.Exit(1)
	}
	abs, err := filepath.Abs("./ax")
	if err != nil {
		fmt.Fprintln(os.Stderr, "resolve binary path:", err)
		os.Exit(1)
	}
	axBinary = abs
	code := m.Run()
	os.Remove("./ax")
	os.Exit(code)
}

// ── Test environment ──────────────────────────────────────────────────────────

type testEnv struct {
	t    *testing.T
	dir  string
	user string
}

func newTestEnv(t *testing.T, user string) *testEnv {
	t.Helper()
	env := &testEnv{t: t, dir: t.TempDir(), user: user}
	env.mustAx("init")
	return env
}

// withUser returns a new testEnv pointing at the same directory but with a different user.
func (e *testEnv) withUser(user string) *testEnv {
	return &testEnv{t: e.t, dir: e.dir, user: user}
}

func (e *testEnv) ax(args ...string) (string, error) {
	cmd := exec.Command(axBinary, args...)
	cmd.Dir = e.dir
	var env []string
	for _, v := range os.Environ() {
		if !strings.HasPrefix(v, "AX_USER=") {
			env = append(env, v)
		}
	}
	env = append(env, "AX_USER="+e.user)
	cmd.Env = env
	out, err := cmd.CombinedOutput()
	return string(out), err
}

func (e *testEnv) mustAx(args ...string) string {
	e.t.Helper()
	out, err := e.ax(args...)
	if err != nil {
		e.t.Fatalf("ax %v failed: %v\n%s", args, err, out)
	}
	return out
}

func (e *testEnv) parseNode(out string) NodeResponse {
	e.t.Helper()
	var n NodeResponse
	if err := json.Unmarshal([]byte(out), &n); err != nil {
		e.t.Fatalf("parse node JSON: %v\noutput: %s", err, out)
	}
	return n
}

func (e *testEnv) parseNodes(out string) []NodeResponse {
	e.t.Helper()
	if strings.TrimSpace(out) == "null" {
		return nil
	}
	var nodes []NodeResponse
	if err := json.Unmarshal([]byte(out), &nodes); err != nil {
		e.t.Fatalf("parse node list JSON: %v\noutput: %s", err, out)
	}
	return nodes
}

func (e *testEnv) findInList(nodes []NodeResponse, id string) (NodeResponse, bool) {
	for _, n := range nodes {
		if n.ID == id {
			return n, true
		}
	}
	return NodeResponse{}, false
}
