package dashboard

import (
	"context"
	"errors"
	"net/http"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/desktop/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
	"github.com/tidbcloud/AgentTeam/desktop/internal/wire/mcpcheck"
)

func toMCPServerResponse(server profile.MCPServer) dto.MCPServerResponse {
	redacted := server.Redacted()
	resp := dto.MCPServerResponse{
		ID:          redacted.ID,
		DisplayName: redacted.DisplayName,
		Description: redacted.Description,
		Enabled:     redacted.Enabled,
		Transport:   string(redacted.Transport),
		CreatedAt:   redacted.CreatedAt,
		UpdatedAt:   redacted.UpdatedAt,
	}
	if redacted.HTTP != nil {
		resp.HTTP = &dto.MCPServerHTTPConfig{
			URL:     redacted.HTTP.URL,
			Headers: append([]string(nil), redacted.HTTP.Headers...),
		}
	}
	if redacted.Stdio != nil {
		resp.Stdio = &dto.MCPServerStdioConfig{
			Command: redacted.Stdio.Command,
			Args:    append([]string(nil), redacted.Stdio.Args...),
			Env:     append([]string(nil), redacted.Stdio.Env...),
			CWD:     redacted.Stdio.CWD,
		}
	}
	if redacted.LastCheck != nil {
		resp.LastCheck = toMCPServerCheckResponse(*redacted.LastCheck)
	}
	return resp
}

func toMCPServerResponses(servers []profile.MCPServer) []dto.MCPServerResponse {
	resp := make([]dto.MCPServerResponse, 0, len(servers))
	for _, server := range servers {
		resp = append(resp, toMCPServerResponse(server))
	}
	return resp
}

func toMCPServerCheckResponse(check profile.MCPServerCheck) *dto.MCPServerCheckResponse {
	tools := make([]dto.MCPToolSummary, 0, len(check.Tools))
	for _, tool := range check.Tools {
		tools = append(tools, dto.MCPToolSummary{Name: tool.Name, Description: tool.Description})
	}
	return &dto.MCPServerCheckResponse{
		OK:        check.OK,
		Stage:     check.Stage,
		Message:   check.Message,
		CheckedAt: check.CheckedAt,
		Tools:     tools,
	}
}

func mcpServerFromRequest(req dto.MCPServerRequest, defaultEnabled bool) profile.MCPServer {
	enabled := defaultEnabled
	if req.Enabled != nil {
		enabled = *req.Enabled
	}
	server := profile.MCPServer{
		ID:          req.ID,
		DisplayName: req.DisplayName,
		Description: req.Description,
		Enabled:     enabled,
		Transport:   profile.MCPServerTransport(req.Transport),
	}
	if req.HTTP != nil {
		server.HTTP = &profile.MCPHTTPConfig{
			URL:     req.HTTP.URL,
			Headers: append([]string(nil), req.HTTP.Headers...),
		}
	}
	if req.Stdio != nil {
		server.Stdio = &profile.MCPStdioConfig{
			Command: req.Stdio.Command,
			Args:    append([]string(nil), req.Stdio.Args...),
			Env:     append([]string(nil), req.Stdio.Env...),
			CWD:     req.Stdio.CWD,
		}
	}
	return server
}

// MCPServersHandler serves the external MCP server registry API.
type MCPServersHandler struct{}

// NewListMCPServersHandler creates a handler for listing external MCP servers.
func NewListMCPServersHandler() gin.HandlerFunc { return (&MCPServersHandler{}).List }

// NewCreateMCPServerHandler creates a handler for creating an external MCP server.
func NewCreateMCPServerHandler() gin.HandlerFunc { return (&MCPServersHandler{}).Create }

// NewTestUnsavedMCPServerHandler creates a handler for testing an unsaved external MCP server.
func NewTestUnsavedMCPServerHandler() gin.HandlerFunc { return (&MCPServersHandler{}).TestUnsaved }

