package taskboard

import (
	"encoding/json"
	"fmt"
	"strings"
	"unicode/utf8"
)

const (
	taskMutationMaxCreateItems              = 20
	taskMutationMaxChecklistItems           = 50
	taskMutationMaxContextRefs              = 50
	taskMutationMaxReviewQuestions          = 20
	taskMutationMaxJSONDepth                = 8
	taskMutationMaxJSONListItems            = 50
	taskMutationMaxJSONObjectKeys           = 50
	taskMutationMaxTitleRunes               = 512
	taskMutationMaxDescriptionRunes         = 8192
	taskMutationMaxShortStringRunes         = 512
	taskMutationMaxChecklistTextRunes       = 2048
	taskMutationMaxContextRefRunes          = 1024
	taskMutationMaxReviewSummaryRunes       = 4096
	taskMutationMaxReviewQuestionRunes      = 1024
	taskMutationMaxReviewTargetTypeRunes    = 128
	taskMutationMaxReviewTargetPayloadBytes = 4096

	taskDetailContextRefLimit              = taskDetailSectionDefaultLimit
	taskReviewTargetPayloadSummaryMaxRunes = 512
)

func validateTaskCreateInputs(inputs []TaskCreateInput) error {
	if len(inputs) > taskMutationMaxCreateItems {
		return badRequest(fmt.Sprintf("tasks must contain at most %d items", taskMutationMaxCreateItems))
	}
	for index, input := range inputs {
		if err := validateTaskCreateInput(input, fmt.Sprintf("tasks[%d]", index)); err != nil {
			return err
		}
	}
	return nil
}

func validateTaskCreateInput(input TaskCreateInput, field string) error {
	if err := validateTaskStringRunes(input.Title, field+".title", taskMutationMaxTitleRunes); err != nil {
		return err
	}
	if err := validateTaskStringRunes(input.Description, field+".description", taskMutationMaxDescriptionRunes); err != nil {
		return err
	}
	for name, value := range map[string]string{
		"kind":             input.Kind,
		"priority":         input.Priority,
		"kindOptionId":     input.KindOptionID,
		"priorityOptionId": input.PriorityOptionID,
		"assigneeRef":      input.AssigneeRef,
	} {
		if err := validateTaskStringRunes(value, field+"."+name, taskMutationMaxShortStringRunes); err != nil {
			return err
		}
	}
	return nil
}

func validateTaskUpdatePatch(patch TaskUpdatePatch) error {
	if err := validateOptionalTaskStringRunes(patch.Title, "title", taskMutationMaxTitleRunes); err != nil {
		return err
	}
	if err := validateOptionalTaskStringRunes(patch.Description, "description", taskMutationMaxDescriptionRunes); err != nil {
		return err
	}
	for name, value := range map[string]*string{
		"kind":             patch.Kind,
		"priority":         patch.Priority,
		"kindOptionId":     patch.KindOptionID,
		"priorityOptionId": patch.PriorityOptionID,
		"assigneeRef":      patch.AssigneeRef,
		"blockedReason":    patch.BlockedReason,
		"parentTaskRef":    patch.ParentTaskRef,
	} {
		if err := validateOptionalTaskStringRunes(value, name, taskMutationMaxShortStringRunes); err != nil {
			return err
		}
	}
	if err := validateTaskChecklistMutationItems(patch.AcceptanceCriteria, "acceptanceCriteria"); err != nil {
		return err
	}
	if err := validateTaskChecklistMutationItems(patch.ReviewChecklist, "reviewChecklist"); err != nil {
		return err
	}
	if err := validateTaskStringList(patch.PinnedMessageRefs, "pinnedMessageRefs", taskMutationMaxContextRefs, taskMutationMaxContextRefRunes); err != nil {
		return err
	}
	if err := validateTaskStringList(patch.RelevantURLs, "relevantURLs", taskMutationMaxContextRefs, taskMutationMaxContextRefRunes); err != nil {
		return err
	}
	if err := validateTaskStringList(patch.WorkspaceRefs, "workspaceRefs", taskMutationMaxContextRefs, taskMutationMaxContextRefRunes); err != nil {
		return err
	}
	if err := validateTaskStringList(patch.AttachmentIDs, "attachmentIds", taskMutationMaxContextRefs, taskMutationMaxContextRefRunes); err != nil {
		return err
	}
	return nil
}

func validateTaskBlockInput(input TaskBlockInput) error {
	if strings.TrimSpace(input.Reason) == "" {
		return badRequest("reason is required")
	}
	return validateTaskStringRunes(input.Reason, "reason", taskMutationMaxShortStringRunes)
}

func validateTaskCheckpointInput(input TaskCheckpointInput) error {
	if strings.TrimSpace(input.Text) == "" {
		return badRequest("checkpoint text is required")
	}
	return validateTaskStringRunes(input.Text, "checkpoint text", taskMutationMaxChecklistTextRunes)
}

func validateTaskChecklistMutationItems(items []TaskChecklistItem, field string) error {
	if len(items) > taskMutationMaxChecklistItems {
		return badRequest(fmt.Sprintf("%s must contain at most %d items", field, taskMutationMaxChecklistItems))
	}
	for index, item := range items {
		prefix := fmt.Sprintf("%s[%d]", field, index)
		if err := validateTaskStringRunes(item.ID, prefix+".id", taskMutationMaxShortStringRunes); err != nil {
			return err
		}
		if err := validateTaskStringRunes(item.Text, prefix+".text", taskMutationMaxChecklistTextRunes); err != nil {
			return err
		}
	}
	return nil
}

