package binding

import (
	"errors"
	"net/http"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
	"github.com/tidbcloud/AgentTeam/internal/workflow/bindingmanage"
	"github.com/tidbcloud/AgentTeam/internal/workflow/publicerr"
)

// publicError constructs a canonical API-layer domain error that the shared
// classifier projects into the canonical public Workflow error contract.
func publicError(status int, code string, problem string) error {
	return publicerr.WorkflowError{
		Kind: storeerr.ErrBadRequest, Status: status, Code: code, Problem: problem,
	}
}

// APIErrorDetails is an optional domain error projection. The production
// manage adapter can implement it without making this transport depend on a
// store error concrete type.
type APIErrorDetails interface {
	error
	BindingAPIError() (code string, details any)
}

func classifyError(err error) (api.ErrorResponse, bool) {
	options := publicerr.Options{
		Stage:       publicerr.StageBinding,
		DefaultCode: "binding_request_failed",
		StatusForCode: func(code string, status int) int {
			if code == string(bindingmanage.ErrorCodeRevisionConflict) {
				return http.StatusPreconditionFailed
			}
			return status
		},
		ProtectedSelectors: map[string]string{
			string(bindingmanage.ErrorCodeNotFound):     string(bindingmanage.ErrorCodeNotFound),
			string(bindingmanage.ErrorCodeForbidden):    string(bindingmanage.ErrorCodeNotFound),
			string(bindingmanage.ErrorCodePlanNotFound): string(bindingmanage.ErrorCodeNotFound),
		},
		ProtectedNotFoundCode: string(bindingmanage.ErrorCodeNotFound),
	}
	if projected := (APIErrorDetails)(nil); errors.As(err, &projected) {
		code, details := projected.BindingAPIError()
		status := publicerr.StatusForError(err)
		if status == 0 {
			return api.ErrorResponse{}, false
		}
		if options.StatusForCode != nil {
			status = options.StatusForCode(code, status)
		}
		// Project the code-only binding error through the canonical public
		// classifier so every canonical contract field (stage, path, hint,
		// retryability, bounded diagnostics/truncation) survives instead of an
		// ad-hoc envelope that only carries the stage. The Problem is a fixed
		// bounded default rather than the arbitrary projected error text, and
		// the classifier-bounded details projection is kept (details is nil so
		// workflowErrorResponse falls back to classified.Details): an unbounded
		// details.diagnostics is never restored after classification.
		classified, _ := publicerr.Classify(
			publicerr.WorkflowError{Kind: storeerr.ErrBadRequest, Status: status, Code: code, Problem: bindingRequestFailedProblem, Details: details},
			publicerr.Options{Stage: publicerr.StageBinding, DefaultCode: "binding_request_failed"},
		)
		return workflowErrorResponse(classified, nil), true
	}
	classified, ok := publicerr.Classify(err, options)
	if !ok {
		return api.ErrorResponse{}, false
	}
	// The classifier is the single projection: it bounds the canonical
	// top-level diagnostics/truncation and strips the duplicate from the
	// details envelope. Never restore details.diagnostics after classification,
	// so the response carries exactly one canonical list.
	return workflowErrorResponse(classified, nil), true
}

// ClassifyError shares the first-class Binding error contract with trusted
// Agent transports without duplicating status/code mapping or the single
// diagnostics projection.
func ClassifyError(err error) (api.ErrorResponse, bool) {
	return classifyError(err)
}

// bindingRequestFailedProblem is the fixed bounded default problem projected for
// the code-only APIErrorDetails path. The arbitrary projected error text is
// never trusted as a public Problem.
const bindingRequestFailedProblem = "Workflow request failed"

// workflowErrorResponse maps a canonical public Workflow classification into the
// shared API error envelope, carrying every canonical contract field. details
// optionally overrides the classifier's safe details projection; pass nil to
// keep classified.Details.
func workflowErrorResponse(classified publicerr.Classified, details any) api.ErrorResponse {
	if details == nil {
		details = classified.Details
	}
	response := api.ErrorResponse{
		Status: classified.Status, Message: classified.Error.Problem,
		Code: classified.Error.Code, Details: details,
		Stage: string(classified.Error.Stage), Path: classified.Error.Path,
		Hint: classified.Error.Hint, Retryable: classified.Error.Retryable,
		Diagnostics: classified.Error.Diagnostics,
	}
	if classified.Error.Truncation != nil {
		response.Truncation = classified.Error.Truncation
	}
	return response
}
