// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0

package api

import (
	"context"
	"io"
	"log/slog"
	"net/http"

	"github.com/absmach/supermq"
	api "github.com/absmach/supermq/api/http"
	apiutil "github.com/absmach/supermq/api/http/util"
	"github.com/absmach/supermq/pkg/errors"
	"github.com/absmach/supermq/pkg/messaging"
	"github.com/go-chi/chi/v5"
	kithttp "github.com/go-kit/kit/transport/http"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

const (
	ctSenmlJSON    = "application/senml+json"
	ctSenmlCBOR    = "application/senml+cbor"
	contentType    = "application/json"
	authzHeaderKey = "Authorization"
)

// MakeHandler returns a HTTP handler for API endpoints.
func MakeHandler(logger *slog.Logger, instanceID string) http.Handler {
	opts := []kithttp.ServerOption{
		kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)),
	}

	r := chi.NewRouter()
	r.Post("/m/{domain}/c/{channel}", otelhttp.NewHandler(kithttp.NewServer(
		sendMessageEndpoint(),
		decodeRequest,
		api.EncodeResponse,
		opts...,
	), "publish").ServeHTTP)

	r.Post("/m/{domain}/c/{channel}/*", otelhttp.NewHandler(kithttp.NewServer(
		sendMessageEndpoint(),
		decodeRequest,
		api.EncodeResponse,
		opts...,
	), "publish").ServeHTTP)

	r.Post("/hc/{domain}", otelhttp.NewHandler(kithttp.NewServer(
		healthCheckEndpoint(),
		decodeHealthCheckRequest,
		api.EncodeResponse,
		opts...,
	), "health_check").ServeHTTP)

	r.Get("/health", supermq.Health("http", instanceID))
	r.Handle("/metrics", promhttp.Handler())

	return r
}

func decodeRequest(_ context.Context, r *http.Request) (any, error) {
	ct := r.Header.Get("Content-Type")
	if ct != ctSenmlJSON && ct != contentType && ct != ctSenmlCBOR {
		return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
	}

	var req publishReq
	_, pass, ok := r.BasicAuth()
	switch {
	case ok:
		req.token = pass
	case !ok:
		req.token = r.Header.Get(authzHeaderKey)
	}

	payload, err := io.ReadAll(r.Body)
	if err != nil {
		return nil, errors.Wrap(apiutil.ErrValidation, errors.ErrMalformedEntity)
	}
	defer r.Body.Close()

	req.msg = &messaging.Message{Payload: payload}

	return req, nil
}

func decodeHealthCheckRequest(_ context.Context, r *http.Request) (any, error) {
	var req healthCheckReq
	req.domain = chi.URLParam(r, "domain")
	_, pass, ok := r.BasicAuth()
	switch {
	case ok:
		req.token = pass
	case !ok:
		req.token = r.Header.Get(authzHeaderKey)
	}

	if err := req.validate(); err != nil {
		return nil, errors.Wrap(apiutil.ErrValidation, err)
	}

	return req, nil
}
