# retoor <retoor@molodetz.nl>

import itertools
import socket

import pytest

from devplacepy.database import get_table, init_db
from devplacepy.services.containers import store
from devplacepy.services.containers.workspace import provision
from tests.conftest import run_async

OWNER = "provision-owner"
STATUSES = (
    store.ST_CREATED,
    store.ST_STARTING,
    store.ST_RUNNING,
    store.ST_STOPPED,
    store.ST_PAUSED,
    store.ST_CRASHED,
    store.ST_RESTARTING,
    store.ST_REMOVING,
    store.ST_REMOVED,
    "",
)
DESIRED = (store.DESIRED_RUNNING, store.DESIRED_STOPPED, store.DESIRED_PAUSED, "")


@pytest.fixture(autouse=True)
def _provision_db():
    init_db()
    yield
    get_table("instances").delete(workspace_owner_uid=OWNER)


def _instance(**overrides) -> dict:
    row = {
        "uid": "ws-phase",
        "status": store.ST_RUNNING,
        "desired_state": store.DESIRED_RUNNING,
        "suspended_at": "",
        "editor_port": 8443,
        "ports_json": "[]",
        "container_ip": "",
        "workspace_owner_uid": OWNER,
    }
    row.update(overrides)
    return row


@pytest.fixture
def listener():
    sock = socket.socket()
    sock.bind(("127.0.0.1", 0))
    sock.listen(4)
    try:
        yield sock
    finally:
        sock.close()


@pytest.mark.parametrize(
    "status, desired, ready, expected",
    [
        (store.ST_RUNNING, store.DESIRED_RUNNING, True, provision.PHASE_READY),
        (store.ST_RUNNING, store.DESIRED_RUNNING, False, provision.PHASE_STARTING),
        (store.ST_CREATED, store.DESIRED_RUNNING, False, provision.PHASE_STARTING),
        (store.ST_STOPPED, store.DESIRED_RUNNING, False, provision.PHASE_STARTING),
        (store.ST_CRASHED, store.DESIRED_RUNNING, False, provision.PHASE_STARTING),
        (store.ST_RUNNING, store.DESIRED_STOPPED, True, provision.PHASE_STOPPING),
        (store.ST_PAUSED, store.DESIRED_STOPPED, False, provision.PHASE_STOPPING),
        (store.ST_RESTARTING, store.DESIRED_STOPPED, False, provision.PHASE_STOPPING),
        (store.ST_CRASHED, store.DESIRED_STOPPED, False, provision.PHASE_CRASHED),
        (store.ST_STOPPED, store.DESIRED_STOPPED, False, provision.PHASE_STOPPED),
        (store.ST_CREATED, store.DESIRED_STOPPED, False, provision.PHASE_STOPPED),
        ("", "", False, provision.PHASE_STOPPED),
    ],
)
def test_phase_follows_desired_state_status_and_the_editor_probe(
    status, desired, ready, expected
):
    row = _instance(status=status, desired_state=desired)
    assert provision.phase(row, ready) == expected


def test_a_suspended_workspace_is_suspended_whatever_else_it_says():
    for status, desired, ready in itertools.product(STATUSES, DESIRED, (True, False)):
        row = _instance(
            status=status, desired_state=desired, suspended_at="2026-01-01T00:00:00"
        )
        assert provision.phase(row, ready) == provision.PHASE_SUSPENDED


def test_ready_needs_a_running_container_that_wants_to_run():
    for status, desired in itertools.product(STATUSES, DESIRED):
        row = _instance(status=status, desired_state=desired)
        is_ready = provision.phase(row, True) == provision.PHASE_READY
        assert is_ready == (
            status == store.ST_RUNNING and desired == store.DESIRED_RUNNING
        )


def test_every_phase_over_the_whole_input_domain_has_a_label():
    for status, desired, ready, suspended in itertools.product(
        STATUSES, DESIRED, (True, False), ("", "2026-01-01T00:00:00")
    ):
        row = _instance(status=status, desired_state=desired, suspended_at=suspended)
        assert provision.phase(row, ready) in provision.PHASE_LABELS


def test_transitional_phases_are_exactly_starting_and_stopping():
    assert set(provision.TRANSITIONAL_PHASES) == {
        provision.PHASE_STARTING,
        provision.PHASE_STOPPING,
    }


def test_editor_ready_when_the_editor_port_accepts_connections(listener):
    port = listener.getsockname()[1]
    row = _instance(container_ip="127.0.0.1", editor_port=port)
    assert run_async(provision.editor_ready(row)) is True


def test_editor_ready_is_false_when_nothing_listens():
    probe = socket.socket()
    probe.bind(("127.0.0.1", 0))
    port = probe.getsockname()[1]
    probe.close()
    row = _instance(container_ip="127.0.0.1", editor_port=port)
    assert run_async(provision.editor_ready(row)) is False


def test_editor_ready_is_false_without_a_reachable_target(listener):
    row = _instance(container_ip="", ports_json="[]")
    assert run_async(provision.editor_ready(row)) is False


def test_editor_ready_is_false_unless_the_container_runs(listener):
    port = listener.getsockname()[1]
    for status in STATUSES:
        if status == store.ST_RUNNING:
            continue
        row = _instance(container_ip="127.0.0.1", editor_port=port, status=status)
        assert run_async(provision.editor_ready(row)) is False


def test_editor_ready_is_false_while_suspended(listener):
    port = listener.getsockname()[1]
    row = _instance(
        container_ip="127.0.0.1", editor_port=port, suspended_at="2026-01-01T00:00:00"
    )
    assert run_async(provision.editor_ready(row)) is False


def test_view_carries_the_phase_its_label_readiness_and_the_owner(listener):
    port = listener.getsockname()[1]
    instance = store.create_instance(
        {
            "project_uid": "provision-project",
            "name": "ws-view",
            "status": store.ST_RUNNING,
            "desired_state": store.DESIRED_RUNNING,
            "is_workspace": 1,
            "workspace_owner_uid": OWNER,
            "container_ip": "127.0.0.1",
            "editor_port": port,
            "ports_json": "[]",
        }
    )
    view = run_async(provision.view(instance))
    assert view["phase"] == provision.PHASE_READY
    assert view["phase_label"] == "Ready"
    assert view["editor_ready"] is True
    assert view["owner_uid"] == OWNER

    store.update_instance(instance["uid"], {"desired_state": store.DESIRED_STOPPED})
    view = run_async(provision.view(store.get_instance(instance["uid"])))
    assert view["phase"] == provision.PHASE_STOPPING
    assert view["editor_ready"] is True
