package workflow

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"cyberstrike-ai/internal/config"
)

func TestGenerateDraftFromNaturalLanguageHighRiskAddsHITLAndValidGraph(t *testing.T) {
	result, err := GenerateDraftFromNaturalLanguage(context.Background(), DraftRequest{
		Prompt: "对目标资产做端口扫描，如果发现高危端口就执行加固脚本，最后输出报告",
		Options: DraftOptions{
			IncludeObjective: true,
			AllowSchedule:    false,
			AllowHighRisk:    false,
		},
		AvailableTools: []DraftTool{{Key: "nmap", Name: "nmap", Enabled: true}},
	})
	if err != nil {
		t.Fatalf("GenerateDraftFromNaturalLanguage: %v", err)
	}
	raw, _ := json.Marshal(result.Graph)
	if err := ValidateGraphJSON(context.Background(), string(raw)); err != nil {
		t.Fatalf("generated graph should validate: %v\n%s", err, raw)
	}
	if !result.Audit.HighRisk || !result.Audit.NeedsHITL || len(result.Audit.RiskWarnings) == 0 {
		t.Fatalf("audit did not flag high-risk HITL path: %#v", result.Audit)
	}
	var hasTool, hasHITL, hasConfirmation bool
	for _, node := range result.Graph.Nodes {
		if node.Type == "tool" && cfgString(node.Config, "tool_name") == "nmap" {
			hasTool = true
		}
		if node.Type == "hitl" {
			hasHITL = true
		}
		if cfgString(node.Config, "requires_human_confirmation") == "true" {
			hasConfirmation = true
		}
	}
	if !hasTool || !hasHITL || !hasConfirmation {
		t.Fatalf("expected nmap tool, HITL, and confirmation marker; tool=%v hitl=%v confirmation=%v", hasTool, hasHITL, hasConfirmation)
	}
}

func TestGenerateDraftAllowHighRiskStillLabelsConditionBranch(t *testing.T) {
	result, err := GenerateDraftFromNaturalLanguage(context.Background(), DraftRequest{
		Prompt: "如果漏洞扫描发现高危漏洞，允许生成执行修复脚本的草稿并输出报告",
		Options: DraftOptions{
			AllowHighRisk: true,
		},
		AvailableTools: []DraftTool{{Key: "nuclei", Name: "nuclei", Enabled: true}},
	})
	if err != nil {
		t.Fatalf("GenerateDraftFromNaturalLanguage: %v", err)
	}
	raw, _ := json.Marshal(result.Graph)
	if err := ValidateGraphJSON(context.Background(), string(raw)); err != nil {
		t.Fatalf("generated graph should validate: %v\n%s", err, raw)
	}
	branches := map[string]bool{}
	for _, edge := range result.Graph.Edges {
		if branch := cfgString(edge.Config, "branch"); branch != "" {
			branches[branch] = true
		}
	}
	if !branches["true"] || !branches["false"] {
		t.Fatalf("condition branches = %#v, want true and false", branches)
	}
}