// NewGetMCPServerHandler creates a handler for fetching one external MCP server.
func NewGetMCPServerHandler() gin.HandlerFunc { return (&MCPServersHandler{}).Get }

// NewUpdateMCPServerHandler creates a handler for updating one external MCP server.
func NewUpdateMCPServerHandler() gin.HandlerFunc { return (&MCPServersHandler{}).Update }

// NewDeleteMCPServerHandler creates a handler for deleting one external MCP server.
func NewDeleteMCPServerHandler() gin.HandlerFunc { return (&MCPServersHandler{}).Delete }

// NewTestMCPServerHandler creates a handler for testing one saved external MCP server.
func NewTestMCPServerHandler() gin.HandlerFunc { return (&MCPServersHandler{}).TestSaved }

// List handles GET /api/internal/mcp-servers.
// It returns all AgentTeam-managed external MCP server configs with secret values redacted.
func (h *MCPServersHandler) List(c *gin.Context) {
	servers := profile.Default().MCPServers().All()
	c.JSON(http.StatusOK, toMCPServerResponses(servers))
}

// Create handles POST /api/internal/mcp-servers.
// It validates and persists a new AgentTeam-managed external MCP server config.
func (h *MCPServersHandler) Create(c *gin.Context) {
	var req dto.MCPServerRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	server, err := profile.Default().MCPServers().Add(mcpServerFromRequest(req, true))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusCreated, toMCPServerResponse(server))
}

// TestUnsaved handles POST /api/internal/mcp-servers/test.
// It validates and probes an unsaved config from the MCP server setup wizard.
func (h *MCPServersHandler) TestUnsaved(c *gin.Context) {
	var req dto.MCPServerRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	server := mcpServerFromRequest(req, true)
	if server.ID != "" {
		if _, ok := profile.Default().MCPServers().Get(server.ID); ok {
			_ = server.Validate()
		}
	}
	check := mcpcheck.Check(c.Request.Context(), server)
	status := http.StatusOK
	if !check.OK {
		status = http.StatusBadRequest
	}
	c.JSON(status, toMCPServerCheckResponse(check))
}

// Get handles GET /api/internal/mcp-servers/:id.
// It returns one external MCP server config with secret values redacted.
func (h *MCPServersHandler) Get(c *gin.Context) {
	server, ok := profile.Default().MCPServers().Get(c.Param("id"))
	if !ok {
		c.JSON(http.StatusNotFound, gin.H{"error": "mcp server not found"})
		return
	}
	c.JSON(http.StatusOK, toMCPServerResponse(server))
}

// Update handles PUT /api/internal/mcp-servers/:id.
// It validates and persists a replacement external MCP server config.
func (h *MCPServersHandler) Update(c *gin.Context) {
	manager := profile.Default().MCPServers()
	existing, ok := manager.Get(c.Param("id"))
	if !ok {
		c.JSON(http.StatusNotFound, gin.H{"error": "mcp server not found"})
		return
	}
	var req dto.MCPServerRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if req.HTTP != nil && req.HTTP.Headers == nil && existing.HTTP != nil {
		req.HTTP.Headers = append([]string(nil), existing.HTTP.Headers...)
	}
	if req.Stdio != nil && req.Stdio.Env == nil && existing.Stdio != nil {
		req.Stdio.Env = append([]string(nil), existing.Stdio.Env...)
	}
	server, err := manager.Update(c.Param("id"), mcpServerFromRequest(req, existing.Enabled))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, toMCPServerResponse(server))
}

// Delete handles DELETE /api/internal/mcp-servers/:id.
// It removes one external MCP server config from the AgentTeam-managed registry.
func (h *MCPServersHandler) Delete(c *gin.Context) {
	if err := profile.Default().MCPServers().Delete(c.Param("id")); err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "mcp server not found"})
		return
	}
	c.Status(http.StatusNoContent)
}

