package tools

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"os"
	"strings"

	"github.com/ClareAI/astra-insight-mcp/internal/gateway"
	"github.com/ClareAI/astra-insight-mcp/internal/mcp"
)

// ──────────────────────────────────────────────────────────────────────────────
// Voice Agent Config CRUD
// ──────────────────────────────────────────────────────────────────────────────

func registerListVoiceAgents(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "list_voice_agents",
		Description: "List all voice agents for the authenticated tenant. Returns voice agent configs including draft and published configurations. Optionally include disabled agents.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"include_disabled": {Type: "boolean", Description: "Include disabled agents in the list (default: false)"},
			},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		if apiKey == "" {
			return nil, fmt.Errorf("api_key is required")
		}
		includeDisabled, _ := args["include_disabled"].(bool)
		return gw.ListVoiceAgents(ctx, apiKey, includeDisabled)
	})
}

func registerGetVoiceAgent(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "get_voice_agent",
		Description: "Get a voice agent's full configuration by ID, including draft and published agent_config, instruction, voice settings, and runtime parameters.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"voice_agent_id": {Type: "string", Description: "Voice agent ID (UUID)"},
			},
			Required: []string{"voice_agent_id"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		agentID, _ := args["voice_agent_id"].(string)
		if apiKey == "" || agentID == "" {
			return nil, fmt.Errorf("api_key and voice_agent_id are required")
		}
		return gw.GetVoiceAgent(ctx, apiKey, agentID)
	})
}

func registerGetVoiceAgentByTextAgent(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "get_voice_agent_by_text_agent",
		Description: "Look up a voice agent by its associated text agent ID. Useful for finding the voice counterpart of a text agent.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"text_agent_id": {Type: "string", Description: "Text agent ID to look up"},
			},
			Required: []string{"text_agent_id"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		textAgentID, _ := args["text_agent_id"].(string)
		if apiKey == "" || textAgentID == "" {
			return nil, fmt.Errorf("api_key and text_agent_id are required")
		}
		return gw.GetVoiceAgentByTextAgent(ctx, apiKey, textAgentID)
	})
}

func registerGetDefaultTenantVoiceAgents(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "get_default_tenant_voice_agents",
		Description: "List voice agents under the default tenant. Returns summaries with agent_id, agent_name, channel_phone_number, and wati_tenant_id.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
			},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		if apiKey == "" {
			return nil, fmt.Errorf("api_key is required")
		}
		return gw.GetDefaultTenantVoiceAgents(ctx, apiKey)
	})
}

func registerQuickCreateVoiceAgent(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "quick_create_voice_agent",
		Description: "Quick-create a voice agent from a template, linked to an existing text agent. Automatically resolves tenant_id from the text agent. Before calling, use get_voice_agent_by_text_agent to check if a voice agent already exists for this text agent. Requires text_agent_id and template_id. Optionally set alias for greeting placeholders.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"text_agent_id": {Type: "string", Description: "Text agent ID (required)"},
				"template_id":   {Type: "string", Description: "Template ID to initialize from (required)"},
				"alias":         {Type: "string", Description: "Alias for greeting placeholders"},
			},
			Required: []string{"text_agent_id", "template_id"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		textAgentID, _ := args["text_agent_id"].(string)
		templateID, _ := args["template_id"].(string)
		if apiKey == "" || textAgentID == "" || templateID == "" {
			return nil, fmt.Errorf("api_key, text_agent_id, and template_id are required")
		}

		// Auto-resolve tenant_id from the text agent to avoid extra LLM reasoning.
		textAgent, err := gw.GetAgent(ctx, apiKey, textAgentID)
		if err != nil {
			return nil, fmt.Errorf("failed to get text agent %s: %w", textAgentID, err)
		}
		if textAgent.TenantID == "" {
			return nil, fmt.Errorf("text agent %s has no tenant_id", textAgentID)
		}

		return gw.QuickCreateVoiceAgent(ctx, apiKey, gateway.QuickCreateVoiceAgentRequest{
			TextAgentID: textAgentID,
			TemplateID:  templateID,
			TenantID:    textAgent.TenantID,
			Alias:       strval(args, "alias"),
		})
	})
}