func validateTaskReviewRequestInput(input TaskReviewRequestInput) error {
	if err := validateTaskStringRunes(input.ReviewerRef, "reviewerRef", taskMutationMaxShortStringRunes); err != nil {
		return err
	}
	if err := validateTaskStringRunes(input.Summary, "summary", taskMutationMaxReviewSummaryRunes); err != nil {
		return err
	}
	if err := validateTaskStringRunes(input.ReviewTargetType, "reviewTargetType", taskMutationMaxReviewTargetTypeRunes); err != nil {
		return err
	}
	if len(input.Questions) > taskMutationMaxReviewQuestions {
		return badRequest(fmt.Sprintf("questions must contain at most %d items", taskMutationMaxReviewQuestions))
	}
	for index, question := range input.Questions {
		if err := validateTaskStringRunes(question, fmt.Sprintf("questions[%d]", index), taskMutationMaxReviewQuestionRunes); err != nil {
			return err
		}
	}
	return validateTaskReviewTargetPayload(input.ReviewTargetPayload)
}

func validateTaskReviewDecisionInput(input TaskReviewDecisionInput) error {
	if strings.TrimSpace(input.ReviewID) == "" {
		return badRequest("reviewId is required")
	}
	if err := validateTaskStringRunes(input.ReviewID, "reviewId", taskMutationMaxShortStringRunes); err != nil {
		return err
	}
	if err := validateTaskStringRunes(input.Decision, "decision", taskMutationMaxReviewSummaryRunes); err != nil {
		return err
	}
	if err := validateTaskStringRunes(input.Summary, "summary", taskMutationMaxReviewSummaryRunes); err != nil {
		return err
	}
	return validateTaskStringList(input.Questions, "questions", taskMutationMaxReviewQuestions, taskMutationMaxReviewQuestionRunes)
}

func validateTaskReviewTargetPayload(payload string) error {
	return validateTaskJSONDocumentPayload(payload, "reviewTargetPayload", taskMutationMaxReviewTargetPayloadBytes)
}

func validateTaskJSONDocumentPayload(payload string, field string, maxBytes int) error {
	trimmed := strings.TrimSpace(payload)
	if trimmed == "" {
		return nil
	}
	if len([]byte(trimmed)) > maxBytes {
		return badRequest(fmt.Sprintf("%s must be at most %d bytes", field, maxBytes))
	}
	var value any
	if err := json.Unmarshal([]byte(trimmed), &value); err != nil {
		return badRequest(fmt.Sprintf("invalid %s JSON", field))
	}
	return validateTaskJSONValue(value, field, 0)
}

func validateTaskJSONValue(value any, field string, depth int) error {
	if depth > taskMutationMaxJSONDepth {
		return badRequest(fmt.Sprintf("%s exceeds maximum JSON depth %d", field, taskMutationMaxJSONDepth))
	}
	switch typed := value.(type) {
	case map[string]any:
		if len(typed) > taskMutationMaxJSONObjectKeys {
			return badRequest(fmt.Sprintf("%s must contain at most %d keys", field, taskMutationMaxJSONObjectKeys))
		}
		for key, child := range typed {
			if err := validateTaskStringRunes(key, field+" key", taskMutationMaxShortStringRunes); err != nil {
				return err
			}
			if err := validateTaskJSONValue(child, field+"."+key, depth+1); err != nil {
				return err
			}
		}
	case []any:
		if len(typed) > taskMutationMaxJSONListItems {
			return badRequest(fmt.Sprintf("%s must contain at most %d items", field, taskMutationMaxJSONListItems))
		}
		for index, child := range typed {
			if err := validateTaskJSONValue(child, fmt.Sprintf("%s[%d]", field, index), depth+1); err != nil {
				return err
			}
		}
	case string:
		return validateTaskStringRunes(typed, field, taskMutationMaxChecklistTextRunes)
	}
	return nil
}

func validateTaskStringList(values []string, field string, maxItems int, maxRunes int) error {
	if len(values) > maxItems {
		return badRequest(fmt.Sprintf("%s must contain at most %d items", field, maxItems))
	}
	for index, value := range values {
		if err := validateTaskStringRunes(value, fmt.Sprintf("%s[%d]", field, index), maxRunes); err != nil {
			return err
		}
	}
	return nil
}

func validateOptionalTaskStringRunes(value *string, field string, maxRunes int) error {
	if value == nil {
		return nil
	}
	return validateTaskStringRunes(*value, field, maxRunes)
}

func validateTaskStringRunes(value string, field string, maxRunes int) error {
	if maxRunes <= 0 || utf8.RuneCountInString(strings.TrimSpace(value)) <= maxRunes {
		return nil
	}
	return badRequest(fmt.Sprintf("%s must be at most %d characters", field, maxRunes))
}

func capTaskReviewQuestions(values []string) []string {
	capped := values
	if len(capped) > taskMutationMaxReviewQuestions {
		capped = capped[:taskMutationMaxReviewQuestions]
	}
	out := make([]string, 0, len(capped))
	for _, value := range capped {
		out = append(out, trimTaskRunes(value, taskMutationMaxReviewQuestionRunes))
	}
	return out
}

func trimTaskRunes(value string, maxRunes int) string {
	trimmed := strings.TrimSpace(value)
	if maxRunes <= 0 {
		return trimmed
	}
	runes := []rune(trimmed)
	if len(runes) <= maxRunes {
		return trimmed
	}
	return strings.TrimSpace(string(runes[:maxRunes])) + "..."
}
