//
//  Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

package component

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

	einotool "github.com/cloudwego/eino/components/tool"

	agentcanvas "ragflow/internal/agent/canvas"
)

type fakeWencaiInvoker struct {
	args  map[string]any
	calls int
	out   string
	err   error
}

func (f *fakeWencaiInvoker) InvokableRun(_ context.Context, argsJSON string, _ ...einotool.Option) (string, error) {
	f.calls++
	if err := json.Unmarshal([]byte(argsJSON), &f.args); err != nil {
		return "", err
	}
	if f.out == "" && f.err == nil {
		return `{"report":""}`, nil
	}
	return f.out, f.err
}

func TestWencai_RegisteredFactoryMatchesPythonSurface(t *testing.T) {
	t.Parallel()

	c, err := New("WenCai", map[string]any{
		"top_n":      float64(20),
		"query_type": "stock",
		"outputs":    map[string]any{"report": map[string]any{}},
		"setups":     map[string]any{"query": "configured query"},
	})
	if err != nil {
		t.Fatalf("New(WenCai): %v", err)
	}
	if got := c.Name(); got != "WenCai" {
		t.Fatalf("Name() = %q, want WenCai", got)
	}
	if _, ok := c.Inputs()["query"]; !ok {
		t.Fatal("Inputs() missing query")
	}
	if _, ok := c.Outputs()["report"]; !ok {
		t.Fatal("Outputs() missing report")
	}
	formGetter, ok := c.(interface{ GetInputForm() map[string]any })
	if !ok {
		t.Fatal("WenCai component does not expose GetInputForm")
	}
	query, ok := formGetter.GetInputForm()["query"].(map[string]any)
	if !ok {
		t.Fatalf("query form has type %T, want map", formGetter.GetInputForm()["query"])
	}
	if query["name"] != "Query" || query["type"] != "line" {
		t.Fatalf("query form = %#v, want name=Query type=line", query)
	}
}

func TestWencai_InvokeReturnsEmptyReportWithoutError(t *testing.T) {
	t.Parallel()

	fake := &fakeWencaiInvoker{}
	c := newWencaiComponentWithInvoker(fake)
	out, err := c.Invoke(context.Background(), map[string]any{
		"query":      "商业航天",
		"top_n":      20,
		"query_type": "stock",
	})
	if err != nil {
		t.Fatalf("Invoke: %v", err)
	}
	if got := out["report"]; got != "" {
		t.Fatalf("report = %v, want empty string", got)
	}
	if fake.calls != 1 {
		t.Fatalf("calls = %d, want 1", fake.calls)
	}
	if got := fake.args["query"]; got != "商业航天" {
		t.Fatalf("runtime query = %v, want 商业航天", got)
	}
	if len(fake.args) != 1 {
		t.Fatalf("runtime args = %#v, want query only", fake.args)
	}
}

func TestWencai_InvokeEmptyQuerySkipsTool(t *testing.T) {
	t.Parallel()

	fake := &fakeWencaiInvoker{}
	c := newWencaiComponentWithInvoker(fake)
	out, err := c.Invoke(context.Background(), map[string]any{"query": ""})
	if err != nil {
		t.Fatalf("Invoke: %v", err)
	}
	if got := out["report"]; got != "" {
		t.Fatalf("report = %v, want empty string", got)
	}
	if fake.calls != 0 {
		t.Fatalf("calls = %d, want 0", fake.calls)
	}
}

func TestWencai_InvokePreservesErrorEnvelope(t *testing.T) {
	t.Parallel()

	fake := &fakeWencaiInvoker{
		out: `{"report":"","_ERROR":"upstream failed"}`,
		err: errors.New("upstream failed"),
	}
	c := newWencaiComponentWithInvoker(fake)
	out, err := c.Invoke(context.Background(), map[string]any{"query": "商业航天"})
	if err != nil {
		t.Fatalf("Invoke: %v", err)
	}
	if got := out["report"]; got != "" {
		t.Fatalf("report = %v, want empty string", got)
	}
	if got := out["_ERROR"]; got != "upstream failed" {
		t.Fatalf("_ERROR = %v, want upstream failed", got)
	}
}

func TestWencai_InvokePreservesErrorEnvelopeWithoutGoError(t *testing.T) {
	t.Parallel()

	fake := &fakeWencaiInvoker{
		out: `{"report":"","_ERROR":"business error"}`,
	}
	c := newWencaiComponentWithInvoker(fake)
	out, err := c.Invoke(context.Background(), map[string]any{"query": "商业航天"})
	if err != nil {
		t.Fatalf("Invoke: %v", err)
	}
	if got := out["report"]; got != "" {
		t.Fatalf("report = %v, want empty string", got)
	}
	if got := out["_ERROR"]; got != "business error" {
		t.Fatalf("_ERROR = %v, want business error", got)
	}
}

func TestWencai_BuildWorkflowUsesPythonComponentName(t *testing.T) {
	c := &agentcanvas.Canvas{
		Components: map[string]agentcanvas.CanvasComponent{
			"begin_0": {
				Obj:        agentcanvas.CanvasComponentObj{ComponentName: "Begin", Params: map[string]any{}},
				Downstream: []string{"wencai_0"},
			},
			"wencai_0": {
				Obj: agentcanvas.CanvasComponentObj{ComponentName: "WenCai", Params: map[string]any{
					"top_n":      float64(20),
					"query_type": "stock",
				}},
				Upstream:   []string{"begin_0"},
				Downstream: []string{"message_0"},
			},
			"message_0": {
				Obj:      agentcanvas.CanvasComponentObj{ComponentName: "Message", Params: map[string]any{}},
				Upstream: []string{"wencai_0"},
			},
		},
		Path: []string{"begin_0", "wencai_0", "message_0"},
	}
	if _, err := agentcanvas.BuildWorkflow(context.Background(), c); err != nil {
		t.Fatalf("BuildWorkflow with WenCai: %v", err)
	}
}