func registerUpdateVoiceAgent(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name: "update_voice_agent",
		Description: `Update a voice agent. All agent_config changes use safe GET→merge→PUT to avoid clearing existing fields.

Shortcut params: instruction → prompt_config.realtime_template, greeting → prompt_config.greeting_template, outbound_instruction/outbound_greeting → outbound_prompt_config equivalents.
agent_config param: merge any top-level config fields — voice_id, voice, language, default_accent, tone, persona, speed, model_provider, services, expertise, minimax_config, rag_config, silence_config, etc.
All params can be combined in a single call.`,
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"voice_agent_id":       {Type: "string", Description: "Voice agent ID (UUID)"},
				"agent_name":           {Type: "string", Description: "New agent name"},
				"disabled":             {Type: "boolean", Description: "Enable or disable the agent"},
				"instruction":          {Type: "string", Description: "New inbound system prompt (prompt_config.realtime_template)"},
				"greeting":             {Type: "string", Description: "New inbound greeting (prompt_config.greeting_template)"},
				"outbound_instruction": {Type: "string", Description: "New outbound system prompt (outbound_prompt_config.realtime_template)"},
				"outbound_greeting":    {Type: "string", Description: "New outbound greeting (outbound_prompt_config.greeting_template)"},
				"agent_config":         {Type: "object", Description: "Partial agent_config fields to merge (e.g. voice_id, language, model_provider). Merged on top of existing config."},
			},
			Required: []string{"voice_agent_id"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		agentID, _ := args["voice_agent_id"].(string)
		if apiKey == "" || agentID == "" {
			return nil, fmt.Errorf("api_key and voice_agent_id are required")
		}

		instruction := strval(args, "instruction")
		greeting := strval(args, "greeting")
		outboundInstruction := strval(args, "outbound_instruction")
		outboundGreeting := strval(args, "outbound_greeting")
		partialConfig, hasPartialConfig := args["agent_config"].(map[string]interface{})

		needsConfigMerge := instruction != "" || greeting != "" ||
			outboundInstruction != "" || outboundGreeting != "" || hasPartialConfig

		req := gateway.UpdateVoiceAgentRequest{
			AgentName: strval(args, "agent_name"),
		}
		if v, ok := args["disabled"]; ok {
			if b, ok := v.(bool); ok {
				req.Disabled = &b
			}
		}

		if needsConfigMerge {
			// GET current config first to avoid clearing existing fields.
			raw, err := gw.GetVoiceAgent(ctx, apiKey, agentID)
			if err != nil {
				return nil, fmt.Errorf("get voice agent for merge: %w", err)
			}
			var current map[string]interface{}
			if err := json.Unmarshal(raw, &current); err != nil {
				return nil, fmt.Errorf("parse voice agent: %w", err)
			}

			agentConfig, _ := current["agent_config"].(map[string]interface{})
			if agentConfig == nil {
				agentConfig = make(map[string]interface{})
			}

			// Merge partial agent_config top-level fields (voice_id, language, etc.)
			for k, v := range partialConfig {
				agentConfig[k] = v
			}

			// Merge instruction/greeting into prompt_config.
			if instruction != "" || greeting != "" {
				pc, _ := agentConfig["prompt_config"].(map[string]interface{})
				if pc == nil {
					pc = make(map[string]interface{})
				}
				if instruction != "" {
					pc["realtime_template"] = instruction
				}
				if greeting != "" {
					pc["greeting_template"] = greeting
				}
				agentConfig["prompt_config"] = pc
			}

			// Merge outbound variants.
			if outboundInstruction != "" || outboundGreeting != "" {
				opc, _ := agentConfig["outbound_prompt_config"].(map[string]interface{})
				if opc == nil {
					opc = make(map[string]interface{})
				}
				if outboundInstruction != "" {
					opc["realtime_template"] = outboundInstruction
				}
				if outboundGreeting != "" {
					opc["greeting_template"] = outboundGreeting
				}
				agentConfig["outbound_prompt_config"] = opc
			}

			data, _ := json.Marshal(agentConfig)
			req.AgentConfig = json.RawMessage(data)
		}

		return gw.UpdateVoiceAgent(ctx, apiKey, agentID, req)
	})
}

