package meshxapi

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

	"github.com/tidbcloud/AgentTeam/internal/identity/trusted"
)

const defaultInvalidJSONMessage = "Invalid JSON body"
const HeaderWorkspaceID = "X-Workspace-Id"

// AuthenticatedRequest is the typed context passed to auth-scoped MeshX API handlers.
type AuthenticatedRequest struct {
	Request  *http.Request
	Identity trusted.Identity
}

// AuthenticatedJSON is the typed context passed to auth-scoped JSON handlers.
type AuthenticatedJSON[T any] struct {
	AuthenticatedRequest
	Body T
}

// Context returns the underlying request context.
func (request AuthenticatedRequest) Context() context.Context {
	if request.Request != nil && request.Request.Context() != nil {
		return request.Request.Context()
	}
	return context.Background()
}

// Subject returns the authenticated OIDC subject.
func (request AuthenticatedRequest) Subject() string { return request.Identity.Subject }

// WorkspaceID returns the AgentTeam workspace scope carried by the request header.
func (request AuthenticatedRequest) WorkspaceID() string {
	if request.Request == nil {
		return ""
	}
	return strings.TrimSpace(request.Request.Header.Get(HeaderWorkspaceID))
}

// Response describes the HTTP response a typed handler wants the adapter to write.
type Response struct {
	Status int
	Body   any
	NoBody bool
}

func OK(body any) Response      { return Response{Status: http.StatusOK, Body: body} }
func Created(body any) Response { return Response{Status: http.StatusCreated, Body: body} }
func NoContent() Response       { return Response{Status: http.StatusNoContent, NoBody: true} }

// HTTPError is a generic API-layer error with an explicit HTTP status/message.
type HTTPError struct {
	Status  int
	Message string
	Code    string
	Cause   error
}

func (err HTTPError) Error() string {
	message := strings.TrimSpace(err.Message)
	if message == "" {
		message = http.StatusText(err.Status)
	}
	if message == "" {
		message = "Request failed"
	}
	return message
}

func (err HTTPError) Unwrap() error { return err.Cause }

func NewHTTPError(status int, message string) error {
	return HTTPError{Status: status, Message: message}
}

// ErrorResponse is the normalized result of classifying a domain error.
type ErrorResponse struct {
	Status  int
	Message string
	Code    string
	Details any
}

// ErrorClassifier maps domain errors to API responses.
type ErrorClassifier func(error) (ErrorResponse, bool)

type handlerOptions struct {
	invalidJSONMessage string
	classifiers        []ErrorClassifier
}

// HandlerOption customizes typed handler adapters without creating a central domain registry.
type HandlerOption func(*handlerOptions)

func WithErrorClassifier(classifier ErrorClassifier) HandlerOption {
	return func(options *handlerOptions) {
		if classifier != nil {
			options.classifiers = append(options.classifiers, classifier)
		}
	}
}

func WithInvalidJSONMessage(message string) HandlerOption {
	return func(options *handlerOptions) {
		if trimmed := strings.TrimSpace(message); trimmed != "" {
			options.invalidJSONMessage = trimmed
		}
	}
}

func newHandlerOptions(opts []HandlerOption) handlerOptions {
	options := handlerOptions{invalidJSONMessage: defaultInvalidJSONMessage}
	for _, opt := range opts {
		if opt != nil {
			opt(&options)
		}
	}
	return options
}

func NewAuthenticatedRequest(r *http.Request) (AuthenticatedRequest, error) {
	identity, ok := trusted.IdentityFromContext(r.Context())
	if !ok {
		return AuthenticatedRequest{}, NewHTTPError(http.StatusUnauthorized, "Missing authenticated identity")
	}
	return AuthenticatedRequest{Request: r, Identity: identity}, nil
}

// AuthHandler is a typed handler that requires an authenticated identity.
type AuthHandler func(AuthenticatedRequest) (Response, error)

// HandleAuth adapts an auth-scoped typed handler to net/http.
func HandleAuth(handler AuthHandler, opts ...HandlerOption) http.Handler {
	options := newHandlerOptions(opts)
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		request, err := NewAuthenticatedRequest(r)
		if err != nil {
			writeHandlerError(w, err, options)
			return
		}
		response, err := handler(request)
		if err != nil {
			writeHandlerError(w, err, options)
			return
		}
		WriteResponse(w, response)
	})
}

// BindJSON decodes a typed JSON request body.
func BindJSON[T any](r *http.Request) (T, error) {
	return bindJSON[T](nil, r, 0)
}

// BindJSONLimit decodes a typed JSON request body with an optional byte cap.
func BindJSONLimit[T any](w http.ResponseWriter, r *http.Request, maxBodyBytes int64) (T, error) {
	return bindJSON[T](w, r, maxBodyBytes)
}

