package taskboard

import (
	"context"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
)

func normalizeChecklistItems(items []TaskChecklistItem) ([]TaskChecklistItem, error) {
	if items == nil {
		return nil, nil
	}
	normalized := make([]TaskChecklistItem, 0, len(items))
	for _, item := range items {
		text := strings.TrimSpace(item.Text)
		if text == "" {
			return nil, badRequest("task checklist item text is required")
		}
		normalized = append(normalized, TaskChecklistItem{
			Text:    text,
			Checked: item.Checked,
		})
	}
	return normalized, nil
}

func normalizeOptionalChannelMemberRef(value string, fieldName string) (string, error) {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return "", nil
	}
	normalized := strings.TrimPrefix(trimmed, "@")
	if normalized == "" {
		return "", badRequest(fieldName + " must reference a channel member")
	}
	return normalized, nil
}

func resolveOptionalChannelMemberRef(ctx context.Context, queryer coredb.Queryer, workspaceID string, channelID string, actorMemberID string, value string, fieldName string, agentOnly bool) (string, error) {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return "", nil
	}
	ref := strings.TrimSpace(strings.TrimPrefix(trimmed, "@"))
	if ref == "" {
		if agentOnly {
			return "", badRequest(fieldName + " must reference a channel agent")
		}
		return "", badRequest(fieldName + " must reference a channel member")
	}
	page, err := loadTaskMemberOptions(ctx, queryer, workspaceID, channelID, actorMemberID, taskMemberLookup{
		Role:  mapTaskMemberLookupRole(agentOnly),
		Query: ref,
		Limit: 2,
		Exact: true,
	})
	if err != nil {
		return "", err
	}
	lookup := strings.ToLower(ref)
	matches := make(map[string]TaskMemberOption)
	for _, option := range page.options {
		if agentOnly && strings.TrimSpace(option.Type) != memberTypeAgent {
			continue
		}
		keys := []string{
			option.MemberID,
			option.UserID,
			option.AgentID,
			option.Name,
		}
		for _, key := range keys {
			if strings.ToLower(strings.TrimSpace(key)) == lookup {
				matches[option.MemberID] = option
				break
			}
		}
	}
	if len(matches) == 0 {
		if agentOnly {
			return "", badRequest(fieldName + " must reference a channel agent")
		}
		return "", badRequest(fieldName + " must reference a channel member")
	}
	if len(matches) > 1 {
		return "", badRequest(fieldName + " is ambiguous; use an exact memberId")
	}
	for memberID := range matches {
		return memberID, nil
	}
	return "", nil
}

func mapTaskMemberLookupRole(agentOnly bool) string {
	if agentOnly {
		return "agent"
	}
	return ""
}

func resolveOptionalTaskAssigneeRef(ctx context.Context, queryer coredb.Queryer, workspaceID string, channelID string, actorMemberID string, value string) (string, error) {
	resolved, err := resolveOptionalChannelMemberRef(ctx, queryer, workspaceID, channelID, actorMemberID, value, "assigneeRef", false)
	if err != nil || resolved == "" {
		return resolved, err
	}
	row := queryer.QueryRow(ctx, `
SELECT type
FROM members
WHERE workspace_id = $1
  AND id = $2
  AND status = 'active'
`, strings.TrimSpace(workspaceID), resolved)
	var memberType string
	if err := row.Scan(&memberType); err != nil {
		if coredb.IsNotFound(err) {
			return "", badRequest("assigneeRef must reference an active channel human or agent")
		}
		return "", fmt.Errorf("load task assignee member type: %w", err)
	}
	if memberType != memberTypeHuman && memberType != memberTypeAgent {
		return "", badRequest("assigneeRef must reference a channel human or agent")
	}
	return resolved, nil
}

func resolveOptionalTaskAssigneeRefPointer(ctx context.Context, queryer coredb.Queryer, workspaceID string, channelID string, actorMemberID string, value *string) (*string, error) {
	if value == nil {
		return nil, nil
	}
	resolved, err := resolveOptionalTaskAssigneeRef(ctx, queryer, workspaceID, channelID, actorMemberID, *value)
	if err != nil {
		return nil, err
	}
	return &resolved, nil
}

func normalizeStringList(values []string) []string {
	if values == nil {
		return nil
	}
	normalized := make([]string, 0, len(values))
	for _, value := range values {
		trimmed := strings.TrimSpace(value)
		if trimmed == "" {
			continue
		}
		normalized = append(normalized, trimmed)
	}
	return normalized
}

func marshalStringList(values []string) ([]byte, error) {
	encoded, err := model.MarshalTaskStringList(normalizeStringList(values))
	if err != nil {
		return nil, fmt.Errorf("marshal string list: %w", err)
	}
	return encoded, nil
}

func unmarshalStringList(payload []byte) ([]string, error) {
	if len(payload) == 0 {
		return []string{}, nil
	}
	values, err := model.UnmarshalTaskStringList(payload)
	if err != nil {
		return nil, fmt.Errorf("decode task string list: %w", err)
	}
	return values, nil
}

func normalizeJSONDocument(value string, fallback string) ([]byte, error) {
	document, ok := model.NormalizeJSONDocument(value, fallback)
	if !ok {
		return nil, badRequest("invalid reviewTargetPayload JSON")
	}
	return document, nil
}

func normalizeOptionalString(value *string) *string {
	if value == nil {
		return nil
	}
	trimmed := strings.TrimSpace(*value)
	return &trimmed
}

func normalizeOptionalStringList(values []string) []string {
	if values == nil {
		return nil
	}

	normalized := make([]string, 0, len(values))
	for _, value := range values {
		trimmed := strings.TrimSpace(value)
		if trimmed == "" {
			continue
		}
		normalized = append(normalized, trimmed)
	}
	return normalized
}

func marshalStringListJSON(values []string) (string, error) {
	normalized := values
	if normalized == nil {
		normalized = []string{}
	}
	blob, err := model.MarshalTaskStringListString(normalized)
	if err != nil {
		return "", fmt.Errorf("marshal string list json: %w", err)
	}
	return blob, nil
}

func taskboardJSONParamExpression(runtime any, param int) string {
	if coredb.IsTiDB(runtime) {
		return fmt.Sprintf("$%d", param)
	}
	return fmt.Sprintf("$%d::jsonb", param)
}

func valueOrEmpty(value *string) string {
	if value == nil {
		return ""
	}
	return *value
}
