package services

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

	"github.com/cloudwego/eino/components/tool"
	"github.com/cloudwego/eino/schema"
	"wa-assistant/backend/models"
)

type handoffTestTransport func(*http.Request) (*http.Response, error)

func (f handoffTestTransport) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }

// Exercise ChatResult, the native provider adapter and Eino's terminal tool path.
// The transport consumes all HTTP requests in memory; no provider or customer is contacted.
func TestContextualHandoffThroughAgentPipeline(t *testing.T) {
	const safeVerdict = `{"contextual":true,"safe":true,"transfer_clear":true}`
	for _, tc := range []struct {
		name, message, draft, verdict string
		history                       []models.ChatHistory
		wantDraft, skipReview         bool
	}{
		{"purchase", "Bisa beli di sini?", "Untuk pembelian lewat chat ini, saya teruskan ke tim untuk dikonfirmasi ya, Kak.", safeVerdict, nil, true, false},
		{"shipping_from_history", "Kalau ke sana berapa?", "Untuk ongkir ke Bandung, saya teruskan ke tim agar bisa dipastikan ya, Kak.", safeVerdict, []models.ChatHistory{{Message: "Saya tinggal di Bandung.", Reply: "Produknya sudah dipilih?"}}, true, false},
		{"negotiation", "Kalau beli dua bisa diskon?", "Soal diskon untuk pembelian dua unit, saya teruskan ke tim untuk dipertimbangkan ya, Kak.", safeVerdict, nil, true, false},
		{"unsupported_purchase", "Bisa beli di sini?", "Bisa dibeli di sini, Kak. Saya teruskan ke tim.", `{"contextual":true,"safe":false,"transfer_clear":true}`, nil, false, false},
		{"wrong_topic", "Bisa beli di sini?", "Saya teruskan kendala pengiriman ke tim ya, Kak.", `{"contextual":false,"safe":true,"transfer_clear":true}`, nil, false, false},
		{"unclear_transfer", "Bisa beli di sini?", "Saya cek dulu untuk pembelian lewat chat ya, Kak.", `{"contextual":true,"safe":true,"transfer_clear":false}`, nil, false, false},
		{"broken_review", "Bisa beli di sini?", "Untuk pembelian lewat chat ini, saya teruskan ke tim ya, Kak.", `{"safe":true}`, nil, false, false},
		{"missing_draft", "Bisa beli di sini?", "", "", nil, false, true},
		{"wait_promise", "Bisa beli di sini?", "Sebentar, tim akan segera memproses pembelian Kakak.", "", nil, false, true},
		{"internal_directive", "Bisa beli di sini?", "Saya teruskan ke tim. [[START_PRODUCT:1]]", "", nil, false, true},
	} {
		t.Run(tc.name, func(t *testing.T) {
			db := agenticTestDB(t)
			if err := db.AutoMigrate(&models.AppSetting{}, &models.Agent{}, &models.Product{}); err != nil {
				t.Fatal(err)
			}
			if err := db.Create(&models.AppSetting{Key: "ai_provider", Value: "deepseek"}).Error; err != nil {
				t.Fatal(err)
			}
			t.Setenv("DEEPSEEK_API_KEY", "fixture-handoff-key")
			t.Setenv("DEEPSEEK_MODEL", "deepseek-flash")
			calls := 0
			original := http.DefaultTransport
			t.Cleanup(func() { http.DefaultTransport = original })
			http.DefaultTransport = handoffTestTransport(func(req *http.Request) (*http.Response, error) {
				if req.URL.Host != "api.deepseek.com" || req.Method != http.MethodPost {
					return nil, fmt.Errorf("unexpected fixture request")
				}
				calls++
				var body struct {
					Messages  []struct{ Role, Content string } `json:"messages"`
					Tools     []json.RawMessage                `json:"tools"`
					Thinking  map[string]string                `json:"thinking"`
					MaxTokens int                              `json:"max_tokens"`
				}
				if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
					return nil, err
				}
				message := map[string]any{"role": "assistant", "content": ""}
				finish := "tool_calls"
				switch calls {
				case 1:
					if !strings.Contains(body.Messages[0].Content, "customer_message") {
						t.Error("agent prompt does not request a contextual handoff message")
					}
					message["tool_calls"] = []schema.ToolCall{{ID: "search", Type: "function", Function: schema.FunctionCall{Name: "search_knowledge", Arguments: `{"query":"pembelian ongkir diskon"}`}}}
				case 2:
					args, _ := json.Marshal(handoffToolInput{Reason: "insufficient_evidence", CustomerMessage: tc.draft})
					message["tool_calls"] = []schema.ToolCall{{ID: "handoff", Type: "function", Function: schema.FunctionCall{Name: "request_handoff", Arguments: string(args)}}}
				case 3:
					if tc.skipReview || len(body.Tools) != 0 || body.Thinking["type"] != "disabled" || body.MaxTokens > 320 {
						t.Error("handoff reviewer is not isolated and bounded")
					}
					var contextData map[string]string
					if err := json.Unmarshal([]byte(body.Messages[len(body.Messages)-1].Content), &contextData); err != nil || contextData["latest_customer_message"] != tc.message || contextData["proposed_message"] != tc.draft {
						t.Error("review did not receive the customer request and exact proposed wording")
					}
					if len(tc.history) > 0 && !strings.Contains(contextData["recent_conversation"], "Bandung") {
						t.Error("review lost the topic from earlier conversation")
					}
					message["content"], finish = tc.verdict, "stop"
				default:
					return nil, errors.New("handoff unexpectedly made another provider call")
				}
				raw, _ := json.Marshal(map[string]any{"id": fmt.Sprint(calls), "choices": []any{map[string]any{"index": 0, "finish_reason": finish, "message": message}}})
				return &http.Response{StatusCode: 200, Header: http.Header{"Content-Type": []string{"application/json"}}, Body: io.NopCloser(strings.NewReader(string(raw)))}, nil
			})
			agent := models.Agent{ID: 1, AgenticEnabled: true, ResponseLength: "short"}
			result, err := chatAgentic(agent, "Asisten toko yang ramah. Jangan mengarang informasi.", "ramah", tc.message+"\n[KONTEKS LINK: judul halaman tambahan]", tc.history, ChatOptions{DryRun: true, LatestCustomerMessage: tc.message})
			if err != nil || !result.Escalate || result.HandoffReason != "insufficient_evidence" || result.Reply != "" {
				t.Fatalf("handoff result was lost or became a business answer: %+v err=%v", result, err)
			}
			want := DefaultHandoffReply
			if tc.wantDraft {
				want = tc.draft
			}
			if result.HandoffMessage() != want {
				t.Fatalf("customer notice=%q, want %q", result.HandoffMessage(), want)
			}
			wantCalls := 3
			if tc.skipReview {
				wantCalls = 2
			}
			if calls != wantCalls {
				t.Fatalf("provider calls=%d, want %d", calls, wantCalls)
			}
		})
	}
}

