// e2eprobe is a temporary typed IPC driver used by the 2026-08-15 isolated
// whole-Maven acceptance session. It is removed after the session; keeping the
// driver inside the module lets it import Maven's internal IPC contract rather
// than peeking into sqlite.
package main

import (
	"context"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"math"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/kami/maven/internal/ipc"
	"github.com/kami/maven/internal/router"
	"github.com/kami/maven/internal/store"
)

func main() {
	if err := run(os.Args[1:]); err != nil {
		fmt.Fprintln(os.Stderr, "e2eprobe:", err)
		os.Exit(1)
	}
}

func run(args []string) error {
	fs := flag.NewFlagSet("e2eprobe", flag.ContinueOnError)
	sock := fs.String("sock", "", "mavend unix socket")
	if err := fs.Parse(args); err != nil {
		return err
	}
	argv := fs.Args()
	if len(argv) == 0 {
		return errors.New("usage: e2eprobe -sock PATH COMMAND [ARGS]")
	}
	ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
	defer cancel()
	if argv[0] == "score-pair" {
		out, err := scorePair(ctx, argv)
		if err != nil {
			return err
		}
		return encode(out)
	}
	if argv[0] == "parse-task-status" {
		if len(argv) != 2 {
			return errors.New("parse-task-status needs TEXT")
		}
		parsed, ok := router.ParseTaskStatus(argv[1])
		return encode(map[string]any{"accepted": ok, "parsed": parsed})
	}
	if *sock == "" {
		return errors.New("usage: e2eprobe -sock PATH COMMAND [ARGS]")
	}

	cli, err := ipc.DialWait(*sock, 15*time.Second)
	if err != nil {
		return err
	}
	defer cli.Close()

	var out any
	switch argv[0] {
	case "ping":
		out, err = cli.Ping(ctx)
	case "chat":
		if len(argv) < 3 {
			return errors.New("chat needs CONVERSATION TEXT")
		}
		out, err = cli.Chat(ctx, argv[1], strings.Join(argv[2:], " "))
	case "create-reminder":
		if len(argv) < 3 || len(argv) > 4 {
			return errors.New("create-reminder needs RFC3339 TEXT [CRON]")
		}
		fire, parseErr := time.Parse(time.RFC3339, argv[1])
		if parseErr != nil {
			return parseErr
		}
		cron := ""
		if len(argv) == 4 {
			cron = argv[3]
		}
		id, createErr := cli.CreateReminder(ctx, fire, `{"text":`+quote(argv[2])+`}`, cron)
		out, err = map[string]any{"id": id}, createErr
	case "cancel-reminder":
		id, parseErr := oneID(argv)
		if parseErr != nil {
			return parseErr
		}
		err = cli.CancelReminder(ctx, id)
		out = map[string]any{"cancelled": id}
	case "mark-reminder":
		if len(argv) != 3 {
			return errors.New("mark-reminder needs ID STATUS")
		}
		id, parseErr := strconv.ParseInt(argv[1], 10, 64)
		if parseErr != nil {
			return parseErr
		}
		err = cli.MarkReminder(ctx, id, argv[2])
		out = map[string]any{"marked": id, "status": argv[2]}
	case "reminders":
		n, parseErr := optionalN(argv, 200)
		if parseErr != nil {
			return parseErr
		}
		out, err = cli.ListReminders(ctx, n)
	case "pending-reminders":
		n, parseErr := optionalN(argv, 0)
		if parseErr != nil {
			return parseErr
		}
		out, err = cli.ListPendingReminders(ctx, n)
	case "create-task":
		if len(argv) != 2 {
			return errors.New("create-task needs TEXT")
		}
		out, err = cli.CaptureTask(ctx, ipc.CaptureTaskReq{
			Text: argv[1], Source: "tap:web", Status: store.TaskOpen, Ts: time.Now(),
		})
	case "tasks":
		status := "live"
		if len(argv) == 2 {
			status = argv[1]
		} else if len(argv) != 1 {
			return errors.New("tasks takes optional STATUS")
		}
		out, err = cli.ListTasks(ctx, status)
	case "notes":
		n, parseErr := optionalN(argv, 50)
		if parseErr != nil {
			return parseErr
		}
		out, err = cli.RecentNotes(ctx, n)
	case "query-notes":
		if len(argv) != 2 {
			return errors.New("query-notes needs TEXT")
		}
		embedder, embedErr := router.NewONNXEmbedder(
			"models/embedder/multilingual-e5-small/model_quantized.onnx",
			"models/embedder/multilingual-e5-small/tokenizer.json",
			"deps/onnxruntime-linux-x64-1.26.0/lib/libonnxruntime.so",
		)
		if embedErr != nil {
			return embedErr
		}
		defer embedder.Close()
		vec, embedErr := router.EmbedQuery(ctx, embedder, argv[1])
		if embedErr != nil {
			return embedErr
		}
		out, err = cli.QueryNotes(ctx, vec, 10)
	case "score-pair":
		out, err = scorePair(ctx, argv)
	case "facts":
		n, parseErr := optionalN(argv, 50)
		if parseErr != nil {
			return parseErr
		}
		out, err = cli.RecentFacts(ctx, n)
	case "decisions":
		n, parseErr := optionalN(argv, 50)
		if parseErr != nil {
			return parseErr
		}
		out, err = cli.TurnDecisions(ctx, n)
	case "events":
		n, parseErr := optionalN(argv, 50)
		if parseErr != nil {
			return parseErr
		}
		out, err = cli.RecentEvents(ctx, n)
	case "eco-traces":
		n, parseErr := optionalN(argv, 50)
		if parseErr != nil {
			return parseErr
		}
		out, err = cli.RecentEcosystemTraces(ctx, n)
	case "delivery-attempts":
		status := ""
		if len(argv) == 2 {
			status = argv[1]
		} else if len(argv) != 1 {
			return errors.New("delivery-attempts takes optional STATUS")
		}
		out, err = cli.DeliveryAttempts(ctx, status, 200)
	case "nudges":
		n, parseErr := optionalN(argv, 50)
		if parseErr != nil {
			return parseErr
		}
		out, err = cli.RecentNudges(ctx, n)
	case "tools":
		status := ""
		if len(argv) == 2 {
			status = argv[1]
		} else if len(argv) != 1 {
			return errors.New("tools takes optional STATUS")
		}
		out, err = cli.ListTools(ctx, status)
	case "plan":
		out, err = cli.DayPlan(ctx)
	case "correct":
		if len(argv) != 3 {
			return errors.New("correct needs TRACE_ID SHOULD_BE")
		}
		id, parseErr := strconv.ParseInt(argv[1], 10, 64)
		if parseErr != nil {
			return parseErr
		}
		err = cli.CorrectTurn(ctx, id, argv[2])
		out = map[string]any{"corrected": id, "should_be": argv[2]}
	default:
		return fmt.Errorf("unknown command %q", argv[0])
	}
	if err != nil {
		return err
	}
	return encode(out)
}