func TestGenerateDraftFromLLMUsesOpenAICompatibleEndpoint(t *testing.T) {
	called := false
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
		if r.URL.Path != "/chat/completions" {
			t.Fatalf("path = %s, want /chat/completions", r.URL.Path)
		}
		if got := r.Header.Get("Authorization"); got != "Bearer test-key" {
			t.Fatalf("authorization = %q", got)
		}
		var payload struct {
			Temperature    float64 `json:"temperature"`
			ResponseFormat struct {
				Type string `json:"type"`
			} `json:"response_format"`
			Messages []struct {
				Role    string `json:"role"`
				Content string `json:"content"`
			} `json:"messages"`
		}
		if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
			t.Fatalf("decode request: %v", err)
		}
		if payload.Temperature != 0 || payload.ResponseFormat.Type != "json_object" {
			t.Fatalf("unexpected structured output controls: temperature=%v response_format=%#v", payload.Temperature, payload.ResponseFormat)
		}
		if len(payload.Messages) == 0 || strings.Contains(payload.Messages[0].Content, "start|tool|agent") {
			t.Fatalf("system prompt still contains pipe enum: %q", payload.Messages[0].Content)
		}
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"{\"meta\":{\"id\":\"llm-port-scan\",\"name\":\"端口扫描\",\"description\":\"端口扫描\",\"enabled\":true},\"graph\":{\"nodes\":[{\"id\":\"start-1\",\"type\":\"start\",\"label\":\"开始\",\"position\":{\"x\":120,\"y\":150},\"config\":{\"input_keys\":\"message, target\"}},{\"id\":\"tool-2\",\"type\":\"tool\",\"label\":\"端口扫描\",\"position\":{\"x\":330,\"y\":150},\"config\":{\"tool_name\":\"nmap\",\"arguments\":\"{\\\"target\\\":\\\"{{inputs.target}}\\\"}\",\"timeout_seconds\":\"120\",\"join_strategy\":\"all_merge\"}},{\"id\":\"output-3\",\"type\":\"output\",\"label\":\"输出报告\",\"position\":{\"x\":540,\"y\":150},\"config\":{\"source_binding\":{\"from\":\"previous\",\"field\":\"output\"},\"join_strategy\":\"all_merge\"}}],\"edges\":[{\"id\":\"edge-1\",\"source\":\"start-1\",\"target\":\"tool-2\"},{\"id\":\"edge-2\",\"source\":\"tool-2\",\"target\":\"output-3\"}],\"config\":{\"schema_version\":1}},\"capabilities\":[{\"label\":\"端口扫描\",\"tool_name\":\"nmap\",\"tool_candidates\":[\"nmap\"]}],\"audit\":{\"assumptions\":[]}}"}}]}`))
	}))
	defer srv.Close()

	result, err := GenerateDraftFromLLM(context.Background(), DraftRequest{
		Prompt:         "对目标做端口扫描并输出报告",
		AvailableTools: []DraftTool{{Key: "nmap", Name: "nmap", Enabled: true}},
	}, config.OpenAIConfig{APIKey: "test-key", BaseURL: srv.URL, Model: "test-model"}, nil)
	if err != nil {
		t.Fatalf("GenerateDraftFromLLM: %v", err)
	}
	if !called {
		t.Fatal("expected LLM endpoint to be called")
	}
	if result.Generator != "llm" || !result.Audit.Savable || result.Meta.ID != "llm-port-scan" {
		t.Fatalf("unexpected result: %#v", result)
	}
	for _, node := range result.Graph.Nodes {
		if node.Type == "output" && cfgString(node.Config, "output_key") != "result" {
			t.Fatalf("output_key = %q, want result", cfgString(node.Config, "output_key"))
		}
	}
}

func TestGenerateDraftFromLLMReturnsErrorOnMalformedJSON(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"{\"meta\":{\"id\":\"bad\"},|\"graph\":{\"nodes\":[]}}"}}]}`))
	}))
	defer srv.Close()

	_, err := GenerateDraftFromLLM(context.Background(), DraftRequest{
		Prompt: "随便生成一个工作流，要求所有节点都用到输出变量",
		Options: DraftOptions{
			IncludeObjective: true,
		},
	}, config.OpenAIConfig{APIKey: "test-key", BaseURL: srv.URL, Model: "test-model"}, nil)
	if err == nil {
		t.Fatal("expected malformed JSON error")
	}
	if !strings.Contains(err.Error(), "解析大模型工作流 JSON 失败") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestNormalizeLLMDraftRepairsMissingRequiredConfig(t *testing.T) {
	result := normalizeLLMDraft("随便生成一个工作流", DraftRequest{}, llmDraftEnvelope{
		Graph: graphDef{
			Nodes: []graphNode{
				{ID: "start-1", Type: "start", Label: "开始", Config: map[string]any{}},
				{ID: "agent-1", Type: "agent", Label: "分析", Config: map[string]any{}},
				{ID: "out-1", Type: "output", Label: "输出结果", Config: map[string]any{}},
			},
			Edges: []graphEdge{
				{ID: "e1", Source: "start-1", Target: "agent-1"},
				{ID: "e2", Source: "agent-1", Target: "out-1"},
			},
		},
	})
	raw, _ := json.Marshal(result.Graph)
	if err := ValidateGraphJSON(context.Background(), string(raw)); err != nil {
		t.Fatalf("normalized graph should validate: %v\n%s", err, raw)
	}
	var agentKey, outputKey string
	for _, node := range result.Graph.Nodes {
		switch node.Type {
		case "agent":
			agentKey = cfgString(node.Config, "output_key")
		case "output":
			outputKey = cfgString(node.Config, "output_key")
		}
	}
	if agentKey == "" || outputKey != "result" {
		t.Fatalf("agentKey=%q outputKey=%q", agentKey, outputKey)
	}
}

func TestNormalizeLLMDraftRepairsConditionBranches(t *testing.T) {
	result := normalizeLLMDraft("如果发现异常则输出详情，否则输出正常", DraftRequest{}, llmDraftEnvelope{
		Graph: graphDef{
			Nodes: []graphNode{
				{ID: "start-1", Type: "start", Label: "开始", Config: map[string]any{}},
				{ID: "cond-1", Type: "condition", Label: "判断", Config: map[string]any{"expression": `{{inputs.message}} != ""`}},
				{ID: "out-yes", Type: "output", Label: "异常", Config: map[string]any{}},
				{ID: "out-no", Type: "output", Label: "正常", Config: map[string]any{}},
			},
			Edges: []graphEdge{
				{ID: "e1", Source: "start-1", Target: "cond-1"},
				{ID: "e2", Source: "cond-1", Target: "out-yes"},
				{ID: "e3", Source: "cond-1", Target: "out-no"},
			},
		},
	})
	raw, _ := json.Marshal(result.Graph)
	if err := ValidateGraphJSON(context.Background(), string(raw)); err != nil {
		t.Fatalf("normalized graph should validate: %v\n%s", err, raw)
	}
	branches := map[string]bool{}
	for _, edge := range result.Graph.Edges {
		if edge.Source == "cond-1" {
			branches[cfgString(edge.Config, "branch")] = true
		}
	}
	if !branches["true"] || !branches["false"] {
		t.Fatalf("branches = %#v, want true and false", branches)
	}
}
