package server

import (
	"context"
	"encoding/json"
	"fmt"
	"net/url"
	"reflect"
	"strconv"
	"strings"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	"github.com/scaleway/scaleway-cli/v2/core"
	"github.com/scaleway/scaleway-cli/v2/internal/args"
	"github.com/scaleway/scaleway-sdk-go/strcase"
)

// CommandResource wraps a CLI command for MCP resource exposure
type CommandResource struct {
	Command *core.Command
}

// NewCommandResource creates a new CommandResource from a core.Command
func NewCommandResource(cmd *core.Command) *CommandResource {
	return &CommandResource{
		Command: cmd,
	}
}

// ToMCPResource converts the CommandResource to an MCP Resource
func (cr *CommandResource) ToMCPResource() *mcp.Resource {
	// Build URI: scw://{namespace}/{resource}
	uri := BuildResourceURI(cr.Command.Namespace, cr.Command.Resource)

	resource := &mcp.Resource{
		URI:         uri,
		Name:        ResourceName(cr.Command),
		Description: cr.Command.Short,
		MIMEType:    "application/json",
	}

	// Add command metadata in description for debugging
	if cr.Command.Long != "" {
		resource.Description = cr.Command.Short + "\n\n" + truncateString(cr.Command.Long, 200)
	}

	return resource
}

// ResourceName generates a human-readable name for the resource
func ResourceName(cmd *core.Command) string {
	parts := []string{}

	if cmd.Namespace != "" {
		parts = append(parts, cmd.Namespace)
	}
	if cmd.Resource != "" {
		parts = append(parts, cmd.Resource)
	}

	return strings.Join(parts, " ")
}

// BuildResourceURI creates a URI for a namespace/resource combination
func BuildResourceURI(namespace, resource string) string {
	return fmt.Sprintf("scw://%s/%s", namespace, resource)
}

