package agent

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

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

const (
	agentWorkflowBindingMaxEnvelopeBytes = int64(2 << 20)
	agentWorkflowBindingMaxInputBytes    = int64(1 << 20)
	agentWorkflowBindingMaxJSONNesting   = 64
)

func decodeAgentWorkflowBindingJSONBytes[T any](payload []byte) (T, error) {
	return decodeWorkflowCLIJSONBytes[T](
		payload, agentWorkflowBindingMaxInputBytes, agentWorkflowBindingMaxJSONNesting, agentWorkflowBindingJSONError,
	)
}

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

func requiredAgentPlanID(raw string) (string, error) {
	planID := strings.TrimSpace(raw)
	if planID == "" {
		return "", publicError(http.StatusBadRequest, "binding_invalid_request", "planId is required")
	}
	return planID, nil
}

func requiredAgentRevisionSpec(planVersionID string, source json.RawMessage, inputPolicy json.RawMessage) (string, json.RawMessage, json.RawMessage, error) {
	planVersionID = strings.TrimSpace(planVersionID)
	if planVersionID == "" {
		return "", nil, nil, publicError(http.StatusBadRequest, "binding_invalid_request", "planVersionId is required")
	}
	if len(bytes.TrimSpace(source)) == 0 || bytes.Equal(bytes.TrimSpace(source), []byte("null")) {
		return "", nil, nil, publicError(http.StatusBadRequest, "binding_invalid_request", "source is required")
	}
	if len(bytes.TrimSpace(inputPolicy)) == 0 || bytes.Equal(bytes.TrimSpace(inputPolicy), []byte("null")) {
		inputPolicy = json.RawMessage(`{}`)
	}
	return planVersionID, source, inputPolicy, nil
}

func agentBindingOK(binding binding.Binding) api.Response {
	return api.Response{Status: http.StatusOK, Body: agentBindingResponse(binding), Headers: http.Header{
		"ETag": []string{agentBindingETag(binding.ID, binding.Revision)},
	}}
}

func agentTriggerShowOK(binding binding.Binding, revision binding.Revision) api.Response {
	return api.Response{
		Status: http.StatusOK,
		Body: map[string]any{
			"trigger":  agentBindingResponse(binding),
			"revision": workflowTriggerRevisionResponse(agentBindingRevisionResponse(revision)),
		},
		Headers: http.Header{
			"ETag":                    []string{agentBindingETag(binding.ID, binding.Revision)},
			"X-Trigger-Revision-ETag": []string{agentBindingRevisionETag(revision.ID, revision.Revision, revision.Digest)},
		},
	}
}

func agentBindingRevisionMutationResponse(status int, binding binding.Binding, revision binding.Revision) api.Response {
	return api.Response{
		Status: status,
		Body: map[string]any{
			"trigger":  agentBindingResponse(binding),
			"revision": workflowTriggerRevisionResponse(agentBindingRevisionResponse(revision)),
		},
		Headers: http.Header{
			"ETag":                    []string{agentBindingETag(binding.ID, binding.Revision)},
			"X-Binding-Revision-ETag": []string{agentBindingRevisionETag(revision.ID, revision.Revision, revision.Digest)},
		},
	}
}

func workflowTriggerRevisionResponse(value dto.WorkflowBindingRevisionResponse) map[string]any {
	return map[string]any{
		"id": value.ID, "triggerId": value.BindingID, "planId": value.PlanID,
		"revision": value.Revision, "planVersionId": value.PlanVersionID,
		"source": value.Source, "inputPolicy": value.InputPolicy, "digest": value.Digest,
		"createdBy": value.CreatedBy, "createdAt": value.CreatedAt,
	}
}

func agentBindingResponse(value binding.Binding) dto.WorkflowBindingResponse {
	response := dto.WorkflowBindingResponse{
		ID: value.ID, PlanID: value.PlanID, Key: value.Key, DisplayName: value.DisplayName,
		PlanDisplayName: value.PlanDisplayName,
		DescriptionMD:   value.DescriptionMD, State: string(value.State), Revision: value.Revision,
		Owner:     dto.WorkflowBindingOwnerResponse{UserID: value.Owner.UserID, MemberID: value.Owner.MemberID},
		CreatedBy: agentBindingActorResponse(value.CreatedBy), CreatedAt: value.CreatedAt,
		UpdatedBy: agentBindingActorResponse(value.UpdatedBy), UpdatedAt: value.UpdatedAt, ArchivedAt: value.ArchivedAt,
	}
	if value.LatestRevision != nil {
		response.LatestRevision = &dto.WorkflowBindingRevisionRefResponse{ID: value.LatestRevision.ID, Revision: value.LatestRevision.Revision}
	}
	if value.ActiveRevision != nil {
		response.ActiveRevision = &dto.WorkflowBindingRevisionRefResponse{ID: value.ActiveRevision.ID, Revision: value.ActiveRevision.Revision}
	}
	if value.ArchivedBy != nil {
		actor := agentBindingActorResponse(*value.ArchivedBy)
		response.ArchivedBy = &actor
	}
	return response
}

func agentBindingRevisionResponse(value binding.Revision) dto.WorkflowBindingRevisionResponse {
	source := value.Source
	if len(source) == 0 {
		source = json.RawMessage(`{}`)
	}
	inputPolicy := value.InputPolicy
	if len(inputPolicy) == 0 {
		inputPolicy = json.RawMessage(`{}`)
	}
	return dto.WorkflowBindingRevisionResponse{
		ID: value.ID, BindingID: value.BindingID, PlanID: value.PlanID, Revision: value.Revision,
		PlanVersionID: value.PlanVersionID, Source: source, InputPolicy: inputPolicy, Digest: value.Digest,
		CreatedBy: agentBindingActorResponse(value.CreatedBy), CreatedAt: value.CreatedAt,
	}
}

func agentBindingValidationResponse(value binding.Validation) dto.WorkflowBindingValidationResponse {
	diagnostics := make([]dto.WorkflowBindingDiagnosticResponse, 0, len(value.Diagnostics))
	for _, diagnostic := range value.Diagnostics {
		diagnostics = append(diagnostics, dto.WorkflowBindingDiagnosticResponse{
			Severity: diagnostic.Severity, Code: diagnostic.Code, Path: diagnostic.Path, Message: diagnostic.Message,
		})
	}
	return dto.WorkflowBindingValidationResponse{Diagnostics: diagnostics, Digest: value.Digest}
}

func agentBindingActorResponse(value binding.Actor) dto.WorkflowBindingActorResponse {
	return dto.WorkflowBindingActorResponse{Type: value.Type, ID: value.ID, MemberID: value.MemberID}
}

func agentBindingETag(bindingID string, revision int64) string {
	return fmt.Sprintf(`"workflow-binding:%s:%d"`, strings.TrimSpace(bindingID), revision)
}

func agentBindingRevisionETag(revisionID string, revision int64, digest string) string {
	return fmt.Sprintf(`"workflow-binding-revision:%s:%d:%s"`, strings.TrimSpace(revisionID), revision, strings.TrimSpace(digest))
}