func encode(out any) error {
	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")
	return enc.Encode(out)
}

func scorePair(ctx context.Context, argv []string) (any, error) {
	if len(argv) != 3 {
		return nil, errors.New("score-pair needs QUERY PASSAGE")
	}
	embedder, err := router.NewONNXEmbedder(
		"models/embedder/multilingual-e5-small/model_quantized.onnx",
		"models/embedder/multilingual-e5-small/tokenizer.json",
		"deps/onnxruntime-linux-x64-1.26.0/lib/libonnxruntime.so",
	)
	if err != nil {
		return nil, err
	}
	defer embedder.Close()
	qvec, err := router.EmbedQuery(ctx, embedder, argv[1])
	if err != nil {
		return nil, err
	}
	pvec, err := router.EmbedPassage(ctx, embedder, argv[2])
	if err != nil {
		return nil, err
	}
	if len(qvec) != len(pvec) {
		return nil, fmt.Errorf("embedding widths differ: %d != %d", len(qvec), len(pvec))
	}
	var dot float64
	for i := range qvec {
		dot += float64(qvec[i]) * float64(pvec[i])
	}
	return map[string]any{"score": math.Round(dot*1e9) / 1e9}, nil
}

func quote(s string) string {
	b, _ := json.Marshal(s)
	return string(b)
}

func oneID(argv []string) (int64, error) {
	if len(argv) != 2 {
		return 0, errors.New("command needs ID")
	}
	return strconv.ParseInt(argv[1], 10, 64)
}

func optionalN(argv []string, fallback int) (int, error) {
	if len(argv) == 1 {
		return fallback, nil
	}
	if len(argv) != 2 {
		return 0, errors.New("command takes optional N")
	}
	return strconv.Atoi(argv[1])
}
