package setting

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/yaoapp/gou/mcp"
	mcpTypes "github.com/yaoapp/gou/mcp/types"
	gouTypes "github.com/yaoapp/gou/types"
	"github.com/yaoapp/yao/mcpclient"
	"github.com/yaoapp/yao/openapi/oauth/authorized"
	oauthTypes "github.com/yaoapp/yao/openapi/oauth/types"
	"github.com/yaoapp/yao/openapi/response"
)

const mcpMaskPrefixLen = 7

func mcpOwner(info *oauthTypes.AuthorizedInfo) mcpclient.ClientOwner {
	if info.TeamID != "" {
		return mcpclient.ClientOwner{Type: "team", ID: info.TeamID}
	}
	return mcpclient.ClientOwner{Type: "user", ID: info.UserID}
}

func mcpCheckOwnership(c *mcpclient.Client, info *oauthTypes.AuthorizedInfo) error {
	owner := mcpOwner(info)
	if c.Owner.Type != owner.Type || c.Owner.ID != owner.ID {
		return fmt.Errorf("server not found")
	}
	return nil
}

func mcpMaskToken(token string) string {
	if token == "" {
		return ""
	}
	plain := cloudDecrypt(token)
	if len(plain) <= mcpMaskPrefixLen {
		return strings.Repeat("*", len(plain))
	}
	suffix := plain[len(plain)-4:]
	prefix := plain[:mcpMaskPrefixLen]
	return prefix + "..." + suffix
}

func mcpClientToResponse(c *mcpclient.Client) map[string]interface{} {
	resp := map[string]interface{}{
		"id":        c.ID,
		"name":      c.Name,
		"label":     c.Label,
		"transport": string(c.Transport),
		"url":       c.URL,
		"enabled":   c.Enabled,
		"status":    c.Status,
	}
	if c.Description != "" {
		resp["description"] = c.Description
	}
	if c.AuthorizationToken != "" {
		resp["authorization_token"] = mcpMaskToken(c.AuthorizationToken)
	}
	if c.Timeout != "" {
		resp["timeout"] = c.Timeout
	}
	if len(c.Tags) > 0 {
		resp["tags"] = c.Tags
	}
	return resp
}

// handleMCPList returns MCP servers for the current user/team.
// Only http and sse transports are returned.
// GET /setting/mcp/servers
func handleMCPList(c *gin.Context) {
	info := authorized.GetInfo(c)
	owner := mcpOwner(info)

	if mcpclient.Global == nil {
		respondError(c, http.StatusInternalServerError, "MCP client registry not initialized")
		return
	}

	all, err := mcpclient.Global.List(&mcpclient.ClientFilter{
		Owner:  &owner,
		Source: mcpclient.ClientSourceAll,
	})
	if err != nil {
		all = []mcpclient.Client{}
	}

	servers := make([]map[string]interface{}, 0, len(all))
	for i := range all {
		t := all[i].Transport
		if t != mcpTypes.TransportHTTP && t != mcpTypes.TransportSSE {
			continue
		}
		servers = append(servers, mcpClientToResponse(&all[i]))
	}

	response.RespondWithSuccess(c, http.StatusOK, map[string]interface{}{
		"servers": servers,
	})
}

