package llm

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestCompletionsStreamStreamsReasoningDeltas(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/chat/completions" {
			t.Fatalf("unexpected path: %s", r.URL.Path)
		}
		w.Header().Set("Content-Type", "text/event-stream")
		for _, data := range []string{
			`{"choices":[{"index":0,"delta":{"role":"assistant"}}]}`,
			`{"choices":[{"index":0,"delta":{"reasoning_content":"thinking"}}]}`,
			`{"choices":[{"index":0,"delta":{"reasoning_content":" more"}}]}`,
			`{"choices":[{"index":0,"delta":{"content":"answer"}}]}`,
		} {
			fmt.Fprintf(w, "data: %s\n\n", data)
		}
		fmt.Fprint(w, "data: [DONE]\n\n")
	}))
	defer srv.Close()

	client, err := NewClient(Config{URL: srv.URL}, nil, nil)
	if err != nil {
		t.Fatalf("NewClient: %v", err)
	}

	events := make(chan StreamEvent)
	errCh := make(chan error, 1)
	go func() {
		errCh <- client.CompletionsStream(
			context.Background(), CompletionsParams{
				Model:         "test-model",
				SystemMessage: "",
				MaxIterations: 0,
				Messages: []Message{{
					Role: RoleUser,
					ContentParts: []ContentPart{{
						Type: ContentTypeText,
						Text: "hello",
					}},
				}},
			}, events,
		)
	}()

	var got []StreamEvent
	for evt := range events {
		got = append(got, evt)
	}
	if err := <-errCh; err != nil {
		t.Fatalf("CompletionsStream: %v", err)
	}

	wantTypes := []string{
		streamEventReasoningDelta,
		streamEventReasoningDelta,
		streamEventDelta,
		streamEventDone,
	}
	if len(got) != len(wantTypes) {
		t.Fatalf("got %d events, want %d: %#v", len(got), len(wantTypes), got)
	}
	for i, want := range wantTypes {
		if got[i].Type != want {
			t.Fatalf("event %d type = %q, want %q", i, got[i].Type, want)
		}
	}
	if got[0].ReasoningDelta != "thinking" {
		t.Errorf("first reasoning delta = %q", got[0].ReasoningDelta)
	}
	if got[1].ReasoningDelta != " more" {
		t.Errorf("second reasoning delta = %q", got[1].ReasoningDelta)
	}
	if got[2].Delta != "answer" {
		t.Errorf("content delta = %q", got[2].Delta)
	}
	final := got[3].Message
	if final.ReasoningContent != "thinking more" {
		t.Errorf("final reasoning = %q", final.ReasoningContent)
	}
	if final.Text() != "answer" {
		t.Errorf("final text = %q", final.Text())
	}
}