func registerDeleteVoiceAgent(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "delete_voice_agent",
		Description: "Permanently delete a voice agent and sync removal to cache. This cannot be undone.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"voice_agent_id": {Type: "string", Description: "Voice agent ID to delete (UUID)"},
			},
			Required: []string{"voice_agent_id"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		agentID, _ := args["voice_agent_id"].(string)
		if apiKey == "" || agentID == "" {
			return nil, fmt.Errorf("api_key and voice_agent_id are required")
		}
		if err := gw.DeleteVoiceAgent(ctx, apiKey, agentID); err != nil {
			return nil, err
		}
		return map[string]string{"status": "deleted", "voice_agent_id": agentID}, nil
	})
}

func registerPublishVoiceAgent(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "publish_voice_agent",
		Description: "Publish a voice agent's draft config to make it live. Copies draft agent_config to published_agent_config and syncs to runtime. The agent must have a non-empty draft config.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"voice_agent_id": {Type: "string", Description: "Voice agent ID (UUID)"},
				"comment":        {Type: "string", Description: "Optional publish comment"},
			},
			Required: []string{"voice_agent_id"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		agentID, _ := args["voice_agent_id"].(string)
		if apiKey == "" || agentID == "" {
			return nil, fmt.Errorf("api_key and voice_agent_id are required")
		}
		return gw.PublishVoiceAgent(ctx, apiKey, gateway.PublishVoiceAgentRequest{
			AgentID: agentID,
			Comment: strval(args, "comment"),
		})
	})
}

func registerGetVoiceAgentCount(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "get_voice_agent_count",
		Description: "Get the total count of voice agents for the authenticated tenant.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
			},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		if apiKey == "" {
			return nil, fmt.Errorf("api_key is required")
		}
		return gw.GetVoiceAgentCount(ctx, apiKey)
	})
}

func registerCheckVoiceAgentExists(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "check_voice_agent_exists",
		Description: "Check if a voice agent exists by ID. Returns true/false without fetching the full config.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"voice_agent_id": {Type: "string", Description: "Voice agent ID (UUID)"},
			},
			Required: []string{"voice_agent_id"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		agentID, _ := args["voice_agent_id"].(string)
		if apiKey == "" || agentID == "" {
			return nil, fmt.Errorf("api_key and voice_agent_id are required")
		}
		exists, err := gw.CheckVoiceAgentExists(ctx, apiKey, agentID)
		if err != nil {
			return nil, err
		}
		return map[string]interface{}{"exists": exists, "voice_agent_id": agentID}, nil
	})
}

// ──────────────────────────────────────────────────────────────────────────────
// Voice Call Control
// ──────────────────────────────────────────────────────────────────────────────

func registerNewVoiceCall(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "new_voice_call",
		Description: "Accept a new incoming WhatsApp voice call. Requires callId and SDP offer. Tenant is validated and usage gating is applied. Call is processed asynchronously.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"call_id": {Type: "string", Description: "Unique call ID"},
				"sdp":     {Type: "string", Description: "WebRTC SDP offer"},
			},
			Required: []string{"call_id", "sdp"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		callID, _ := args["call_id"].(string)
		sdp, _ := args["sdp"].(string)
		if apiKey == "" || callID == "" || sdp == "" {
			return nil, fmt.Errorf("api_key, call_id, and sdp are required")
		}
		return gw.NewVoiceCall(ctx, apiKey, gateway.VoiceNewCallRequest{
			CallID: callID,
			SDP:    sdp,
		})
	})
}

func registerWebNewVoiceCall(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "web_new_voice_call",
		Description: "Start a new Web channel voice call. Requires agentId, callId, and SDP. Optional: businessNumber, contactName, from, language, accent, modelProvider.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"agent_id":        {Type: "string", Description: "Voice agent ID"},
				"call_id":         {Type: "string", Description: "Unique call ID"},
				"sdp":             {Type: "string", Description: "WebRTC SDP offer"},
				"business_number": {Type: "string", Description: "Business phone number"},
				"contact_name":    {Type: "string", Description: "Contact name"},
				"from":            {Type: "string", Description: "Caller identifier"},
				"language":        {Type: "string", Description: "Voice language"},
				"accent":          {Type: "string", Description: "Voice accent"},
				"model_provider":  {Type: "string", Description: "Model provider (openai, gemini, minimax)"},
			},
			Required: []string{"agent_id", "call_id", "sdp"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		agentID, _ := args["agent_id"].(string)
		callID, _ := args["call_id"].(string)
		sdp, _ := args["sdp"].(string)
		if apiKey == "" || agentID == "" || callID == "" || sdp == "" {
			return nil, fmt.Errorf("api_key, agent_id, call_id, and sdp are required")
		}
		return gw.WebNewVoiceCall(ctx, apiKey, gateway.VoiceWebNewCallRequest{
			CallID:         callID,
			AgentID:        agentID,
			SDP:            sdp,
			BusinessNumber: strval(args, "business_number"),
			ContactName:    strval(args, "contact_name"),
			From:           strval(args, "from"),
			Language:       strval(args, "language"),
			Accent:         strval(args, "accent"),
			ModelProvider:  strval(args, "model_provider"),
		})
	})
}

