package com.atlassian.bitbucket.mesh.metrics;

import com.google.common.collect.ImmutableMap;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.config.NamingConvention;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.Before;
import org.junit.Test;

import javax.management.ObjectName;
import java.util.Arrays;
import java.util.Map;

import static org.junit.Assert.assertEquals;

public class JmxRegistryNameHandlerTest {

    private final Map<String, String> commonTags = ImmutableMap.of(
            "application", "mesh",
            "host", "mesh-1.internal.atlassian.com"
    );
    private JmxRegistryNameHandler nameHandler;
    private MeterRegistry registry;

    @Before
    public void setUp() {
        nameHandler = new JmxRegistryNameHandler(commonTags);
        registry = new SimpleMeterRegistry();
    }

    @Test
    public void testCreateNameSimpleMeter() {
        assertEquals("metrics:type=packCache,name=hits", objectName("packCache.hits"));
        assertEquals("metrics:type=pack,section=cache,name=scmHostingHits", objectName("pack.cache.scm-hosting.hits"));
        assertEquals("metrics:type=pack,section=cache,name=scmHostingHits", objectName("pack.cache.scm hosting.hits"));
    }

    @Test
    public void testCreateNameMeterWithTags() {
        assertEquals("metrics:type=rpc,rpcService=HostingService,rpcMethod=HttpBackEnd,name=ops",
                objectName("rpc.ops", Tag.of("application", "mesh"), Tag.of("rpc-method", "HttpBackEnd"),
                        Tag.of("rpc-service", "HostingService")));

        //Check whitespace and . handling
        assertEquals("metrics:type=rpc,rpcMethod=Http_Back_End,rpcService=\"Hosting\\*Service\",name=ops",
                objectName("rpc.ops", Tag.of("rpc method", "Http Back.End"), Tag.of("rpc.service", "Hosting*Service")));
    }

    @Test
    public void testCreateNameMeterWithTagMappedAsName() {
        //rpc.calls has rpc-method marked as the tag providing the name property
        assertEquals("metrics:type=rpc,section=calls,rpcService=HostingService,name=HttpBackEnd",
                objectName("rpc.calls", Tag.of("rpc-method", "HttpBackEnd"), Tag.of("rpc-service", "HostingService")));
        //throttling.threads.queued has throttled-resource marked as the naming tag
        assertEquals("metrics:type=throttling,section=threads,subsection=queued,name=PACK",
                objectName("throttling.threads.queued", Tag.of("throttled-resource", "PACK")));
    }

    @Test
    public void testHierarchicalNameSimpleMeter() {
        assertEquals("packCache.hits", hierarchicalName("packCache.hits"));
        assertEquals("pack.cache.scm-hosting.hits", hierarchicalName("pack.cache.scm-hosting.hits"));
        assertEquals("pack.cache.scm hosting.hits", hierarchicalName("pack.cache.scm hosting.hits"));
    }

    @Test
    public void testHierarchicalNameMeterWithTags() {
        //Verify that rpc-service gets sorted before rpc-method
        assertEquals("rpc.calls/rpcService.HostingService.rpcMethod.HttpBackEnd", hierarchicalName("rpc.calls",
                Tag.of("rpc-method", "HttpBackEnd"), Tag.of("rpc-service", "HostingService")));
        assertEquals("rpc.calls/rpcService.HostingService.rpcMethod.HttpBackEnd", hierarchicalName("rpc.calls",
                Tag.of("rpc-service", "HostingService"), Tag.of("rpc-method", "HttpBackEnd")));

        //Check whitespace and . handling
        assertEquals("rpc.calls/rpcMethod.Http_Back_End.rpcService.HostingService", hierarchicalName("rpc.calls",
                Tag.of("rpc.service", "HostingService"), Tag.of("rpc method", "Http Back.End")));
    }

    private String hierarchicalName(String name, Tag... tags) {
        Gauge gauge = Gauge.builder(name, () -> 1).tags(Arrays.asList(tags)).register(registry);
        try {
            return nameHandler.toHierarchicalName(gauge.getId(), NamingConvention.camelCase);
        } finally {
            registry.remove(gauge);
        }
    }

    private String objectName(String name, Tag... tags) {
        String hierarchicalName = hierarchicalName(name, tags);
        ObjectName objectName = nameHandler.createName("type", "metrics", hierarchicalName);
        return objectName.toString();
    }
}