package integration

import (
	"context"
	"encoding/json"
	"testing"
)

type integrationConfigRepoTestDouble struct {
	saves int
}

func (*integrationConfigRepoTestDouble) ListIntegrationConfigs(context.Context, string) ([]*IntegrationConfig, error) {
	return nil, nil
}
func (*integrationConfigRepoTestDouble) FindIntegrationConfig(context.Context, string, string) (*IntegrationConfig, error) {
	return nil, nil
}
func (r *integrationConfigRepoTestDouble) SaveIntegrationConfig(context.Context, *IntegrationConfig) error {
	r.saves++
	return nil
}
func (*integrationConfigRepoTestDouble) DeleteIntegrationConfig(context.Context, string, string) error {
	return nil
}

type integrationConnectionTesterDouble struct {
	calls  int
	config *IntegrationConfig
}

func (t *integrationConnectionTesterDouble) TestIntegration(_ context.Context, config *IntegrationConfig) error {
	t.calls++
	t.config = config
	return nil
}

func TestCommunicationIntegrationDefinitionsAndValidation(t *testing.T) {
	for _, target := range []struct{ kind, provider string }{
		{IntegrationKindMQ, "emqx"},
		{IntegrationKindMQ, "rabbitmq"},
		{IntegrationKindWebSocket, "melody"},
	} {
		values := DefaultIntegrationConfig(target.kind, target.provider)
		if len(values) == 0 {
			t.Fatalf("default config missing for %s/%s", target.kind, target.provider)
		}
		if err := ValidateIntegrationConfig(target.kind, target.provider, values); err != nil {
			t.Fatalf("default config invalid for %s/%s: %v", target.kind, target.provider, err)
		}
	}
}

func TestCommunicationIntegrationValidationRejectsInvalidValues(t *testing.T) {
	rabbit := DefaultIntegrationConfig(IntegrationKindMQ, "rabbitmq")
	rabbit["port"] = 0
	if err := ValidateIntegrationConfig(IntegrationKindMQ, "rabbitmq", rabbit); err == nil {
		t.Fatal("invalid rabbitmq port was accepted")
	}
	websocket := DefaultIntegrationConfig(IntegrationKindWebSocket, "melody")
	websocket["path"] = "ws"
	if err := ValidateIntegrationConfig(IntegrationKindWebSocket, "melody", websocket); err == nil {
		t.Fatal("invalid websocket path was accepted")
	}
}

func TestIntegrationConfigTestDoesNotPersistCandidate(t *testing.T) {
	repo := &integrationConfigRepoTestDouble{}
	tester := &integrationConnectionTesterDouble{}
	usecase := NewIntegrationConfigUsecase(repo, tester)
	raw, _ := json.Marshal(map[string]any{"path": "/candidate"})

	err := usecase.Test(context.Background(), &IntegrationConfig{
		Kind:     " WebSocket ",
		Provider: " Melody ",
		Enabled:  false,
		Values:   raw,
	})
	if err != nil {
		t.Fatal(err)
	}
	if repo.saves != 0 {
		t.Fatalf("candidate config was persisted %d times", repo.saves)
	}
	if tester.calls != 1 || tester.config == nil {
		t.Fatalf("connection tester calls = %d, config = %#v", tester.calls, tester.config)
	}
	if tester.config.Kind != IntegrationKindWebSocket || tester.config.Provider != "melody" || !tester.config.Enabled {
		t.Fatalf("tested config = %#v", tester.config)
	}
	values := map[string]any{}
	if err = json.Unmarshal(tester.config.Values, &values); err != nil {
		t.Fatal(err)
	}
	if values["path"] != "/candidate" || values["write_wait"] != "10s" {
		t.Fatalf("tested values = %#v", values)
	}
}

func TestIntegrationConfigRejectsJSONNull(t *testing.T) {
	repo := &integrationConfigRepoTestDouble{}
	usecase := NewIntegrationConfigUsecase(repo, &integrationConnectionTesterDouble{})
	if err := usecase.Save(context.Background(), &IntegrationConfig{Kind: IntegrationKindMQ, Provider: "emqx", Values: json.RawMessage("null")}); err == nil {
		t.Fatal("Save() accepted JSON null as an object")
	}
	if err := usecase.Test(context.Background(), &IntegrationConfig{Kind: IntegrationKindMQ, Provider: "emqx", Values: json.RawMessage("null")}); err == nil {
		t.Fatal("Test() accepted JSON null as an object")
	}
}