func registerTestNewVoiceCall(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "test_new_voice_call",
		Description: "Start a test voice call without Wati tenant validation. Same parameters as web_new_voice_call. Use for development and testing.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"agent_id":       {Type: "string", Description: "Voice agent ID"},
				"call_id":        {Type: "string", Description: "Unique call ID"},
				"sdp":            {Type: "string", Description: "WebRTC SDP offer"},
				"language":       {Type: "string", Description: "Voice language"},
				"accent":         {Type: "string", Description: "Voice accent"},
				"model_provider": {Type: "string", Description: "Model provider (openai, gemini, minimax)"},
			},
			Required: []string{"agent_id", "call_id", "sdp"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		agentID, _ := args["agent_id"].(string)
		callID, _ := args["call_id"].(string)
		sdp, _ := args["sdp"].(string)
		if apiKey == "" || agentID == "" || callID == "" || sdp == "" {
			return nil, fmt.Errorf("api_key, agent_id, call_id, and sdp are required")
		}
		return gw.TestNewVoiceCall(ctx, apiKey, gateway.VoiceWebNewCallRequest{
			CallID:        callID,
			AgentID:       agentID,
			SDP:           sdp,
			Language:      strval(args, "language"),
			Accent:        strval(args, "accent"),
			ModelProvider: strval(args, "model_provider"),
		})
	})
}

func registerTerminateVoiceCall(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "terminate_voice_call",
		Description: "Terminate an active voice call. Cleanup is broadcast to all pods.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"call_id": {Type: "string", Description: "Call ID to terminate"},
			},
			Required: []string{"call_id"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		callID, _ := args["call_id"].(string)
		if apiKey == "" || callID == "" {
			return nil, fmt.Errorf("api_key and call_id are required")
		}
		return gw.TerminateVoiceCall(ctx, apiKey, gateway.VoiceTerminateCallRequest{
			CallID: callID,
		})
	})
}

func registerTestTerminateVoiceCall(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "test_terminate_voice_call",
		Description: "Terminate a test voice call by callId without calling Wati API. Use for development and testing.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"call_id": {Type: "string", Description: "Call ID to terminate"},
			},
			Required: []string{"call_id"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		callID, _ := args["call_id"].(string)
		if apiKey == "" || callID == "" {
			return nil, fmt.Errorf("api_key and call_id are required")
		}
		return gw.TestTerminateVoiceCall(ctx, apiKey, gateway.VoiceTerminateCallRequest{
			CallID: callID,
		})
	})
}

func registerInitiateOutboundCall(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "initiate_outbound_call",
		Description: "Initiate an outbound voice call to a WhatsApp user (test mode, uses Draft agent config). The user's wa_id is available in the webhook context — use it directly, do NOT ask the user for their phone number. tenant_id is auto-resolved from WATI_TENANT_ID env var.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"waid":                 {Type: "string", Description: "WhatsApp user ID — use wa_id from webhook context"},
				"channel_phone_number": {Type: "string", Description: "Business channel phone number"},
				"agent_id":             {Type: "string", Description: "Voice agent ID"},
				"voice_language":       {Type: "string", Description: "Voice language (default: en)"},
				"accent":               {Type: "string", Description: "Voice accent"},
			},
			Required: []string{"waid"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		waid, _ := args["waid"].(string)
		if apiKey == "" || waid == "" {
			return nil, fmt.Errorf("api_key and waid are required")
		}

		tenantID := os.Getenv("WATI_TENANT_ID")
		if tenantID == "" {
			return nil, fmt.Errorf("WATI_TENANT_ID env var is required for outbound calls")
		}

		return gw.InitiateOutboundCall(ctx, apiKey, gateway.VoiceInitiateOutboundRequest{
			WAID:               waid,
			ChannelPhoneNumber: strval(args, "channel_phone_number"),
			AgentID:            strval(args, "agent_id"),
			VoiceLanguage:      strval(args, "voice_language"),
			Accent:             strval(args, "accent"),
			TenantID:           tenantID,
		})
	})
}