func TestHandoffReviewFailureKeepsEscalationAndDoesNotLoop(t *testing.T) {
	for _, tc := range []struct {
		name   string
		review handoffReviewFunc
	}{
		{"unavailable", nil},
		{"provider_error", func(context.Context, string, string, string, string) (bool, error) {
			return false, errors.New("fixture-private-error")
		}},
		{"deadline", func(ctx context.Context, _, _, _, _ string) (bool, error) { <-ctx.Done(); return false, ctx.Err() }},
	} {
		t.Run(tc.name, func(t *testing.T) {
			state := &agentTurnState{calls: 6, knowledgeLoadIssue: "knowledge_unavailable", reviewHandoff: tc.review}
			base, err := makeHandoffTool(state)
			if err != nil {
				t.Fatal(err)
			}
			ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
			defer cancel()
			raw, err := base.(tool.InvokableTool).InvokableRun(ctx, `{"reason":"insufficient_evidence","customer_message":"Saya teruskan pertanyaan pembelian ini ke tim ya, Kak."}`)
			if err != nil || !strings.Contains(raw, "[[ESCALATE]]") || state.handoffReason != "knowledge_unavailable" || state.handoffReply != "" {
				t.Fatalf("wording failure changed handoff: reply=%q reason=%s err=%v", state.handoffReply, state.handoffReason, err)
			}
			if strings.Contains(raw, "fixture-private") {
				t.Fatal("internal review error leaked")
			}
			if state.calls != 7 || !state.handoffMessageChecked {
				t.Fatal("handoff was blocked by the data budget")
			}
		})
	}
}

func TestHandoffRepeatedToolDoesNotReplaceCheckedNotice(t *testing.T) {
	checks := 0
	state := &agentTurnState{reviewHandoff: func(context.Context, string, string, string, string) (bool, error) { checks++; return true, nil }}
	base, err := makeHandoffTool(state)
	if err != nil {
		t.Fatal(err)
	}
	for _, draft := range []string{"Saya teruskan pertanyaan pembelian ke tim ya, Kak.", "Saya teruskan hal lainnya ke CS."} {
		raw, _ := json.Marshal(handoffToolInput{Reason: "customer_request", CustomerMessage: draft})
		if _, err := base.(tool.InvokableTool).InvokableRun(context.Background(), string(raw)); err != nil {
			t.Fatal(err)
		}
	}
	if checks != 1 || state.handoffReply != "Saya teruskan pertanyaan pembelian ke tim ya, Kak." {
		t.Fatal("duplicate tool call replaced the original notice or spent another review")
	}
}
