package ollama

import (
	"context"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"fiko-ai/internal/llm"
)

func writeLine(w http.ResponseWriter, s string) {
	_, _ = w.Write([]byte(s + "\n"))
	if f, ok := w.(http.Flusher); ok {
		f.Flush()
	}
}

func userReq(text string) llm.Request {
	return llm.Request{Messages: []llm.Message{{Role: llm.RoleUser, Content: text}}}
}

func TestGenerateStream_Success(t *testing.T) {
	var got chatRequest
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/api/chat" || r.Method != http.MethodPost {
			t.Errorf("beklenmeyen istek: %s %s", r.Method, r.URL.Path)
		}
		_ = json.NewDecoder(r.Body).Decode(&got)
		writeLine(w, `{"message":{"role":"assistant","content":"Mer"},"done":false}`)
		writeLine(w, `{"message":{"role":"assistant","content":"haba"},"done":false}`)
		writeLine(w, `{"model":"m1","message":{"role":"assistant","content":""},"done":true,"done_reason":"stop","prompt_eval_count":10,"eval_count":3}`)
	}))
	defer srv.Close()

	c := New(srv.URL, "m1", 5*time.Second)
	temp := 0.3
	req := userReq("selam")
	req.Options = llm.Options{Temperature: &temp, MaxTokens: 128, ContextSize: 4096}

	var deltas []string
	res, err := c.GenerateStream(context.Background(), req, func(d string) error {
		deltas = append(deltas, d)
		return nil
	})
	if err != nil {
		t.Fatalf("hata: %v", err)
	}
	if res.Content != "Merhaba" || len(deltas) != 2 {
		t.Fatalf("içerik=%q deltas=%v", res.Content, deltas)
	}
	if res.PromptTokens != 10 || res.CompletionTokens != 3 || res.DoneReason != "stop" || res.Model != "m1" {
		t.Fatalf("sonuç meta hatalı: %+v", res)
	}
	if got.Model != "m1" || !got.Stream || len(got.Messages) != 1 {
		t.Fatalf("istek gövdesi hatalı: %+v", got)
	}
	if got.Options["temperature"] != 0.3 || got.Options["num_predict"] != float64(128) || got.Options["num_ctx"] != float64(4096) {
		t.Fatalf("options hatalı: %+v", got.Options)
	}
}

func TestGenerateStream_CancelReturnsPartial(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		writeLine(w, `{"message":{"content":"Mer"},"done":false}`)
		<-r.Context().Done() // istemci kopana kadar bekle
	}))
	defer srv.Close()

	c := New(srv.URL, "m1", 5*time.Second)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	res, err := c.GenerateStream(ctx, userReq("selam"), func(string) error {
		cancel() // ilk parçadan sonra iptal
		return nil
	})
	if !errors.Is(err, context.Canceled) {
		t.Fatalf("context.Canceled bekleniyordu, gelen: %v", err)
	}
	if res.Content != "Mer" {
		t.Fatalf("kısmi içerik %q, beklenen %q", res.Content, "Mer")
	}
}

func TestGenerateStream_ErrorLine(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		writeLine(w, `{"message":{"content":"a"},"done":false}`)
		writeLine(w, `{"error":"model çöktü"}`)
	}))
	defer srv.Close()

	res, err := New(srv.URL, "m1", time.Second).Generate(context.Background(), userReq("x"))
	var pe *llm.ProviderError
	if !errors.As(err, &pe) || pe.Message != "model çöktü" {
		t.Fatalf("ProviderError bekleniyordu: %v", err)
	}
	if res.Content != "a" {
		t.Fatalf("kısmi içerik kaybolmamalı: %q", res.Content)
	}
}

func TestGenerateStream_HTTPError(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusNotFound)
		_, _ = w.Write([]byte(`{"error":"model 'x' not found"}`))
	}))
	defer srv.Close()

	_, err := New(srv.URL, "x", time.Second).Generate(context.Background(), userReq("x"))
	var pe *llm.ProviderError
	if !errors.As(err, &pe) || pe.StatusCode != 404 || pe.Message != "model 'x' not found" {
		t.Fatalf("beklenen 404 ProviderError, gelen: %v", err)
	}
}

func TestGenerateStream_UnexpectedEOF(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		writeLine(w, `{"message":{"content":"a"},"done":false}`) // done gelmeden biter
	}))
	defer srv.Close()

	res, err := New(srv.URL, "m1", time.Second).Generate(context.Background(), userReq("x"))
	if !errors.Is(err, io.ErrUnexpectedEOF) {
		t.Fatalf("ErrUnexpectedEOF bekleniyordu: %v", err)
	}
	if res.Content != "a" {
		t.Fatalf("kısmi içerik: %q", res.Content)
	}
}

func TestGenerateStream_CallbackError(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		writeLine(w, `{"message":{"content":"a"},"done":false}`)
		<-r.Context().Done()
	}))
	defer srv.Close()

	boom := errors.New("boom")
	_, err := New(srv.URL, "m1", time.Second).GenerateStream(context.Background(), userReq("x"),
		func(string) error { return boom })
	if !errors.Is(err, boom) {
		t.Fatalf("callback hatası aynen dönmeli: %v", err)
	}
}

func TestGenerateStream_Validation(t *testing.T) {
	c := New("http://127.0.0.1:1", "", time.Second)
	if _, err := c.Generate(context.Background(), userReq("x")); !errors.Is(err, llm.ErrNoModel) {
		t.Fatalf("ErrNoModel bekleniyordu: %v", err)
	}
	c = New("http://127.0.0.1:1", "m", time.Second)
	if _, err := c.Generate(context.Background(), llm.Request{}); !errors.Is(err, llm.ErrEmptyMessages) {
		t.Fatalf("ErrEmptyMessages bekleniyordu: %v", err)
	}
}

func TestListModels(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/api/tags" {
			t.Errorf("yol: %s", r.URL.Path)
		}
		_, _ = w.Write([]byte(`{"models":[{"name":"a:1b","size":123,"modified_at":"2026-09-01T10:00:00.123456789+03:00"},{"name":"cloud:x","size":0,"modified_at":""}]}`))
	}))
	defer srv.Close()

	ms, err := New(srv.URL, "", time.Second).ListModels(context.Background())
	if err != nil {
		t.Fatal(err)
	}
	if len(ms) != 2 || ms[0].Name != "a:1b" || ms[0].SizeBytes != 123 || ms[0].ModifiedAt.IsZero() || !ms[1].ModifiedAt.IsZero() {
		t.Fatalf("model listesi hatalı: %+v", ms)
	}
}