func registerGetWebRTCConfig(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "get_webrtc_config",
		Description: "Get WebRTC configuration for frontend voice calls. Returns ICE servers (STUN/TURN) and iceCandidatePoolSize.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
			},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		if apiKey == "" {
			return nil, fmt.Errorf("api_key is required")
		}
		return gw.GetWebRTCConfig(ctx, apiKey)
	})
}

// ──────────────────────────────────────────────────────────────────────────────
// Voice ASR
// ──────────────────────────────────────────────────────────────────────────────

func registerASRTranscribe(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "asr_transcribe",
		Description: "Transcribe audio from a URL using ASR (Automatic Speech Recognition). Downloads the audio and returns the transcription text (and optional summary). Use this to convert voice messages into text before processing with a text agent. Only https/http URLs are allowed.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"url":      {Type: "string", Description: "Downloadable audio file URL (https or http only)"},
				"language": {Type: "string", Description: "Language hint (e.g. en, hi). Empty = auto-detect. Default: en"},
				"tenant":   {Type: "string", Description: "Tenant ID (required when Statsig gate is enabled)"},
			},
			Required: []string{"url"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		audioURL, _ := args["url"].(string)
		if apiKey == "" || audioURL == "" {
			return nil, fmt.Errorf("api_key and url are required")
		}
		return gw.ASRTranscribe(ctx, apiKey, gateway.VoiceASRTranscribeRequest{
			URL:      audioURL,
			Language: strval(args, "language"),
			Tenant:   strval(args, "tenant"),
		})
	})
}

// ──────────────────────────────────────────────────────────────────────────────
// Minimax Voice Clone
// ──────────────────────────────────────────────────────────────────────────────

func registerListMinimaxVoices(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "list_minimax_voices",
		Description: "List Minimax voices with optional filters. Returns a paginated list of voice records with status, language, accent, and clone info.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"voice_id":   {Type: "string", Description: "Filter by voice ID"},
				"status":     {Type: "string", Description: "Filter by status"},
				"voice_name": {Type: "string", Description: "Filter by voice name"},
				"page":       {Type: "integer", Description: "Page number (default: 1)"},
				"page_size":  {Type: "integer", Description: "Page size (default: 20)"},
			},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		if apiKey == "" {
			return nil, fmt.Errorf("api_key is required")
		}
		opts := gateway.MinimaxVoiceListOpts{
			VoiceID:   strval(args, "voice_id"),
			Status:    strval(args, "status"),
			VoiceName: strval(args, "voice_name"),
		}
		if v, ok := args["page"].(float64); ok {
			opts.Page = int(v)
		}
		if v, ok := args["page_size"].(float64); ok {
			opts.PageSize = int(v)
		}
		return gw.ListMinimaxVoices(ctx, apiKey, opts)
	})
}

func registerGetMinimaxVoice(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "get_minimax_voice",
		Description: "Check voice clone status. Auto-confirms tentative clones. When status=completed, the response contains agent_config_voice_id — you MUST call update_voice_agent(voice_agent_id, agent_config={voice_id: <agent_config_voice_id>}) to bind this voice, then publish_voice_agent. Do NOT expose agent_config_voice_id or internal details to the user.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"id":      {Type: "string", Description: "Minimax voice record ID"},
			},
			Required: []string{"id"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		id, _ := args["id"].(string)
		if apiKey == "" || id == "" {
			return nil, fmt.Errorf("api_key and id are required")
		}

		raw, err := gw.GetMinimaxVoice(ctx, apiKey, id)
		if err != nil {
			return nil, err
		}

		var voice map[string]interface{}
		if err := json.Unmarshal(raw, &voice); err != nil {
			return nil, fmt.Errorf("parse voice: %w", err)
		}

		status, _ := voice["status"].(string)

		// Auto-confirm tentative → completed so the voice becomes usable immediately.
		if status == "tentative" {
			_, confirmErr := gw.UpdateMinimaxVoice(ctx, apiKey, id, gateway.VoiceUpdateMinimaxRequest{
				Status: "completed",
			})
			if confirmErr != nil {
				slog.Warn("auto-confirm tentative voice failed", "id", id, "error", confirmErr)
			} else {
				status = "completed"
				slog.Info("auto-confirmed tentative voice", "id", id)
			}
		}

		result := map[string]interface{}{
			"voice_name": voice["voice_name"],
			"status":     status,
		}
		if status == "completed" {
			for _, key := range []string{"demo_audio", "demo_audio_url", "preview_audio_url", "preview_url", "sample_url"} {
				if preview, ok := voice[key].(string); ok && preview != "" {
					result["demo_audio"] = preview
					break
				}
			}
			// CRITICAL: this is the value that MUST be written to agent_config.voice_id
			result["agent_config_voice_id"] = id
			result["ACTION_REQUIRED"] = fmt.Sprintf(
				"Voice clone is ready. You MUST now call: update_voice_agent(voice_agent_id, agent_config={\"voice_id\": \"%s\"}), then publish_voice_agent. Do NOT skip this step.",
				id,
			)
		}
		return result, nil
	})
}

