import sys
import stat
import pytest

# Skip this module if on Windows. Unix sockets are not available on Windows.
pytestmark = pytest.mark.skipif(
    sys.platform.startswith('win'),
    reason="Unix sockets are not available on Windows."
)

import os
import subprocess
import pathlib
import time

from jupyter_server.utils import urlencode_unix_socket, urlencode_unix_socket_path


@pytest.mark.integration_test
def test_shutdown_sock_server_integration(jp_unix_socket_file):
    url = urlencode_unix_socket(jp_unix_socket_file).encode()
    encoded_sock_path = urlencode_unix_socket_path(jp_unix_socket_file)
    p = subprocess.Popen(
        ['jupyter-server', '--sock=%s' % jp_unix_socket_file, '--sock-mode=0700'],
        stdout=subprocess.PIPE, stderr=subprocess.PIPE
    )

    complete = False
    for line in iter(p.stderr.readline, b''):
        if url in line:
            complete = True
            break

    assert complete, 'did not find socket URL in stdout when launching notebook'

    socket_path = encoded_sock_path.encode()
    assert socket_path in subprocess.check_output(['jupyter-server', 'list'])

    # Ensure umask is properly applied.
    assert stat.S_IMODE(os.lstat(jp_unix_socket_file).st_mode) == 0o700

    try:
        subprocess.check_output(['jupyter-server', 'stop'], stderr=subprocess.STDOUT)
    except subprocess.CalledProcessError as e:
        assert 'There is currently no server running on' in e.output.decode()
    else:
        raise AssertionError('expected stop command to fail due to target mis-match')

    assert encoded_sock_path.encode() in subprocess.check_output(['jupyter-server', 'list'])

    subprocess.check_output(['jupyter-server', 'stop', jp_unix_socket_file])

    assert encoded_sock_path.encode() not in subprocess.check_output(['jupyter-server', 'list'])

    p.wait()


@pytest.mark.integration_test
def test_sock_server_validate_sockmode_type():
    try:
        subprocess.check_output(
            ['jupyter-server', '--sock=/tmp/nonexistent', '--sock-mode=badbadbad'],
            stderr=subprocess.STDOUT
        )
    except subprocess.CalledProcessError as e:
        assert 'badbadbad' in e.output.decode()
    else:
        raise AssertionError('expected execution to fail due to validation of --sock-mode param')


@pytest.mark.integration_test
def test_sock_server_validate_sockmode_accessible():
    try:
        subprocess.check_output(
            ['jupyter-server', '--sock=/tmp/nonexistent', '--sock-mode=0444'],
            stderr=subprocess.STDOUT
        )
    except subprocess.CalledProcessError as e:
        assert '0444' in e.output.decode()
    else:
        raise AssertionError('expected execution to fail due to validation of --sock-mode param')


def _ensure_stopped(check_msg='There are no running servers'):
    try:
        subprocess.check_output(
            ['jupyter-server', 'stop'],
            stderr=subprocess.STDOUT
        )
    except subprocess.CalledProcessError as e:
        assert check_msg in e.output.decode()
    else:
        raise AssertionError('expected all servers to be stopped')


@pytest.mark.integration_test
def test_stop_multi_integration(jp_unix_socket_file, jp_http_port):
    """Tests lifecycle behavior for mixed-mode server types w/ default ports.

    Mostly suitable for local dev testing due to reliance on default port binding.
    """
    TEST_PORT = '9797'
    MSG_TMPL = 'Shutting down server on {}...'

    _ensure_stopped()

    # Default port.
    p1 = subprocess.Popen(
        ['jupyter-server', '--no-browser']
    )

    # Unix socket.
    p2 = subprocess.Popen(
        ['jupyter-server', '--sock=%s' % jp_unix_socket_file]
    )

    # Specified port
    p3 = subprocess.Popen(
        ['jupyter-server', '--no-browser', '--port=%s' % TEST_PORT]
    )

    time.sleep(3)

    shutdown_msg = MSG_TMPL.format(jp_http_port)
    assert shutdown_msg in subprocess.check_output(
        ['jupyter-server', 'stop']
    ).decode()

    _ensure_stopped('There is currently no server running on 8888')

    assert MSG_TMPL.format(jp_unix_socket_file) in subprocess.check_output(
        ['jupyter-server', 'stop', jp_unix_socket_file]
    ).decode()

    assert MSG_TMPL.format(TEST_PORT) in subprocess.check_output(
        ['jupyter-server', 'stop', TEST_PORT]
    ).decode()

    _ensure_stopped()

    p1.wait()
    p2.wait()
    p3.wait()


@pytest.mark.integration_test
def test_launch_socket_collision(jp_unix_socket_file):
    """Tests UNIX socket in-use detection for lifecycle correctness."""
    sock = jp_unix_socket_file
    check_msg = 'socket %s is already in use' % sock

    _ensure_stopped()

    # Start a server.
    cmd = ['jupyter-server', '--sock=%s' % sock]
    p1 = subprocess.Popen(cmd)
    time.sleep(3)

    # Try to start a server bound to the same UNIX socket.
    try:
        subprocess.check_output(cmd, stderr=subprocess.STDOUT)
    except subprocess.CalledProcessError as cpe:
        assert check_msg in cpe.output.decode()
    except Exception as ex:
        raise AssertionError(f"expected 'already in use' error, got '{ex}'!")
    else:
        raise AssertionError(f"expected 'already in use' error, got success instead!")

    # Stop the background server, ensure it's stopped and wait on the process to exit.
    subprocess.check_call(['jupyter-server', 'stop', sock])

    _ensure_stopped()

    p1.wait()
