package invocation

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	invocationlang "github.com/tidbcloud/AgentTeam/internal/workflow/invocation"
)

const (
	maxWorkflowInvocationRequestBytes = int64(invocationlang.MaxInputBytes + 4096)
	maxWorkflowInvocationJSONNesting  = 64
)

var (
	errMultipleInvocationJSONValues = errors.New("multiple JSON values")
	errDuplicateInvocationJSONKey   = errors.New("duplicate JSON object key")
	errInvocationJSONTooDeep        = errors.New("JSON nesting exceeds limit")
	errInvocationJSONObjectRequired = errors.New("JSON request body must be an object")
)

func decodeWorkflowInvocationCreateJSON(ctx *gin.Context) (dto.WorkflowInvocationCreateRequest, error) {
	if ctx == nil || ctx.Request == nil || ctx.Request.Body == nil {
		return dto.WorkflowInvocationCreateRequest{}, invocationJSONError(io.EOF)
	}
	ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, maxWorkflowInvocationRequestBytes)
	payload, err := io.ReadAll(ctx.Request.Body)
	if err != nil {
		return dto.WorkflowInvocationCreateRequest{}, invocationJSONError(err)
	}
	return decodeWorkflowInvocationCreateJSONBytes(payload)
}

func decodeWorkflowInvocationCreateJSONBytes(payload []byte) (dto.WorkflowInvocationCreateRequest, error) {
	var body dto.WorkflowInvocationCreateRequest
	if err := validateInvocationJSON(payload); err != nil {
		return body, invocationJSONError(err)
	}
	decoder := json.NewDecoder(bytes.NewReader(payload))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&body); err != nil {
		return body, invocationJSONError(err)
	}
	var trailing any
	if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) {
		if err == nil {
			err = errMultipleInvocationJSONValues
		}
		return body, invocationJSONError(err)
	}
	return body, nil
}

func invocationJSONError(err error) error {
	var maxBytesError *http.MaxBytesError
	if errors.As(err, &maxBytesError) {
		return publicError(http.StatusRequestEntityTooLarge, "invocation_request_too_large", "invalid request body")
	}
	return publicError(http.StatusBadRequest, "invocation_invalid_json", "invalid request body")
}

func validateInvocationJSON(payload []byte) error {
	decoder := json.NewDecoder(bytes.NewReader(payload))
	decoder.UseNumber()
	first, err := decoder.Token()
	if err != nil {
		return err
	}
	if delimiter, ok := first.(json.Delim); !ok || delimiter != '{' {
		return errInvocationJSONObjectRequired
	}
	if err := validateInvocationJSONToken(decoder, first, 0); err != nil {
		return err
	}
	if _, err := decoder.Token(); !errors.Is(err, io.EOF) {
		if err == nil {
			return errMultipleInvocationJSONValues
		}
		return err
	}
	return nil
}

func validateInvocationJSONToken(decoder *json.Decoder, token json.Token, depth int) error {
	delimiter, composite := token.(json.Delim)
	if !composite {
		return nil
	}
	if depth >= maxWorkflowInvocationJSONNesting {
		return errInvocationJSONTooDeep
	}
	switch delimiter {
	case '{':
		keys := map[string]struct{}{}
		for decoder.More() {
			keyToken, err := decoder.Token()
			if err != nil {
				return err
			}
			key, ok := keyToken.(string)
			if !ok {
				return fmt.Errorf("JSON object key is not a string")
			}
			if _, duplicate := keys[key]; duplicate {
				return fmt.Errorf("%w: %q", errDuplicateInvocationJSONKey, key)
			}
			if depth == 0 && key != "input" && key != "planVersionId" {
				return fmt.Errorf("unknown JSON field %q", key)
			}
			keys[key] = struct{}{}
			value, err := decoder.Token()
			if err != nil {
				return err
			}
			if depth == 0 && key == "planVersionId" {
				versionID, ok := value.(string)
				if !ok || strings.TrimSpace(versionID) == "" {
					return fmt.Errorf("planVersionId must be a non-empty string when provided")
				}
			}
			if err := validateInvocationJSONToken(decoder, value, depth+1); err != nil {
				return err
			}
		}
		closing, err := decoder.Token()
		if err != nil {
			return err
		}
		if closing != json.Delim('}') {
			return fmt.Errorf("JSON object is not closed")
		}
	case '[':
		for decoder.More() {
			value, err := decoder.Token()
			if err != nil {
				return err
			}
			if err := validateInvocationJSONToken(decoder, value, depth+1); err != nil {
				return err
			}
		}
		closing, err := decoder.Token()
		if err != nil {
			return err
		}
		if closing != json.Delim(']') {
			return fmt.Errorf("JSON array is not closed")
		}
	default:
		return fmt.Errorf("unexpected JSON delimiter %q", delimiter)
	}
	return nil
}
