package mcpserver

import (
	"context"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/modelcontextprotocol/go-sdk/jsonrpc"
	"github.com/modelcontextprotocol/go-sdk/mcp"

	"git.warky.dev/wdevs/amcs/internal/buildinfo"
	"git.warky.dev/wdevs/amcs/internal/config"
	"git.warky.dev/wdevs/amcs/internal/mcperrors"
	"git.warky.dev/wdevs/amcs/internal/tools"
)

func TestStreamableHTTPReturnsStructuredToolErrors(t *testing.T) {
	handler, err := New(config.MCPConfig{
		ServerName:     "test",
		Version:        "0.0.1",
		SessionTimeout: time.Minute,
	}, nil, streamableTestToolSet(), nil)
	if err != nil {
		t.Fatalf("mcpserver.New() error = %v", err)
	}

	httpServer := httptest.NewServer(handler)
	defer httpServer.Close()

	client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "0.0.1"}, nil)
	cs, err := client.Connect(context.Background(), &mcp.StreamableClientTransport{Endpoint: httpServer.URL}, nil)
	if err != nil {
		t.Fatalf("connect client: %v", err)
	}
	defer func() {
		_ = cs.Close()
	}()

	t.Run("schema_validation", func(t *testing.T) {
		_, err := cs.CallTool(context.Background(), &mcp.CallToolParams{
			Name:      "create_project",
			Arguments: map[string]any{},
		})
		if err == nil {
			t.Fatal("CallTool(create_project) error = nil, want error")
		}

		rpcErr, data := requireWireError(t, err)
		if rpcErr.Code != jsonrpc.CodeInvalidParams {
			t.Fatalf("create_project code = %d, want %d", rpcErr.Code, jsonrpc.CodeInvalidParams)
		}
		if data.Type != mcperrors.TypeInvalidArguments {
			t.Fatalf("create_project data.type = %q, want %q", data.Type, mcperrors.TypeInvalidArguments)
		}
		if data.Field != "name" {
			t.Fatalf("create_project data.field = %q, want %q", data.Field, "name")
		}
	})

	t.Run("project_required", func(t *testing.T) {
		_, err := cs.CallTool(context.Background(), &mcp.CallToolParams{
			Name:      "get_project_context",
			Arguments: map[string]any{},
		})
		if err == nil {
			t.Fatal("CallTool(get_project_context) error = nil, want error")
		}

		rpcErr, data := requireWireError(t, err)
		if rpcErr.Code != mcperrors.CodeProjectRequired {
			t.Fatalf("get_project_context code = %d, want %d", rpcErr.Code, mcperrors.CodeProjectRequired)
		}
		if data.Type != mcperrors.TypeProjectRequired {
			t.Fatalf("get_project_context data.type = %q, want %q", data.Type, mcperrors.TypeProjectRequired)
		}
		if data.Field != "project" {
			t.Fatalf("get_project_context data.field = %q, want %q", data.Field, "project")
		}
	})

	t.Run("version_info", func(t *testing.T) {
		result, err := cs.CallTool(context.Background(), &mcp.CallToolParams{
			Name:      "get_version_info",
			Arguments: map[string]any{},
		})
		if err != nil {
			t.Fatalf("CallTool(get_version_info) error = %v", err)
		}

		got, ok := result.StructuredContent.(map[string]any)
		if !ok {
			t.Fatalf("structured content type = %T, want map[string]any", result.StructuredContent)
		}
		if got["server_name"] != "test" {
			t.Fatalf("server_name = %#v, want %q", got["server_name"], "test")
		}
		if got["version"] != "0.0.1" {
			t.Fatalf("version = %#v, want %q", got["version"], "0.0.1")
		}
		if got["tag_name"] != "v0.0.1" {
			t.Fatalf("tag_name = %#v, want %q", got["tag_name"], "v0.0.1")
		}
		if got["build_date"] != "2026-03-31T00:00:00Z" {
			t.Fatalf("build_date = %#v, want %q", got["build_date"], "2026-03-31T00:00:00Z")
		}
	})

	t.Run("add_learning_requires_summary", func(t *testing.T) {
		_, err := cs.CallTool(context.Background(), &mcp.CallToolParams{
			Name:      "add_learning",
			Arguments: map[string]any{},
		})
		if err == nil {
			t.Fatal("CallTool(add_learning) error = nil, want error")
		}

		rpcErr, data := requireWireError(t, err)
		if rpcErr.Code != jsonrpc.CodeInvalidParams {
			t.Fatalf("add_learning code = %d, want %d", rpcErr.Code, jsonrpc.CodeInvalidParams)
		}
		if data.Type != mcperrors.TypeInvalidArguments {
			t.Fatalf("add_learning data.type = %q, want %q", data.Type, mcperrors.TypeInvalidArguments)
		}
		if data.Field != "summary" {
			t.Fatalf("add_learning data.field = %q, want %q", data.Field, "summary")
		}
	})

	t.Run("get_learning_requires_id", func(t *testing.T) {
		_, err := cs.CallTool(context.Background(), &mcp.CallToolParams{
			Name:      "get_learning",
			Arguments: map[string]any{},
		})
		if err == nil {
			t.Fatal("CallTool(get_learning) error = nil, want error")
		}

		rpcErr, data := requireWireError(t, err)
		if rpcErr.Code != jsonrpc.CodeInvalidParams {
			t.Fatalf("get_learning code = %d, want %d", rpcErr.Code, jsonrpc.CodeInvalidParams)
		}
		if data.Type != mcperrors.TypeInvalidArguments {
			t.Fatalf("get_learning data.type = %q, want %q", data.Type, mcperrors.TypeInvalidArguments)
		}
		if data.Field != "id" {
			t.Fatalf("get_learning data.field = %q, want %q", data.Field, "id")
		}
	})

	t.Run("add_learning_unconfigured_returns_structured_error", func(t *testing.T) {
		_, err := cs.CallTool(context.Background(), &mcp.CallToolParams{
			Name: "add_learning",
			Arguments: map[string]any{
				"summary": "Learning with configured check",
			},
		})
		if err == nil {
			t.Fatal("CallTool(add_learning) error = nil, want error")
		}

		rpcErr, data := requireWireError(t, err)
		if rpcErr.Code != jsonrpc.CodeInvalidParams {
			t.Fatalf("add_learning code = %d, want %d", rpcErr.Code, jsonrpc.CodeInvalidParams)
		}
		if data.Type != mcperrors.TypeInvalidInput {
			t.Fatalf("add_learning data.type = %q, want %q", data.Type, mcperrors.TypeInvalidInput)
		}
	})

	t.Run("list_learnings_unconfigured_returns_structured_error", func(t *testing.T) {
		_, err := cs.CallTool(context.Background(), &mcp.CallToolParams{
			Name:      "list_learnings",
			Arguments: map[string]any{},
		})
		if err == nil {
			t.Fatal("CallTool(list_learnings) error = nil, want error")
		}

		rpcErr, data := requireWireError(t, err)
		if rpcErr.Code != jsonrpc.CodeInvalidParams {
			t.Fatalf("list_learnings code = %d, want %d", rpcErr.Code, jsonrpc.CodeInvalidParams)
		}
		if data.Type != mcperrors.TypeInvalidInput {
			t.Fatalf("list_learnings data.type = %q, want %q", data.Type, mcperrors.TypeInvalidInput)
		}
	})
}