func registerUpdateMinimaxVoice(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "update_minimax_voice",
		Description: "Update a Minimax voice record. Can update voice_name, language, accent, gender, age, description, status, and category_ids.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"id":           {Type: "string", Description: "Minimax voice record ID"},
				"voice_name":   {Type: "string", Description: "Voice name"},
				"language":     {Type: "string", Description: "Language"},
				"accent":       {Type: "string", Description: "Accent"},
				"gender":       {Type: "string", Description: "Gender"},
				"age":          {Type: "string", Description: "Age"},
				"description":  {Type: "string", Description: "Description"},
				"status":       {Type: "string", Description: "Status"},
				"category_ids": {Type: "array", Description: "Category IDs"},
			},
			Required: []string{"id"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		id, _ := args["id"].(string)
		if apiKey == "" || id == "" {
			return nil, fmt.Errorf("api_key and id are required")
		}
		req := gateway.VoiceUpdateMinimaxRequest{
			VoiceName:   strval(args, "voice_name"),
			Language:    strval(args, "language"),
			Accent:      strval(args, "accent"),
			Gender:      strval(args, "gender"),
			Age:         strval(args, "age"),
			Description: strval(args, "description"),
			Status:      strval(args, "status"),
		}
		if v, ok := args["category_ids"].([]interface{}); ok {
			for _, item := range v {
				if s, ok := item.(string); ok {
					req.CategoryIDs = append(req.CategoryIDs, s)
				}
			}
		}
		return gw.UpdateMinimaxVoice(ctx, apiKey, id, req)
	})
}

func registerUploadUserVoice(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "upload_user_voice",
		Description: "Upload a user's voice audio file for voice cloning. Returns an internal reference URL for use with submit_voice_clone. Do NOT expose the returned URL to the user — just confirm the upload succeeded.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"audio_url": {Type: "string", Description: "Source URL of the audio file to download and upload"},
				"filename":  {Type: "string", Description: "Filename hint (e.g. audio.opus, audio.mp3). Default: audio.opus"},
			},
			Required: []string{"audio_url"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		audioURL, _ := args["audio_url"].(string)
		if apiKey == "" || audioURL == "" {
			return nil, fmt.Errorf("api_key and audio_url are required")
		}
		filename := strval(args, "filename")
		if filename == "" {
			filename = "audio.opus"
		}

		audioBytes, err := downloadForTool(ctx, audioURL)
		if err != nil {
			return nil, fmt.Errorf("download audio: %w", err)
		}

		result, err := gw.UploadUserVoice(ctx, apiKey, audioBytes, filename)
		if err != nil {
			return nil, err
		}

		// Extract gcs_url for internal use but present a clean response.
		var raw map[string]interface{}
		if err := json.Unmarshal(result, &raw); err != nil {
			return map[string]string{"status": "success", "message": "Audio uploaded successfully"}, nil
		}
		gcsURL, _ := raw["gcs_url"].(string)
		return map[string]interface{}{
			"status":  "success",
			"message": "Audio uploaded successfully",
			"gcs_url": gcsURL, // needed by submit_voice_clone, but do NOT show to user
		}, nil
	})
}