func bindJSON[T any](w http.ResponseWriter, r *http.Request, maxBodyBytes int64) (T, error) {
	var request T
	if r == nil || r.Body == nil {
		return request, io.EOF
	}
	if maxBodyBytes > 0 {
		r.Body = http.MaxBytesReader(w, r.Body, maxBodyBytes)
	}
	defer r.Body.Close()
	decoder := json.NewDecoder(r.Body)
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&request); err != nil {
		return request, err
	}
	var extra any
	if err := decoder.Decode(&extra); err != nil && !errors.Is(err, io.EOF) {
		return request, err
	} else if err == nil {
		return request, errors.New("multiple JSON values")
	}
	return request, nil
}

// BindEmptyOrJSON decodes optional JSON where absent/empty/whitespace body is valid.
func BindEmptyOrJSON[T any](r *http.Request) (request T, hasBody bool, err error) {
	if r == nil || r.Body == nil {
		return request, false, nil
	}
	defer r.Body.Close()
	body, err := io.ReadAll(r.Body)
	if err != nil {
		return request, false, err
	}
	if len(bytes.TrimSpace(body)) == 0 {
		return request, false, nil
	}
	decoder := json.NewDecoder(bytes.NewReader(body))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&request); err != nil {
		return request, true, err
	}
	var extra any
	if err := decoder.Decode(&extra); err != nil && !errors.Is(err, io.EOF) {
		return request, true, err
	} else if err == nil {
		return request, true, errors.New("multiple JSON values")
	}
	return request, true, nil
}

// HandleAuthJSON adapts an auth-scoped JSON handler to net/http.
func HandleAuthJSON[T any](handler func(AuthenticatedJSON[T]) (Response, error), opts ...HandlerOption) http.Handler {
	return HandleAuthJSONLimit[T](0, handler, opts...)
}

// HandleAuthJSONLimit adapts an auth-scoped JSON handler with a request body cap.
func HandleAuthJSONLimit[T any](maxBodyBytes int64, handler func(AuthenticatedJSON[T]) (Response, error), opts ...HandlerOption) http.Handler {
	options := newHandlerOptions(opts)
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		request, err := NewAuthenticatedRequest(r)
		if err != nil {
			writeHandlerError(w, err, options)
			return
		}
		body, err := BindJSONLimit[T](w, request.Request, maxBodyBytes)
		if err != nil {
			var maxBytesErr *http.MaxBytesError
			if errors.As(err, &maxBytesErr) {
				writeHandlerError(w, NewHTTPError(http.StatusRequestEntityTooLarge, "Request body exceeds maximum size"), options)
				return
			}
			writeHandlerError(w, NewHTTPError(http.StatusBadRequest, options.invalidJSONMessage), options)
			return
		}
		response, err := handler(AuthenticatedJSON[T]{AuthenticatedRequest: request, Body: body})
		if err != nil {
			writeHandlerError(w, err, options)
			return
		}
		WriteResponse(w, response)
	})
}

func WriteResponse(w http.ResponseWriter, response Response) {
	status := response.Status
	if status == 0 {
		status = http.StatusOK
	}
	if response.NoBody {
		w.WriteHeader(status)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(response.Body)
}

func WriteError(w http.ResponseWriter, err error) {
	var httpErr HTTPError
	if errors.As(err, &httpErr) {
		writeErrorResponse(w, ErrorResponse{Status: httpErr.Status, Message: httpErr.Error(), Code: httpErr.Code})
		return
	}
	writeErrorResponse(w, ErrorResponse{Status: http.StatusInternalServerError, Message: http.StatusText(http.StatusInternalServerError), Code: "internal_error"})
}

func writeHandlerError(w http.ResponseWriter, err error, options handlerOptions) {
	var httpErr HTTPError
	if errors.As(err, &httpErr) {
		writeErrorResponse(w, ErrorResponse{Status: httpErr.Status, Message: httpErr.Error(), Code: httpErr.Code})
		return
	}
	for _, classifier := range options.classifiers {
		if response, ok := classifier(err); ok {
			writeErrorResponse(w, response)
			return
		}
	}
	WriteError(w, err)
}

func writeErrorResponse(w http.ResponseWriter, response ErrorResponse) {
	status := response.Status
	if status == 0 {
		status = http.StatusInternalServerError
	}
	message := strings.TrimSpace(response.Message)
	if message == "" {
		message = http.StatusText(status)
	}
	code := strings.TrimSpace(response.Code)
	if code == "" {
		code = strings.ToLower(strings.ReplaceAll(message, " ", "_"))
	}
	body := map[string]any{"error": code, "message": message}
	if response.Details != nil {
		body["details"] = response.Details
	}
	WriteResponse(w, Response{Status: status, Body: body})
}
