package com.atlassian.bitbucket.mesh.platform;

import com.atlassian.bitbucket.mesh.junit.LogListener;
import com.google.common.collect.Lists;
import org.junit.Rule;
import org.junit.Test;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.InstanceNotFoundException;
import javax.management.MalformedObjectNameException;
import java.util.*;

import static java.util.Optional.empty;
import static java.util.Optional.of;
import static org.junit.Assert.*;

public class DefaultOperatingSystemServiceTest {

    @Rule
    public final LogListener logListener = new LogListener(DefaultOperatingSystemService.class);

    private DefaultOperatingSystemService service;

    @Test
    public void testCanGetCpuLoadGivesPresentMeasurement() throws Exception {
        service = newOsServiceReturningCpuLoad(of(0.5d));

        OptionalDouble cpuLoad = service.getCpuLoad();
        assertTrue(cpuLoad.isPresent());
        assertEquals(0, Double.compare(cpuLoad.getAsDouble(), 0.5d));
    }

    @Test
    public void testEmptyCpuLoadAttributeListGivesEmptyMeasurement() throws Exception {
        service = newOsServiceReturningCpuLoad(empty());

        assertFalse(service.getCpuLoad().isPresent());
    }

    @Test
    public void testNonDoubleCpuLoadAttributeGivesEmptyMeasurement() throws Exception {
        service = newOsServiceReturning(of("foobar"));

        assertFalse(service.getCpuLoad().isPresent());
    }

    @Test
    public void testNullCpuLoadAttributeGivesEmptyMeasurement() throws Exception {
        service = new DefaultOperatingSystemService() {
            @Override
            AttributeList getAttribute(String name) {
                return new AttributeList(Lists.newArrayList(new Attribute("SystemCpuLoad", null)));
            }
        };

        assertFalse(service.getCpuLoad().isPresent());
    }

    @Test
    public void testNegInfinityCpuLoadAttributeGivesEmptyMeasurement() throws Exception {
        service = newOsServiceReturningCpuLoad(of(Double.NEGATIVE_INFINITY));

        assertFalse(service.getCpuLoad().isPresent());
    }

    @Test
    public void testNegNaNCpuLoadAttributeGivesEmptyMeasurement() throws Exception {
        service = newOsServiceReturningCpuLoad(of(-Double.NaN));

        assertFalse(service.getCpuLoad().isPresent());
    }

    @Test
    public void testNegCpuLoadAttributeGivesEmptyMeasurement() throws Exception {
        service = newOsServiceReturningCpuLoad(of(-0.1d));

        assertFalse(service.getCpuLoad().isPresent());
    }

    @Test
    public void testNegZeroCpuLoadAttributeGivesEmptyMeasurement() throws Exception {
        service = newOsServiceReturningCpuLoad(of(-0.0d));

        assertFalse(service.getCpuLoad().isPresent());
    }

    @Test
    public void testPositiveZeroCpuLoadAttributeGivesValidMeasurement() throws Exception {
        service = newOsServiceReturningCpuLoad(of(0.0d));

        assertTrue(service.getCpuLoad().isPresent());
        assertEquals(0, Double.compare(0.0d, service.getCpuLoad().getAsDouble()));
    }

    @Test
    public void testOneDotZeroCpuLoadAttributeGivesValidMeasurement() throws Exception {
        service = newOsServiceReturningCpuLoad(of(1.0d));

        assertTrue(service.getCpuLoad().isPresent());
        assertEquals(0, Double.compare(1.0d, service.getCpuLoad().getAsDouble()));
    }

    @Test
    public void testLargerThanOneCpuLoadAttributeGivesEmptyMeasurement() throws Exception {
        service = newOsServiceReturningCpuLoad(of(2.0d));

        assertFalse(service.getCpuLoad().isPresent());
    }

    @Test
    public void testPosNanCpuLoadAttributeGivesEmptyMeasurement() throws Exception {
        service = newOsServiceReturningCpuLoad(of(Double.NaN));

        assertFalse(service.getCpuLoad().isPresent());
    }