func streamableTestToolSet() ToolSet {
	return ToolSet{
		Version:       tools.NewVersionTool("test", buildinfo.Info{Version: "0.0.1", TagName: "v0.0.1", Commit: "test", BuildDate: "2026-03-31T00:00:00Z"}),
		Capture:       new(tools.CaptureTool),
		Search:        new(tools.SearchTool),
		List:          new(tools.ListTool),
		Stats:         new(tools.StatsTool),
		Get:           new(tools.GetTool),
		Update:        new(tools.UpdateTool),
		Delete:        new(tools.DeleteTool),
		Archive:       new(tools.ArchiveTool),
		Projects:      new(tools.ProjectsTool),
		Context:       new(tools.ContextTool),
		Recall:        new(tools.RecallTool),
		Summarize:     new(tools.SummarizeTool),
		Links:         new(tools.LinksTool),
		Files:         new(tools.FilesTool),
		Backfill:      new(tools.BackfillTool),
		Reparse:       new(tools.ReparseMetadataTool),
		RetryMetadata: new(tools.RetryEnrichmentTool),
		Skills:        new(tools.SkillsTool),
		ChatHistory:   new(tools.ChatHistoryTool),
		Describe:      new(tools.DescribeTool),
		Learnings:     new(tools.LearningsTool),
		Plans:         new(tools.PlansTool),
	}
}
