package mcpserver

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
	"unicode"

	"github.com/tidbcloud/aisupport/apps/cli/operation-mcp/devops"
)

// Field names the generated per-operation tools reserve for themselves.
const (
	fieldReason          = "reason"
	fieldStartPolicy     = "start_policy"
	fieldStartScheduleAt = "start_schedule_at"
)

var reservedFields = []string{fieldReason, fieldStartPolicy, fieldStartScheduleAt}

// toolNameFor is the MCP tool name generated for an allowlisted operation.
func toolNameFor(operation string) string {
	return "submit_" + snakeCase(operation)
}

// snakeCase turns an operation name such as RestartChangefeed into
// restart_changefeed.
func snakeCase(s string) string {
	runes := []rune(s)
	var b strings.Builder
	for i, r := range runes {
		if unicode.IsUpper(r) && i > 0 {
			prev := runes[i-1]
			nextIsLower := i+1 < len(runes) && unicode.IsLower(runes[i+1])
			if unicode.IsLower(prev) || unicode.IsDigit(prev) || (unicode.IsUpper(prev) && nextIsLower) {
				b.WriteRune('_')
			}
		}
		b.WriteRune(unicode.ToLower(r))
	}
	return b.String()
}

// operationInputSchema builds the JSON schema of a generated per-operation
// tool: the operation's own params, flattened, plus the submission fields.
func operationInputSchema(op *devops.Operation) (map[string]any, error) {
	properties := map[string]any{
		fieldReason: map[string]any{
			"type": "string",
			"description": "Why this operation is needed. Shown verbatim to the human approver, " +
				"so include the alert or ticket ID and a one-line justification.",
		},
		fieldStartPolicy: map[string]any{
			"type":    "string",
			"enum":    []string{string(devops.StartPolicyImmediate), string(devops.StartPolicyManual), string(devops.StartPolicySchedule)},
			"default": string(devops.DefaultStartPolicy),
			"description": "When the actions start once the operation is approved. " +
				"`immediate` runs right after approval, `manual` waits for a human to press Start in Ops Portal, " +
				"`schedule` waits until start_schedule_at.",
		},
		fieldStartScheduleAt: map[string]any{
			"type":        "integer",
			"description": "Unix timestamp in seconds; required when start_policy is `schedule`.",
		},
	}
	required := []string{fieldReason}

	names := make([]string, 0, len(op.Params))
	for name := range op.Params {
		names = append(names, name)
	}
	sort.Strings(names)

	for _, name := range names {
		def := op.Params[name]
		if def.Const != nil {
			// Fixed by the server; injected on submit, not caller input.
			continue
		}
		if _, taken := properties[name]; taken {
			return nil, fmt.Errorf("operation %s has a param named %q which collides with a reserved field (%s); "+
				"use the generic submit_operation tool for it", op.Name, name, strings.Join(reservedFields, ", "))
		}
		properties[name] = paramSchema(def)
		if !def.Optional && def.Default == nil {
			required = append(required, name)
		}
	}

	return map[string]any{
		"type":                 "object",
		"properties":           properties,
		"required":             required,
		"additionalProperties": false,
	}, nil
}

// paramSchema maps a DevOps param definition onto a JSON schema property.
func paramSchema(def devops.ParamDefine) map[string]any {
	schema := map[string]any{}

	switch def.Type {
	case devops.ParamTypeInt:
		schema["type"] = "integer"
	case devops.ParamTypeMap:
		schema["type"] = "object"
		schema["additionalProperties"] = map[string]any{"type": "string"}
	default:
		schema["type"] = "string"
	}

	descriptions := make([]string, 0, 2)
	if def.Desc != "" {
		descriptions = append(descriptions, def.Desc)
	}
	if len(def.Enum) > 0 {
		schema["enum"] = def.Enum
	}
	if len(def.MultiEnum) > 0 {
		descriptions = append(descriptions,
			"Comma-separated subset of: "+strings.Join(def.MultiEnum, ", ")+".")
	}
	if def.Default != nil {
		if def.Type == devops.ParamTypeInt {
			if n, err := strconv.ParseInt(*def.Default, 10, 64); err == nil {
				schema["default"] = n
			}
		} else {
			schema["default"] = *def.Default
		}
	}
	if len(descriptions) > 0 {
		schema["description"] = strings.Join(descriptions, " ")
	}

	return schema
}
