"""Tests for dynamic port allocation in loom init / loom up."""

from __future__ import annotations

import socket
from unittest.mock import patch

from loom.cli import _find_available_port, _is_port_available, _parse_host_port


class TestIsPortAvailable:
    def test_available_port(self):
        """An unbound port should be available."""
        # Use a high ephemeral port unlikely to be in use
        assert _is_port_available(59123) is True

    def test_occupied_port(self):
        """A port we're actively listening on should not be available."""
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.bind(("127.0.0.1", 0))
            port = s.getsockname()[1]
            assert _is_port_available(port) is False


class TestFindAvailablePort:
    def test_default_available(self):
        """When the default port is free, return it."""
        with patch("loom.cli._is_port_available", return_value=True):
            assert _find_available_port(5432) == 5432

    def test_skips_occupied(self):
        """Should skip occupied ports and return the first free one."""
        # 5432 and 5433 occupied, 5434 free
        with patch("loom.cli._is_port_available", side_effect=[False, False, True]):
            assert _find_available_port(5432) == 5434

    def test_raises_when_exhausted(self):
        """Should raise RuntimeError if no port found in range."""
        with patch("loom.cli._is_port_available", return_value=False):
            try:
                _find_available_port(5432, max_attempts=3)
                assert False, "Expected RuntimeError"
            except RuntimeError as e:
                assert "5432" in str(e)
                assert "5434" in str(e)


class TestParseHostPort:
    def test_standard_mapping(self):
        assert _parse_host_port("5432:5432") == 5432

    def test_different_ports(self):
        assert _parse_host_port("5433:5432") == 5433

    def test_invalid_mapping(self):
        assert _parse_host_port("not_a_port:5432") is None

    def test_no_colon(self):
        assert _parse_host_port("5432") is None