// TestSaved handles POST /api/internal/mcp-servers/:id/test.
// It probes a saved external MCP server and stores the redacted check result.
func (h *MCPServersHandler) TestSaved(c *gin.Context) {
	result, err := TestMCPServer(c.Request.Context(), c.Param("id"))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
		return
	}
	status := http.StatusOK
	if !result.OK {
		status = http.StatusBadRequest
	}
	c.JSON(status, result)
}

// ---------------------------------------------------------------------------
// Extracted core functions (callable without *gin.Context)
// ---------------------------------------------------------------------------

// ListMCPServers returns every AgentTeam-managed external MCP server with
// secret values redacted. Used by GET /api/internal/mcp-servers and the
// machine.mcp_servers.list remote endpoint.
func ListMCPServers() []dto.MCPServerResponse {
	servers := profile.Default().MCPServers().All()
	return toMCPServerResponses(servers)
}

// UpsertMCPServer creates or replaces the MCP server identified by req.ID.
// It is the core of the machine.mcp_servers.upsert remote endpoint. On
// update (req.ID matches an existing server), omitted fields inherit from
// the existing record: Enabled, HTTP headers, and Stdio env. On create,
// Enabled defaults to true. The HTTP POST/PUT handlers keep their own
// create-only / update-only semantics and are not routed through here.
func UpsertMCPServer(req dto.MCPServerRequest) (dto.MCPServerResponse, error) {
	manager := profile.Default().MCPServers()
	defaultEnabled := true
	if req.ID != "" {
		if existing, ok := manager.Get(req.ID); ok {
			defaultEnabled = existing.Enabled
			if req.HTTP != nil && req.HTTP.Headers == nil && existing.HTTP != nil {
				req.HTTP.Headers = append([]string(nil), existing.HTTP.Headers...)
			}
			if req.Stdio != nil && req.Stdio.Env == nil && existing.Stdio != nil {
				req.Stdio.Env = append([]string(nil), existing.Stdio.Env...)
			}
			saved, err := manager.Update(req.ID, mcpServerFromRequest(req, defaultEnabled))
			if err != nil {
				return dto.MCPServerResponse{}, err
			}
			return toMCPServerResponse(saved), nil
		}
	}
	saved, err := manager.Add(mcpServerFromRequest(req, defaultEnabled))
	if err != nil {
		return dto.MCPServerResponse{}, err
	}
	return toMCPServerResponse(saved), nil
}

// DeleteMCPServer removes the MCP server identified by id. Returns the
// manager's not-found error if the ID does not match a configured
// server. Used by DELETE /api/internal/mcp-servers/:id and the
// machine.mcp_servers.delete remote endpoint.
// DeleteMCPServer removes the MCP server identified by id. It returns
// ErrMCPServerNotFound when the ID does not match a configured server so
// RPC callers can distinguish not-found from operational failures.
func DeleteMCPServer(id string) error {
	manager := profile.Default().MCPServers()
	if _, ok := manager.Get(id); !ok {
		return ErrMCPServerNotFound
	}
	return manager.Delete(id)
}

// TestMCPServer probes the MCP server identified by id, persists the
// redacted check result, and returns it. Used by POST
// /api/internal/mcp-servers/:id/test and the machine.mcp_servers.test
// remote endpoint. Returns a not-found error if the ID does not exist.
func TestMCPServer(ctx context.Context, id string) (dto.MCPServerCheckResponse, error) {
	manager := profile.Default().MCPServers()
	server, ok := manager.Get(id)
	if !ok {
		return dto.MCPServerCheckResponse{}, ErrMCPServerNotFound
	}
	check := mcpcheck.Check(ctx, server)
	if _, err := manager.SetLastCheck(server.ID, check); err != nil {
		return dto.MCPServerCheckResponse{}, err
	}
	return *toMCPServerCheckResponse(check), nil
}

// ErrMCPServerNotFound is the sentinel returned by DeleteMCPServer and
// TestMCPServer when the requested ID does not match a configured server.
// RPC callers branch on it to distinguish not-found (404) from operational
// failures (500).
var ErrMCPServerNotFound = errors.New("mcp server not found")
