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

package http

import (
	"context"
	"encoding/json"
	"errors"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/absmach/magistrala/pkg/messaging"
	"github.com/stretchr/testify/require"
)

type fakeHookParser struct {
	domainID        string
	channelID       string
	subtopic        string
	topicType       messaging.TopicType
	err             error
	publishCalled   bool
	subscribeCalled bool
}

func (p *fakeHookParser) ParsePublishTopic(context.Context, string, bool) (string, string, string, messaging.TopicType, error) {
	p.publishCalled = true
	return p.domainID, p.channelID, p.subtopic, p.topicType, p.err
}

func (p *fakeHookParser) ParseSubscribeTopic(context.Context, string, bool) (string, string, string, messaging.TopicType, error) {
	p.subscribeCalled = true
	return p.domainID, p.channelID, p.subtopic, p.topicType, p.err
}

func TestHooksHandlerReturnsCanonicalTopicModifier(t *testing.T) {
	parser := &fakeHookParser{
		domainID:  "26ad5c3f-cd91-4ff0-9685-0c3115643174",
		channelID: "cdc8f55f-0c54-4a9f-b4aa-8c69d4a8ce15",
		subtopic:  "messages",
		topicType: messaging.MessageType,
	}
	req := httptest.NewRequest("POST", "/hooks", strings.NewReader(`{
		"hook":"auth_on_publish",
		"client_id":"cli1",
		"external_id":"64d6bc95-b313-4412-9369-299543d9c63b",
		"protocol":"mqtt",
		"topic":"m/d1/c/ch1/messages"
	}`))
	w := httptest.NewRecorder()

	MakeHooksHandler(parser).ServeHTTP(w, req)

	require.Equal(t, 200, w.Code)
	require.True(t, parser.publishCalled)
	require.False(t, parser.subscribeCalled)

	var res hookResponse
	require.NoError(t, json.NewDecoder(w.Body).Decode(&res))
	require.Equal(t, hookResultOK, res.Result)
	require.Equal(t, "m/26ad5c3f-cd91-4ff0-9685-0c3115643174/c/cdc8f55f-0c54-4a9f-b4aa-8c69d4a8ce15/messages", res.Topic)
}

func TestHooksHandlerUsesSubscribeParserForSubscribeAndUnsubscribe(t *testing.T) {
	parser := &fakeHookParser{
		domainID:  "26ad5c3f-cd91-4ff0-9685-0c3115643174",
		channelID: "cdc8f55f-0c54-4a9f-b4aa-8c69d4a8ce15",
		subtopic:  "messages/+",
		topicType: messaging.MessageType,
	}
	for _, hook := range []string{hookAuthOnSubscribe, hookAuthOnUnsubscribe} {
		parser.publishCalled = false
		parser.subscribeCalled = false
		req := httptest.NewRequest("POST", "/hooks", strings.NewReader(`{"hook":"`+hook+`","topic":"m/d1/c/ch1/messages/+"}`))
		w := httptest.NewRecorder()

		MakeHooksHandler(parser).ServeHTTP(w, req)

		require.Equal(t, 200, w.Code)
		require.False(t, parser.publishCalled)
		require.True(t, parser.subscribeCalled)
	}
}

func TestHooksHandlerReturnsOKForNonMGTopic(t *testing.T) {
	req := httptest.NewRequest("POST", "/hooks", strings.NewReader(`{"hook":"auth_on_publish","topic":"$SYS/broker/uptime"}`))
	w := httptest.NewRecorder()

	MakeHooksHandler(nil).ServeHTTP(w, req)

	require.Equal(t, 200, w.Code)
	var res hookResponse
	require.NoError(t, json.NewDecoder(w.Body).Decode(&res))
	require.Equal(t, hookResultOK, res.Result)
	require.Empty(t, res.Topic)
}

func TestHooksHandlerAllowsInternalAMQPMessageConsumer(t *testing.T) {
	parser := &fakeHookParser{err: errors.New("should not parse internal consumer")}
	req := httptest.NewRequest("POST", "/hooks", strings.NewReader(`{
		"hook":"auth_on_subscribe",
		"protocol":"amqp091",
		"topic":"m/#"
	}`))
	w := httptest.NewRecorder()

	MakeHooksHandler(parser).ServeHTTP(w, req)

	require.Equal(t, 200, w.Code)
	require.False(t, parser.publishCalled)
	require.False(t, parser.subscribeCalled)

	var res hookResponse
	require.NoError(t, json.NewDecoder(w.Body).Decode(&res))
	require.Equal(t, hookResultOK, res.Result)
	require.Empty(t, res.Topic)
}

func TestHooksHandlerDeniesUnresolvedMGTopic(t *testing.T) {
	parser := &fakeHookParser{err: errors.New("failed to resolve channel route")}
	req := httptest.NewRequest("POST", "/hooks", strings.NewReader(`{"hook":"auth_on_publish","topic":"m/d1/c/ch1/messages"}`))
	w := httptest.NewRecorder()

	MakeHooksHandler(parser).ServeHTTP(w, req)

	require.Equal(t, 200, w.Code)
	var res hookResponse
	require.NoError(t, json.NewDecoder(w.Body).Decode(&res))
	require.Equal(t, hookResultDeny, res.Result)
}