// handleMCPCreate creates a new MCP server.
// POST /setting/mcp/servers
func handleMCPCreate(c *gin.Context) {
	if !guardOwner(c) {
		return
	}
	info := authorized.GetInfo(c)

	if mcpclient.Global == nil {
		respondError(c, http.StatusInternalServerError, "MCP client registry not initialized")
		return
	}

	var body struct {
		Name               string   `json:"name"`
		Label              string   `json:"label"`
		Description        string   `json:"description"`
		Transport          string   `json:"transport"`
		URL                string   `json:"url"`
		AuthorizationToken string   `json:"authorization_token"`
		Timeout            string   `json:"timeout"`
		Tags               []string `json:"tags"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		respondError(c, http.StatusBadRequest, "invalid request body")
		return
	}

	if body.Name == "" {
		respondError(c, http.StatusBadRequest, "name is required")
		return
	}
	if body.URL == "" {
		respondError(c, http.StatusBadRequest, "url is required")
		return
	}
	if _, err := url.ParseRequestURI(body.URL); err != nil {
		respondError(c, http.StatusBadRequest, "invalid url format")
		return
	}

	transport := mcpTypes.TransportHTTP
	if body.Transport == "sse" {
		transport = mcpTypes.TransportSSE
	}

	owner := mcpOwner(info)

	existing, _ := mcpclient.Global.List(&mcpclient.ClientFilter{
		Owner:  &owner,
		Source: mcpclient.ClientSourceAll,
	})
	for _, ex := range existing {
		if strings.EqualFold(ex.Name, body.Name) {
			respondError(c, http.StatusBadRequest, fmt.Sprintf("server with name \"%s\" already exists", body.Name))
			return
		}
	}

	clientID := owner.Type + "." + owner.ID + "." + body.Name
	client := &mcpclient.Client{
		ClientDSL: mcpTypes.ClientDSL{
			ID:        clientID,
			Name:      body.Name,
			Transport: transport,
			URL:       body.URL,
			Timeout:   body.Timeout,
			MetaInfo: gouTypes.MetaInfo{
				Label:       body.Label,
				Description: body.Description,
				Tags:        body.Tags,
			},
		},
		Enabled: true,
		Status:  "unconfigured",
		Source:  mcpclient.ClientSourceDynamic,
		Owner:   owner,
	}

	if body.AuthorizationToken != "" {
		client.AuthorizationToken = cloudEncrypt(body.AuthorizationToken)
	}
	if body.Timeout == "" {
		client.Timeout = "30s"
	}

	token := body.AuthorizationToken
	status, _, errMsg := mcpProbeRaw(transport, body.URL, token, client.Timeout)
	if status != "connected" {
		respondError(c, http.StatusBadRequest, errMsg)
		return
	}

	client.Status = "connected"
	created, err := mcpclient.Global.Create(client)
	if err != nil {
		respondError(c, http.StatusBadRequest, err.Error())
		return
	}
	response.RespondWithSuccess(c, http.StatusOK, mcpClientToResponse(created))
}

// handleMCPUpdate updates an existing MCP server.
// PUT /setting/mcp/servers/:id
func handleMCPUpdate(c *gin.Context) {
	if !guardOwner(c) {
		return
	}
	info := authorized.GetInfo(c)
	id := c.Param("id")

	if mcpclient.Global == nil {
		respondError(c, http.StatusInternalServerError, "MCP client registry not initialized")
		return
	}

	existing, err := mcpclient.Global.Get(id)
	if err != nil {
		respondError(c, http.StatusNotFound, "server not found")
		return
	}
	if err := mcpCheckOwnership(existing, info); err != nil {
		respondError(c, http.StatusNotFound, err.Error())
		return
	}

	var body struct {
		Name               string   `json:"name"`
		Label              string   `json:"label"`
		Description        string   `json:"description"`
		Transport          string   `json:"transport"`
		URL                string   `json:"url"`
		AuthorizationToken string   `json:"authorization_token"`
		Timeout            string   `json:"timeout"`
		Tags               []string `json:"tags"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		respondError(c, http.StatusBadRequest, "invalid request body")
		return
	}

	if body.URL != "" {
		if _, err := url.ParseRequestURI(body.URL); err != nil {
			respondError(c, http.StatusBadRequest, "invalid url format")
			return
		}
	}

	updated := &mcpclient.Client{
		ClientDSL: mcpTypes.ClientDSL{
			ID:   id,
			Name: existing.Name,
			MetaInfo: gouTypes.MetaInfo{
				Label:       existing.Label,
				Description: existing.Description,
				Tags:        existing.Tags,
			},
			Transport:          existing.Transport,
			URL:                existing.URL,
			AuthorizationToken: existing.AuthorizationToken,
			Timeout:            existing.Timeout,
		},
		Enabled: existing.Enabled,
		Status:  existing.Status,
		Source:  existing.Source,
		Owner:   existing.Owner,
	}

	if body.Name != "" {
		updated.Name = body.Name
	}
	if body.Label != "" {
		updated.Label = body.Label
	}
	if body.Description != "" {
		updated.Description = body.Description
	}
	if body.Transport != "" {
		if body.Transport == "sse" {
			updated.Transport = mcpTypes.TransportSSE
		} else {
			updated.Transport = mcpTypes.TransportHTTP
		}
	}
	if body.URL != "" {
		updated.URL = body.URL
	}
	if body.AuthorizationToken != "" {
		updated.AuthorizationToken = cloudEncrypt(body.AuthorizationToken)
	}
	if body.Timeout != "" {
		updated.Timeout = body.Timeout
	}
	if body.Tags != nil {
		updated.Tags = body.Tags
	}

	token := body.AuthorizationToken
	if token == "" && updated.AuthorizationToken != "" {
		token = cloudDecrypt(updated.AuthorizationToken)
	}
	probeTransport := updated.Transport
	probeURL := updated.URL
	status, _, errMsg := mcpProbeRaw(probeTransport, probeURL, token, updated.Timeout)
	if status != "connected" {
		respondError(c, http.StatusBadRequest, errMsg)
		return
	}

	updated.Status = "connected"
	result, err := mcpclient.Global.Update(id, updated)
	if err != nil {
		respondError(c, http.StatusInternalServerError, err.Error())
		return
	}
	response.RespondWithSuccess(c, http.StatusOK, mcpClientToResponse(result))
}

// handleMCPDelete removes an MCP server.
// DELETE /setting/mcp/servers/:id
func handleMCPDelete(c *gin.Context) {
	if !guardOwner(c) {
		return
	}
	info := authorized.GetInfo(c)
	id := c.Param("id")

	if mcpclient.Global == nil {
		respondError(c, http.StatusInternalServerError, "MCP client registry not initialized")
		return
	}

	existing, err := mcpclient.Global.Get(id)
	if err != nil {
		respondError(c, http.StatusNotFound, "server not found")
		return
	}
	if err := mcpCheckOwnership(existing, info); err != nil {
		respondError(c, http.StatusNotFound, err.Error())
		return
	}

	if err := mcpclient.Global.Delete(id); err != nil {
		respondError(c, http.StatusInternalServerError, err.Error())
		return
	}

	c.Status(http.StatusNoContent)
}