func TestCompletionsReturnsUsageAndTimings(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/chat/completions" {
			t.Fatalf("unexpected path: %s", r.URL.Path)
		}
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, `{
			"id": "test-123",
			"model": "test-model",
			"choices": [{
				"index": 0,
				"message": {"role": "assistant", "content": "hello"},
				"finish_reason": "stop"
			}],
			"usage": {
				"prompt_tokens": 10,
				"completion_tokens": 5,
				"total_tokens": 15,
				"prompt_tokens_details": {"cached_tokens": 3},
				"completion_tokens_details": {"reasoning_tokens": 2}
			},
			"timings": {
				"prompt_n": 10,
				"prompt_ms": 50.5,
				"prompt_per_token_ms": 5.05,
				"prompt_per_second": 198.0,
				"predicted_n": 5,
				"predicted_ms": 100.0,
				"predicted_per_token_ms": 20.0,
				"predicted_per_second": 50.0
			}
		}`)
	}))
	defer srv.Close()

	client, err := NewClient(Config{URL: srv.URL}, nil, nil)
	if err != nil {
		t.Fatalf("NewClient: %v", err)
	}

	res, err := client.Completions(
		context.Background(), CompletionsParams{
			Model:         "test-model",
			SystemMessage: "",
			MaxIterations: 0,
			Messages: []Message{{
				Role: RoleUser,
				ContentParts: []ContentPart{{
					Type: ContentTypeText,
					Text: "hi",
				}},
			}},
		},
	)
	if err != nil {
		t.Fatalf("Completions: %v", err)
	}
	if len(res.Messages) != 1 {
		t.Fatalf("got %d messages, want 1", len(res.Messages))
	}
	if res.Usage == nil {
		t.Fatal("usage is nil")
	}
	if res.Usage.PromptTokens != 10 {
		t.Errorf("prompt_tokens = %d, want 10", res.Usage.PromptTokens)
	}
	if res.Usage.CompletionTokens != 5 {
		t.Errorf("completion_tokens = %d, want 5", res.Usage.CompletionTokens)
	}
	if res.Usage.TotalTokens != 15 {
		t.Errorf("total_tokens = %d, want 15", res.Usage.TotalTokens)
	}
	if res.Usage.PromptTokensDetails == nil || res.Usage.PromptTokensDetails.CachedTokens != 3 {
		t.Errorf("cached_tokens unexpected: %#v", res.Usage.PromptTokensDetails)
	}
	if res.Usage.CompletionTokensDetails == nil || res.Usage.CompletionTokensDetails.ReasoningTokens != 2 {
		t.Errorf("reasoning_tokens unexpected: %#v", res.Usage.CompletionTokensDetails)
	}
	if res.Timings == nil {
		t.Fatal("timings is nil")
	}
	if res.Timings.PromptMS != 50.5 {
		t.Errorf("prompt_ms = %f, want 50.5", res.Timings.PromptMS)
	}
	if res.Timings.PredictedMS != 100.0 {
		t.Errorf("predicted_ms = %f, want 100.0", res.Timings.PredictedMS)
	}
}

func TestCompletionsStreamEmitsUsageEvent(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/chat/completions" {
			t.Fatalf("unexpected path: %s", r.URL.Path)
		}
		w.Header().Set("Content-Type", "text/event-stream")
		// Stream some content
		fmt.Fprintf(w, "data: %s\n\n",
			`{"choices":[{"index":0,"delta":{"role":"assistant"}}]}`)
		fmt.Fprintf(w, "data: %s\n\n",
			`{"choices":[{"index":0,"delta":{"content":"hello"}}]}`)
		// Final chunk with usage
		fmt.Fprintf(w, "data: %s\n\n",
			`{"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":10,"completion_tokens":5,"total_tokens":15}}`)
		fmt.Fprint(w, "data: [DONE]\n\n")
	}))
	defer srv.Close()

	client, err := NewClient(Config{URL: srv.URL}, nil, nil)
	if err != nil {
		t.Fatalf("NewClient: %v", err)
	}

	events := make(chan StreamEvent)
	errCh := make(chan error, 1)
	go func() {
		errCh <- client.CompletionsStream(
			context.Background(), CompletionsParams{
				Model:         "test-model",
				SystemMessage: "",
				MaxIterations: 0,
				Messages: []Message{{
					Role: RoleUser,
					ContentParts: []ContentPart{{
						Type: ContentTypeText,
						Text: "hi",
					}},
				}},
			}, events,
		)
	}()

	var got []StreamEvent
	for evt := range events {
		got = append(got, evt)
	}
	if err := <-errCh; err != nil {
		t.Fatalf("CompletionsStream: %v", err)
	}

	// Expect: delta, done events (usage attached to done event)
	wantTypes := []string{
		streamEventDelta,
		streamEventDone,
	}
	if len(got) != len(wantTypes) {
		t.Fatalf("got %d events, want %d: %#v", len(got), len(wantTypes), got)
	}
	for i, want := range wantTypes {
		if got[i].Type != want {
			t.Fatalf("event %d type = %q, want %q", i, got[i].Type, want)
		}
	}
	// Verify usage is on the done event
	doneEvt := got[1]
	if doneEvt.Usage == nil {
		t.Fatal("done event has nil Usage")
	}
	if doneEvt.Usage.PromptTokens != 10 {
		t.Errorf("prompt_tokens = %d, want 10", doneEvt.Usage.PromptTokens)
	}
	if doneEvt.Usage.TotalTokens != 15 {
		t.Errorf("total_tokens = %d, want 15", doneEvt.Usage.TotalTokens)
	}
}