// Execute runs the CLI command with the provided arguments and returns resource contents
func (cr *CommandResource) Execute(
	ctx context.Context,
	inputArgs map[string]any,
) (*mcp.ReadResourceResult, error) {
	// Ensure meta is available in context
	// If meta is already in context (from wrapper), this is a no-op
	// If not, this creates meta from environment/config (useful for tests)
	ctx, err := ensureMetaInContext(ctx, nil)
	if err != nil {
		return nil, err
	}
	// Verify client was successfully injected
	if core.ExtractClient(ctx) == nil {
		return &mcp.ReadResourceResult{
			Contents: []*mcp.ResourceContents{
				{
					URI:  BuildResourceURI(cr.Command.Namespace, cr.Command.Resource),
					Text: "Error: client not initialized - check SCW credentials (access key, secret key, or profile)",
				},
			},
		}, nil
	}

	// Skip commands without a Run function
	if cr.Command.Run == nil {
		return &mcp.ReadResourceResult{
			Contents: []*mcp.ResourceContents{
				{
					URI:  BuildResourceURI(cr.Command.Namespace, cr.Command.Resource),
					Text: "Command has no execution logic",
				},
			},
		}, nil
	}

	// Create a new instance of the expected args type
	var cmdArgs any
	if cr.Command.ArgsType != nil {
		cmdArgs = reflect.New(cr.Command.ArgsType).Interface()

		// Convert map[string]any to []string format expected by args.UnmarshalStruct
		// Format: ["arg1=value1", "arg2=value2"]
		rawArgs := make([]string, 0, len(inputArgs))
		for argName, argValue := range inputArgs {
			// Convert from kebab-case back to original arg spec name
			originalName := argName
			for _, spec := range cr.Command.ArgSpecs {
				if strcase.ToKebab(spec.Name) == argName {
					originalName = spec.Name

					break
				}
			}

			// Convert value to string
			var valueStr string
			switch v := argValue.(type) {
			case string:
				valueStr = v
			case bool:
				valueStr = strconv.FormatBool(v)
			case float64:
				valueStr = fmt.Sprintf("%v", v)
			case int:
				valueStr = strconv.Itoa(v)
			default:
				// For complex types, marshal to JSON
				if b, marshalErr := json.Marshal(v); marshalErr == nil {
					valueStr = string(b)
				} else {
					valueStr = fmt.Sprintf("%v", v)
				}
			}

			rawArgs = append(rawArgs, originalName+"="+valueStr)
		}

		// Unmarshal the args into the struct
		if unmarshalErr := args.UnmarshalStruct(rawArgs, cmdArgs); unmarshalErr != nil {
			return &mcp.ReadResourceResult{
				Contents: []*mcp.ResourceContents{
					{
						URI:  BuildResourceURI(cr.Command.Namespace, cr.Command.Resource),
						Text: fmt.Sprintf("Error parsing arguments: %v", unmarshalErr),
					},
				},
			}, nil
		}
	} else {
		// Fallback for commands without ArgsType
		cmdArgs = inputArgs
	}

	// Execute the command's Run function using the interceptor chain
	// This ensures custom request wrappers (like customListServersRequest) are properly handled
	var runner core.CommandRunner = func(ctx context.Context, argsI any) (i any, err error) {
		return cr.Command.Run(ctx, argsI)
	}

	// Apply command interceptor if present
	var result any
	var execErr error
	if cr.Command.Interceptor != nil {
		result, execErr = cr.Command.Interceptor(ctx, cmdArgs, runner)
	} else {
		result, execErr = runner(ctx, cmdArgs)
	}

	if execErr != nil {
		return &mcp.ReadResourceResult{
			Contents: []*mcp.ResourceContents{
				{
					URI:  BuildResourceURI(cr.Command.Namespace, cr.Command.Resource),
					Text: fmt.Sprintf("Error: %v", execErr),
				},
			},
		}, nil
	}

	// Format the result
	var resultStr string
	if result != nil {
		if data, err := json.MarshalIndent(result, "", "  "); err == nil {
			resultStr = string(data)
		} else {
			resultStr = fmt.Sprintf("%v", result)
		}
	} else {
		resultStr = "Command executed successfully (no output)"
	}

	return &mcp.ReadResourceResult{
		Contents: []*mcp.ResourceContents{
			{
				URI:      BuildResourceURI(cr.Command.Namespace, cr.Command.Resource),
				MIMEType: "application/json",
				Text:     resultStr,
			},
		},
	}, nil
}

// LoadResource registers a CLI command as an MCP resource
// Meta is expected to be already present in the context.
func (s *MCPServer) LoadResource(cmd *core.Command) error {
	resource := NewCommandResource(cmd)

	mcpResource := resource.ToMCPResource()

	// Create a handler function for the resource
	handler := func(ctx context.Context, req *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
		// Extract arguments from the request URI
		// URI format: scw://namespace/resource?arg1=value1&arg2=value2
		inputArgs := parseURIToArgs(req.Params.URI)

		result, err := resource.Execute(ctx, inputArgs)
		if err != nil {
			return nil, err
		}

		return result, nil
	}

	// Register with MCP SDK
	s.server.AddResource(mcpResource, handler)

	s.resources = append(s.resources, resource)

	return nil
}

// parseURIToArgs extracts query parameters from a URI and converts them to input args
func parseURIToArgs(uri string) map[string]any {
	args := make(map[string]any)

	// Parse URI query parameters using url.ParseQuery for proper handling
	// Format: scw://namespace/resource?key1=value1&key2=value2
	parts := strings.SplitN(uri, "?", 2)
	if len(parts) != 2 {
		return args
	}

	query := parts[1]
	params, err := url.ParseQuery(query)
	if err != nil {
		return args
	}

	for key, values := range params {
		if len(values) == 0 {
			continue
		}
		value := values[0] // Use first value for each key

		// Try to parse the value as different types
		if value == "true" {
			args[key] = true
		} else if value == "false" {
			args[key] = false
		} else if num, err := strconv.ParseFloat(value, 64); err == nil {
			args[key] = num
		} else {
			args[key] = value
		}
	}

	return args
}