// mcpProbeRaw creates a temporary MCP client from raw config, tests Connect+Initialize+ListTools.
func mcpProbeRaw(transport mcpTypes.TransportType, urlStr, token, timeout string) (status string, latencyMs int64, errMsg string) {
	if timeout == "" {
		timeout = "30s"
	}
	tempID := fmt.Sprintf("__probe_%d", time.Now().UnixNano())
	dsl := mcpTypes.ClientDSL{
		ID:                 tempID,
		Name:               tempID,
		Transport:          transport,
		URL:                urlStr,
		AuthorizationToken: token,
		Timeout:            timeout,
	}
	dslJSON, err := json.Marshal(dsl)
	if err != nil {
		return "disconnected", 0, fmt.Sprintf("marshal: %s", err)
	}

	start := time.Now()
	mcpClient, err := mcp.LoadClientSourceWithType(string(dslJSON), tempID, "")
	if err != nil {
		return "disconnected", 0, fmt.Sprintf("load: %s", err)
	}
	defer mcp.UnloadClient(tempID)

	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	if err := mcpClient.Connect(ctx); err != nil {
		return "disconnected", time.Since(start).Milliseconds(), fmt.Sprintf("connect: %s", err)
	}
	defer mcpClient.Disconnect(context.Background())

	if _, err := mcpClient.Initialize(ctx); err != nil {
		return "disconnected", time.Since(start).Milliseconds(), fmt.Sprintf("initialize: %s", err)
	}

	_, err = mcpClient.ListTools(ctx, "")
	latencyMs = time.Since(start).Milliseconds()
	if err != nil {
		return "disconnected", latencyMs, fmt.Sprintf("listTools: %s", err)
	}
	return "connected", latencyMs, ""
}

// handleMCPTest tests connectivity using raw config (for add/edit before save).
// Creates a temporary runtime client, tests ListTools, then cleans up.
// POST /setting/mcp/test
func handleMCPTest(c *gin.Context) {
	if !guardOwner(c) {
		return
	}

	var body struct {
		Transport          string `json:"transport"`
		URL                string `json:"url"`
		AuthorizationToken string `json:"authorization_token"`
		Timeout            string `json:"timeout"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		respondError(c, http.StatusBadRequest, "invalid request body")
		return
	}
	if body.URL == "" {
		respondError(c, http.StatusBadRequest, "url is required")
		return
	}

	transport := mcpTypes.TransportHTTP
	if body.Transport == "sse" {
		transport = mcpTypes.TransportSSE
	}
	timeout := body.Timeout
	if timeout == "" {
		timeout = "30s"
	}

	tempID := fmt.Sprintf("__test_%d", time.Now().UnixNano())
	dsl := mcpTypes.ClientDSL{
		ID:                 tempID,
		Name:               tempID,
		Transport:          transport,
		URL:                body.URL,
		AuthorizationToken: body.AuthorizationToken,
		Timeout:            timeout,
	}

	dslJSON, err := json.Marshal(dsl)
	if err != nil {
		respondError(c, http.StatusInternalServerError, "failed to marshal config")
		return
	}

	start := time.Now()
	mcpClient, err := mcp.LoadClientSourceWithType(string(dslJSON), tempID, "")
	if err != nil {
		response.RespondWithSuccess(c, http.StatusOK, mcpclient.ClientTestResult{
			Success: false,
			Message: fmt.Sprintf("Failed to load client: %s", err.Error()),
		})
		return
	}
	defer mcp.UnloadClient(tempID)

	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	if err := mcpClient.Connect(ctx); err != nil {
		latencyMs := time.Since(start).Milliseconds()
		response.RespondWithSuccess(c, http.StatusOK, mcpclient.ClientTestResult{
			Success:   false,
			Message:   fmt.Sprintf("Connection failed: %s", err.Error()),
			LatencyMs: latencyMs,
		})
		return
	}
	defer mcpClient.Disconnect(context.Background())

	if _, err := mcpClient.Initialize(ctx); err != nil {
		latencyMs := time.Since(start).Milliseconds()
		response.RespondWithSuccess(c, http.StatusOK, mcpclient.ClientTestResult{
			Success:   false,
			Message:   fmt.Sprintf("Initialization failed: %s", err.Error()),
			LatencyMs: latencyMs,
		})
		return
	}

	_, err = mcpClient.ListTools(ctx, "")
	latencyMs := time.Since(start).Milliseconds()

	if err != nil {
		response.RespondWithSuccess(c, http.StatusOK, mcpclient.ClientTestResult{
			Success:   false,
			Message:   fmt.Sprintf("Connection failed: %s", err.Error()),
			LatencyMs: latencyMs,
		})
		return
	}

	response.RespondWithSuccess(c, http.StatusOK, mcpclient.ClientTestResult{
		Success:   true,
		Message:   "Connection successful",
		LatencyMs: latencyMs,
	})
}
