package core

import (
	"os"
	"testing"
	"time"

	"github.com/patriceckhart/zot/packages/provider"
)

func TestSessionRoundTrip(t *testing.T) {
	dir := t.TempDir()
	os.Setenv("ZOT_HOME", dir)

	sess, err := NewSession(dir, "/tmp/project", "anthropic", "claude-sonnet-4-5", "test")
	if err != nil {
		t.Fatal(err)
	}
	msg := provider.Message{
		Role: provider.RoleUser,
		Content: []provider.Content{
			provider.TextBlock{Text: "hello"},
		},
		Time: time.Now().UTC(),
	}
	if err := sess.AppendMessage(msg); err != nil {
		t.Fatal(err)
	}
	if err := sess.Close(); err != nil {
		t.Fatal(err)
	}

	reopened, msgs, err := OpenSession(sess.Path)
	if err != nil {
		t.Fatal(err)
	}
	// OpenSession returns a live append writer; close it before t.TempDir
	// runs cleanup, otherwise windows refuses to delete the open file.
	t.Cleanup(func() { _ = reopened.Close() })
	if len(msgs) != 1 {
		t.Fatalf("got %d messages", len(msgs))
	}
	tb, ok := msgs[0].Content[0].(provider.TextBlock)
	if !ok || tb.Text != "hello" {
		t.Fatalf("got %+v", msgs[0])
	}
}

// TestNewSessionAtPathCreatesAtExplicitPath proves the swarm child's
// session-persistence fallback works: NewSessionAtPath creates the
// file (and parent dirs) at the exact path the caller chose,
// independent of SessionsDir(root, cwd). Without this helper, the
// swarm child's --session <path> with a non-existent path would
// fall through to NewSession and write the session under a
// different (autogenerated) name, leaving the supervisor-chosen
// path empty.
func TestNewSessionAtPathCreatesAtExplicitPath(t *testing.T) {
	dir := t.TempDir()
	want := dir + "/nested/sub/session.json"

	s, err := NewSessionAtPath(want, "/tmp/proj", "anthropic", "claude", "test")
	if err != nil {
		t.Fatalf("NewSessionAtPath: %v", err)
	}
	if s.Path != want {
		t.Errorf("Path = %q; want %q", s.Path, want)
	}
	if err := s.AppendMessage(provider.Message{
		Role:    provider.RoleUser,
		Content: []provider.Content{provider.TextBlock{Text: "hi"}},
		Time:    time.Now().UTC(),
	}); err != nil {
		t.Fatal(err)
	}
	if err := s.Close(); err != nil {
		t.Fatal(err)
	}

	// Reopen at the same path and the message must still be there.
	reopened, msgs, err := OpenSession(want)
	if err != nil {
		t.Fatalf("OpenSession at fixed path: %v", err)
	}
	t.Cleanup(func() { _ = reopened.Close() })
	if len(msgs) != 1 {
		t.Fatalf("reopen got %d msgs; want 1", len(msgs))
	}

	// A second call to NewSessionAtPath at the same path must fail
	// (O_EXCL): callers should use OpenSession to reattach to an
	// existing file.
	if _, err := NewSessionAtPath(want, "/tmp/proj", "anthropic", "claude", "test"); err == nil {
		t.Error("NewSessionAtPath on existing path returned nil; want error")
	}
}

func TestCostAdd(t *testing.T) {
	var c CostTracker
	c.Add(provider.Usage{InputTokens: 100, OutputTokens: 50, CostUSD: 0.01})
	c.Add(provider.Usage{InputTokens: 200, OutputTokens: 25, CostUSD: 0.02})
	if c.Total.InputTokens != 300 || c.Total.OutputTokens != 75 {
		t.Fatalf("got %+v", c.Total)
	}
	if c.Total.CostUSD < 0.0299 || c.Total.CostUSD > 0.0301 {
		t.Fatalf("got cost %v", c.Total.CostUSD)
	}
}
