package com.atlassian.bitbucket.installer.validation;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import java.io.IOException;
import java.net.ServerSocket;

import static com.atlassian.bitbucket.installer.validation.PortValidator.*;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class PortValidatorTest {
    /**
     * Sometimes, if you ever see an unexpected port used error coming from this Test,
     * make sure that the following ports are not actually in use by any application on
     * you local machine.
     */
    private static final int HTTP_PORT = 37990;
    private static final int CONTROL_PORT = 38006;

    @Rule
    public final ExpectedException expectedException = ExpectedException.none();
    private PortValidator portValidator;

    @Before
    public void setup() {
        portValidator = new PortValidator();
    }

    @Test
    public void testSamePorts() {
        expectedException.expect(HardValidationException.class);
        expectedException.expectMessage(HardValidationException.ERROR_PORT_SAME);

        portValidator.validate(HTTP_PORT, HTTP_PORT);
    }

    @Test
    public void testOutOfRangeHttpPort() {
        expectedException.expect(HardValidationException.class);
        expectedException.expectMessage(HardValidationException.ERROR_PORT_HTTP_RANGE);

        portValidator.validate(HIGH_PORT_RANGE_INC + 1, CONTROL_PORT);
    }

    @Test
    public void testOutOfRangeControlPort() {
        expectedException.expect(HardValidationException.class);
        expectedException.expectMessage(HardValidationException.ERROR_PORT_CONTROL_RANGE);

        portValidator.validate(HTTP_PORT, LOW_PORT_RANGE_EXC - 1);
    }

    @Test
    public void testOutOfRangeHttpAndControlPort() {
        expectedException.expect(HardValidationException.class);
        expectedException.expectMessage(HardValidationException.ERROR_PORT_RANGE);

        portValidator.validate(LOW_PORT_RANGE_EXC - 1, HIGH_PORT_RANGE_INC + 1);
    }

    @Test
    public void testHttpPortIsSshPort() {
        expectedException.expect(HardValidationException.class);
        expectedException.expectMessage(HardValidationException.ERROR_PORT_HTTP_SSH);

        portValidator.validate(SSH_SERVER_PORT, CONTROL_PORT);
    }

    @Test
    public void testControlPortIsSshPort() {
        expectedException.expect(HardValidationException.class);
        expectedException.expectMessage(HardValidationException.ERROR_PORT_CONTROL_SSH);

        portValidator.validate(HTTP_PORT, SSH_SERVER_PORT);
    }

    @Test
    public void testHttpPortIsElasticsearchServerPort() {
        expectedException.expect(HardValidationException.class);
        expectedException.expectMessage(HardValidationException.ERROR_PORT_HTTP_ELASTICSEARCH_HTTP);

        portValidator.validate(ELASTICSEARCH_HTTP_PORT, CONTROL_PORT);
    }

    @Test
    public void testControlPortIsElasticsearchServerPort() {
        expectedException.expect(HardValidationException.class);
        expectedException.expectMessage(HardValidationException.ERROR_PORT_CONTROL_ELASTICSEARCH_HTTP);

        portValidator.validate(HTTP_PORT, ELASTICSEARCH_HTTP_PORT);
    }

    @Test
    public void testHttpPortIsElasticsearchTransportPort() {
        expectedException.expect(HardValidationException.class);
        expectedException.expectMessage(HardValidationException.ERROR_PORT_HTTP_ELASTICSEARCH_TRANSPORT);

        portValidator.validate(ELASTICSEARCH_TRANSPORT_PORT, CONTROL_PORT);
    }

    @Test
    public void testControlPortIsElasticsearchTransportPort() {
        expectedException.expect(HardValidationException.class);
        expectedException.expectMessage(HardValidationException.ERROR_PORT_CONTROL_ELASTICSEARCH_TRANSPORT);

        portValidator.validate(HTTP_PORT, ELASTICSEARCH_TRANSPORT_PORT);
    }

    @Test
    public void testHttpPortIsTaken() throws Exception {
        expectedException.expect(HardValidationException.class);
        expectedException.expectMessage(HardValidationException.ERROR_PORT_HTTP_USED);

        try (ServerSocket serverSocket = new ServerSocket(HTTP_PORT)) {
            portValidator.validate(HTTP_PORT, CONTROL_PORT);
        }
    }

    @Test
    public void testHttpPortIsTakenAtLocalAddress() throws Exception {
        expectedException.expect(HardValidationException.class);
        expectedException.expectMessage(HardValidationException.ERROR_PORT_HTTP_USED);

        try (ServerSocket serverSocket = new ServerSocket(HTTP_PORT, 0, LOCAL_ADDRESS)) {
            portValidator.validate(HTTP_PORT, CONTROL_PORT);
        }
    }

    @Test
    public void testControlPortIsTaken() throws Exception {
        expectedException.expect(HardValidationException.class);
        expectedException.expectMessage(HardValidationException.ERROR_PORT_CONTROL_USED);

        try (ServerSocket serverSocket = new ServerSocket(CONTROL_PORT)) {
            portValidator.validate(HTTP_PORT, CONTROL_PORT);
        }
    }

    @Test
    public void testControlPortIsTakenAtLocalAddress() throws Exception {
        expectedException.expect(HardValidationException.class);
        expectedException.expectMessage(HardValidationException.ERROR_PORT_CONTROL_USED);

        try (ServerSocket serverSocket = new ServerSocket(CONTROL_PORT, 0, LOCAL_ADDRESS)) {
            portValidator.validate(HTTP_PORT, CONTROL_PORT);
        }
    }

    @Test
    public void testElasticsearchServerPortIsTaken() throws Exception {
        expectedException.expect(SoftValidationException.class);
        expectedException.expectMessage(SoftValidationException.ERROR_PORT_ELASTICSEARCH_HTTP_USED);

        try (ServerSocket serverSocket = new ServerSocket(ELASTICSEARCH_HTTP_PORT)) {
            portValidator.validate(HTTP_PORT, CONTROL_PORT);
        }
    }

    @Test
    public void testElasticsearchServerPortIsTakenAtLocalAddress() throws Exception {
        expectedException.expect(SoftValidationException.class);
        expectedException.expectMessage(SoftValidationException.ERROR_PORT_ELASTICSEARCH_HTTP_USED);

        try (ServerSocket serverSocket = new ServerSocket(ELASTICSEARCH_HTTP_PORT, 0, LOCAL_ADDRESS)) {
            portValidator.validate(HTTP_PORT, CONTROL_PORT);
        }
    }

    @Test
    public void testElasticsearchTransportPortIsTaken() throws Exception {
        expectedException.expect(SoftValidationException.class);
        expectedException.expectMessage(SoftValidationException.ERROR_PORT_ELASTICSEARCH_TRANSPORT_USED);

        try (ServerSocket serverSocket = new ServerSocket(ELASTICSEARCH_TRANSPORT_PORT)) {
            portValidator.validate(HTTP_PORT, CONTROL_PORT);
        }
    }

    @Test
    public void testElasticsearchTransportPortIsTakenAtLocalAddress() throws Exception {
        expectedException.expect(SoftValidationException.class);
        expectedException.expectMessage(SoftValidationException.ERROR_PORT_ELASTICSEARCH_TRANSPORT_USED);

        try (ServerSocket serverSocket = new ServerSocket(ELASTICSEARCH_TRANSPORT_PORT, 0, LOCAL_ADDRESS)) {
            portValidator.validate(HTTP_PORT, CONTROL_PORT);
        }
    }

    @Test
    public void testHttpPortIsElasticsearchServerPortOnMirrorInstall() {
        // Should not throw exception at all, since we should not be checking it for mirror installs
        portValidator.validate(ELASTICSEARCH_HTTP_PORT, CONTROL_PORT, false);
    }

    @Test
    public void testElasticsearchServerPortIsTakenOnMirrorInstall() throws IOException {
        try (ServerSocket serverSocket = new ServerSocket(ELASTICSEARCH_HTTP_PORT)) {
            portValidator.validate(HTTP_PORT, CONTROL_PORT, false);
        }
    }

    @Test
    public void testShouldReturnTrueIfPortIsLowInLinux() throws Exception {
        assertTrue(portValidator.isPortNumberLow(10, 8000, true));
        assertTrue(portValidator.isPortNumberLow(8000, 10, true));
        assertTrue(portValidator.isPortNumberLow(10, 10, true));
    }

    @Test
    public void testShouldReturnFalseIfPortIsNotTooLowInLinux() throws Exception {
        assertFalse(portValidator.isPortNumberLow(8000, 8000, true));
    }

    @Test
    public void testShouldReturnFalseOnOtherPlatform() throws Exception {
        assertFalse(portValidator.isPortNumberLow(10, 8000, false));
        assertFalse(portValidator.isPortNumberLow(8000, 10, false));
        assertFalse(portValidator.isPortNumberLow(10, 10, false));
        assertFalse(portValidator.isPortNumberLow(8000, 8000, false));
    }

}