// downloadForTool fetches a file from URL, adding Wati Bearer token when needed.
func downloadForTool(ctx context.Context, rawURL string) ([]byte, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)
	if err != nil {
		return nil, fmt.Errorf("create request: %w", err)
	}
	if strings.Contains(rawURL, "wati.io") {
		if token := os.Getenv("WATI_TOKEN"); token != "" {
			req.Header.Set("Authorization", "Bearer "+token)
		}
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("download returned %d: %s", resp.StatusCode, string(body))
	}
	return io.ReadAll(resp.Body)
}

func registerSubmitVoiceClone(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "submit_voice_clone",
		Description: "Submit a Minimax voice clone request. Creates a new clone or re-runs an existing one (if id is provided). Requires tenant_id, voice_name, and gcs_url.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"id":                     {Type: "string", Description: "Existing voice record ID (to update and re-run clone)"},
				"tenant_id":              {Type: "string", Description: "Tenant ID (required)"},
				"voice_name":             {Type: "string", Description: "Voice name (required)"},
				"gcs_url":                {Type: "string", Description: "GCS URL of the audio file (required)"},
				"clone_model":            {Type: "string", Description: "Clone model to use"},
				"language":               {Type: "string", Description: "Language"},
				"accent":                 {Type: "string", Description: "Accent"},
				"gender":                 {Type: "string", Description: "Gender"},
				"age":                    {Type: "string", Description: "Age"},
				"description":            {Type: "string", Description: "Description"},
				"custom_text_to_preview": {Type: "string", Description: "Custom text for voice preview"},
				"category_ids":           {Type: "array", Description: "Category IDs"},
			},
			Required: []string{"tenant_id", "voice_name", "gcs_url"},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		tenantID, _ := args["tenant_id"].(string)
		voiceName, _ := args["voice_name"].(string)
		gcsURL, _ := args["gcs_url"].(string)
		if apiKey == "" || tenantID == "" || voiceName == "" || gcsURL == "" {
			return nil, fmt.Errorf("api_key, tenant_id, voice_name, and gcs_url are required")
		}
		req := gateway.VoiceSubmitCloneRequest{
			ID:                  strval(args, "id"),
			TenantID:            tenantID,
			VoiceName:           voiceName,
			GCSURL:              gcsURL,
			CloneModel:          strval(args, "clone_model"),
			Language:            strval(args, "language"),
			Accent:              strval(args, "accent"),
			Gender:              strval(args, "gender"),
			Age:                 strval(args, "age"),
			Description:         strval(args, "description"),
			CustomTextToPreview: strval(args, "custom_text_to_preview"),
		}
		if v, ok := args["category_ids"].([]interface{}); ok {
			for _, item := range v {
				if s, ok := item.(string); ok {
					req.CategoryIDs = append(req.CategoryIDs, s)
				}
			}
		}
		return gw.SubmitVoiceClone(ctx, apiKey, req)
	})
}

func registerSearchVoiceLibrary(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "search_voice_library",
		Description: "Search the Minimax voice library by keyword. Returns matching voices with pagination.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"keyword":   {Type: "string", Description: "Search keyword"},
				"page":      {Type: "integer", Description: "Page number"},
				"page_size": {Type: "integer", Description: "Page size"},
			},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		if apiKey == "" {
			return nil, fmt.Errorf("api_key is required")
		}
		opts := gateway.VoiceLibrarySearchOpts{
			Keyword: strval(args, "keyword"),
		}
		if v, ok := args["page"].(float64); ok {
			opts.Page = int(v)
		}
		if v, ok := args["page_size"].(float64); ok {
			opts.PageSize = int(v)
		}
		return gw.SearchVoiceLibrary(ctx, apiKey, opts)
	})
}

func registerSearchCollectedVoices(s *mcp.Server, gw *gateway.Client) {
	s.RegisterTool(mcp.Tool{
		Name:        "search_collected_voices",
		Description: "Search collected (favorited) voices, optionally filtered by tenant_id.",
		InputSchema: mcp.InputSchema{
			Type: "object",
			Properties: map[string]mcp.Property{
				"tenant_id": {Type: "string", Description: "Filter by tenant ID"},
			},
		},
	}, func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
		apiKey := apiKeyFromArgs(args)
		if apiKey == "" {
			return nil, fmt.Errorf("api_key is required")
		}
		return gw.SearchCollectedVoices(ctx, apiKey, strval(args, "tenant_id"))
	})
}
