package services

import (
	"context"
	"encoding/json"
	"errors"
	"strings"
	"testing"

	"github.com/cloudwego/eino/components/model"
	"github.com/cloudwego/eino/schema"
)

type knowledgeTrainingModel struct {
	calls   *[]string
	invalid bool
	plain   bool
}

func (m *knowledgeTrainingModel) WithTools([]*schema.ToolInfo) (model.ToolCallingChatModel, error) {
	return m, nil
}
func (m *knowledgeTrainingModel) Stream(context.Context, []*schema.Message, ...model.Option) (*schema.StreamReader[*schema.Message], error) {
	return nil, errors.New("unused")
}
func (m *knowledgeTrainingModel) Generate(_ context.Context, messages []*schema.Message, _ ...model.Option) (*schema.Message, error) {
	if len(messages) == 2 && messages[0].Content == knowledgeEvidenceReviewPrompt {
		*m.calls = append(*m.calls, "independent_review")
		return schema.AssistantMessage(`{"approved":true,"issues":[]}`, nil), nil
	}
	if m.plain {
		return schema.AssistantMessage(`[{"question":"Jam buka?","answer":"Buka setiap hari."}]`, nil), nil
	}
	last := messages[len(messages)-1]
	if last.Role != schema.Tool {
		*m.calls = append(*m.calls, "read_source")
		return schema.AssistantMessage("", []schema.ToolCall{{ID: "read", Type: "function", Function: schema.FunctionCall{Name: "read_source", Arguments: `{}`}}}), nil
	}
	if strings.Contains(last.Content, `"accepted":1`) {
		return schema.AssistantMessage("Selesai diperiksa.", nil), nil
	}
	evidence := "Toko buka Senin sampai Jumat pukul 08.00 hingga 17.00."
	if m.invalid || !strings.Contains(last.Content, "rejected") {
		evidence = "Toko buka setiap hari pukul 08.00 hingga 17.00."
	}
	raw, _ := json.Marshal(map[string]any{"items": []map[string]string{{"question": "Kapan toko buka?", "answer": "Toko buka Senin sampai Jumat pukul 08.00 hingga 17.00.", "tags": "jam,operasional", "evidence": evidence}}})
	*m.calls = append(*m.calls, "check_facts")
	return schema.AssistantMessage("", []schema.ToolCall{{ID: "check", Type: "function", Function: schema.FunctionCall{Name: "check_facts", Arguments: string(raw)}}}), nil
}

func TestKnowledgeTrainingAgentCorrectsRejectedEvidence(t *testing.T) {
	var calls []string
	source := "Toko buka Senin sampai Jumat pukul 08.00 hingga 17.00."
	got, err := generateKnowledgeWithModel(context.Background(), &knowledgeTrainingModel{calls: &calls}, "Jam operasional", source)
	if err != nil || len(got) != 1 {
		t.Fatalf("hasil tidak tersusun: %v %+v", err, got)
	}
	if got[0].Evidence != source {
		t.Fatalf("kutipan sumber hilang: %+v", got[0])
	}
	if strings.Join(calls, ",") != "read_source,check_facts,check_facts,independent_review" {
		t.Fatalf("agent tidak memperbaiki fakta memakai tools: %v", calls)
	}
}

func TestKnowledgeTrainingDoesNotTreatFailedChecksAsEmptySuccess(t *testing.T) {
	for _, plain := range []bool{false, true} {
		var calls []string
		got, err := generateKnowledgeWithModel(context.Background(), &knowledgeTrainingModel{calls: &calls, invalid: true, plain: plain}, "Toko", "Toko buka Senin sampai Jumat pukul 08.00 hingga 17.00.")
		if err == nil || len(got) > 0 {
			t.Fatalf("jawaban tanpa pemeriksaan diterima: plain=%v err=%v got=%+v", plain, err, got)
		}
	}
}

func TestKnowledgeSourceChunksPreserveTailAndUnicode(t *testing.T) {
	source := strings.Repeat("Layanan pelanggan tersedia untuk pertanyaan produk. 🙂\n", 170) + "Retur khusus hanya diterima pada hari Sabtu."
	chunks, err := knowledgeSourceChunks(source)
	if err != nil || len(chunks) < 2 {
		t.Fatalf("sumber panjang tidak dibagi: %v", err)
	}
	if !strings.Contains(chunks[len(chunks)-1], "Retur khusus hanya diterima pada hari Sabtu.") {
		t.Fatal("fakta akhir sumber terpotong")
	}
	for _, chunk := range chunks {
		if len([]rune(chunk)) > 6000 || strings.ContainsRune(chunk, '\uFFFD') {
			t.Fatal("potongan merusak Unicode/batas input")
		}
	}
}