    @Test
    public void testPosInfinityCpuLoadAttributeGivesEmptyMeasurement() throws Exception {
        service = newOsServiceReturningCpuLoad(of(Double.POSITIVE_INFINITY));

        assertFalse(service.getCpuLoad().isPresent());
    }

    @Test
    public void testInstanceNotFoundExceptionGivesEmptyMeasurement() throws Exception {
        service = new DefaultOperatingSystemService() {
            @Override
            AttributeList getAttribute(String name) throws InstanceNotFoundException {
                throw new InstanceNotFoundException();
            }
        };

        assertFalse(service.getCpuLoad().isPresent());
    }

    @Test
    public void testReflectionExceptionGivesEmptyMeasurement() throws Exception {
        service = new DefaultOperatingSystemService() {
            @Override
            AttributeList getAttribute(String name) throws InstanceNotFoundException {
                throw new InstanceNotFoundException();
            }
        };

        assertFalse(service.getCpuLoad().isPresent());
    }

    @Test
    public void testCanGetMemoryGivesMeasurement() throws Exception {
        service = newOsServiceReturningTotalMemory(of(1L));

        OptionalLong memory = service.getTotalPhysicalMemory();
        assertTrue(memory.isPresent());
        assertEquals(1L, memory.getAsLong());
    }

    @Test
    public void testEmptyMemoryLoadAttributeListGivesEmptyMeasurement() throws Exception {
        service = newOsServiceReturningTotalMemory(empty());

        assertFalse(service.getTotalPhysicalMemory().isPresent());
    }

    @Test
    public void testNonLongMemoryLoadAttributeGivesEmptyMeasurement() throws Exception {
        service = newOsServiceReturning(of("foobar"));

        assertFalse(service.getTotalPhysicalMemory().isPresent());
    }

    @Test
    public void testNullMemoryAttributeGivesEmptyMeasurement() throws Exception {
        service = new DefaultOperatingSystemService() {
            @Override
            AttributeList getAttribute(String name) {
                return new AttributeList(Lists.newArrayList(new Attribute("TotalPhysicalMemorySize", null)));
            }
        };

        assertFalse(service.getTotalPhysicalMemory().isPresent());
    }

    @Test
    public void testNonPositiveMemoryAttributeGivesEmptyMeasurement() throws Exception {
        service = newOsServiceReturningTotalMemory(of(0L));

        assertFalse(service.getTotalPhysicalMemory().isPresent());

        service = newOsServiceReturningTotalMemory(of(-1L));

        assertFalse(service.getTotalPhysicalMemory().isPresent());
    }

    @Test
    public void testInstanceNotFoundExceptionGivesEmptyMemoryMeasurement() throws Exception {
        service = new DefaultOperatingSystemService() {
            @Override
            AttributeList getAttribute(String name) throws InstanceNotFoundException {
                throw new InstanceNotFoundException();
            }
        };

        assertFalse(service.getTotalPhysicalMemory().isPresent());
    }

    @Test
    public void testReflectionExceptionGivesEmptyMemoryMeasurement() throws Exception {
        service = new DefaultOperatingSystemService() {
            @Override
            AttributeList getAttribute(String name) throws InstanceNotFoundException {
                throw new InstanceNotFoundException();
            }
        };

        assertFalse(service.getTotalPhysicalMemory().isPresent());
    }

    private static DefaultOperatingSystemService newOsServiceReturningCpuLoad(Optional<Double> value)
            throws MalformedObjectNameException {

        return newOsServiceReturning(value);
    }

    private static DefaultOperatingSystemService newOsServiceReturningTotalMemory(Optional<Long> value)
            throws MalformedObjectNameException {

        return newOsServiceReturning(value);
    }

    private static <O> DefaultOperatingSystemService newOsServiceReturning(Optional<O> cpuValue)
            throws MalformedObjectNameException {

        return new DefaultOperatingSystemService() {
            @Override
            AttributeList getAttribute(String name) {
                return new AttributeList(
                        cpuValue.map(val -> (List<Attribute>) Lists.newArrayList(new Attribute(name, val)))
                        .orElse(Collections.emptyList()));
            }
        };
    }
}