"""Tests for FleetMonitorClient (HTTPS)."""

import json
import urllib.error
from unittest.mock import MagicMock, patch

from brinkhaustools.common.fleet.client import (
    FleetMonitorClient,
    _get_local_ips,
    _now_iso,
    _slugify,
)


# -- Helper function tests ------------------------------------------------


def test_slugify_basic():
    assert _slugify("Hello World") == "hello-world"


def test_slugify_special_chars():
    assert _slugify("Test!@#$%Value") == "test-value"


def test_slugify_leading_trailing():
    assert _slugify("  --Hello--  ") == "hello"


def test_slugify_already_clean():
    assert _slugify("already-clean") == "already-clean"


def test_slugify_numbers():
    assert _slugify("test123") == "test123"


def test_now_iso_format():
    ts = _now_iso()
    assert ts.endswith("Z")
    assert "T" in ts


def test_get_local_ips_from_env(monkeypatch):
    monkeypatch.setenv("FLEET_HOST_IPS", "10.0.0.1, 10.0.0.2")
    ips = _get_local_ips()
    assert ips == ["10.0.0.1", "10.0.0.2"]


def test_get_local_ips_empty_env(monkeypatch):
    monkeypatch.setenv("FLEET_HOST_IPS", "")
    ips = _get_local_ips()
    assert isinstance(ips, list)


# -- Constructor tests -----------------------------------------------------


def test_constructor_with_base_url():
    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="Test Customer",
        machine="Machine 1",
        software="my-app",
        token="test-token",
    )
    assert client.base_url == "https://fleet.example.com"
    assert client.customer == "test-customer"
    assert client.machine == "machine-1"
    assert client.software == "my-app"
    assert client.token == "test-token"


def test_constructor_legacy_broker_param():
    client = FleetMonitorClient(
        customer="test",
        machine="machine",
        software="app",
        broker="fleet.example.com",
    )
    assert client.base_url == "https://fleet.example.com"


def test_constructor_legacy_broker_with_port():
    client = FleetMonitorClient(
        customer="test",
        machine="machine",
        software="app",
        broker="fleet.example.com",
        port=8443,
    )
    assert client.base_url == "https://fleet.example.com:8443"


def test_constructor_legacy_broker_no_tls():
    client = FleetMonitorClient(
        customer="test",
        machine="machine",
        software="app",
        broker="fleet.example.com",
        use_tls=False,
    )
    assert client.base_url.startswith("http://")


def test_constructor_legacy_password_as_token():
    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="test",
        machine="machine",
        software="app",
        password="my-secret-token",
    )
    assert client.token == "my-secret-token"


def test_constructor_default_base_url():
    client = FleetMonitorClient(
        customer="test",
        machine="machine",
        software="app",
    )
    assert client.base_url == "https://fleet.brinkhaus-gmbh.de"


def test_ingest_url_constructed():
    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="Kunde A",
        machine="Maschine 1",
        software="My App",
    )
    assert client._ingest_url == (
        "https://fleet.example.com/api/ingest/kunde-a/maschine-1/my-app"
    )


def test_is_connected_initially_false():
    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="test",
        machine="m1",
        software="app",
    )
    assert not client.is_connected


# -- Helper to create a mock urlopen response ------------------------------


def _mock_urlopen_200():
    mock_resp = MagicMock()
    mock_resp.getcode.return_value = 200
    mock_resp.__enter__ = MagicMock(return_value=mock_resp)
    mock_resp.__exit__ = MagicMock(return_value=False)
    return mock_resp


# -- Sending tests (mocked HTTP) ------------------------------------------


@patch("brinkhaustools.common.fleet.client.urllib.request.urlopen")
def test_send_heartbeat(mock_urlopen):
    mock_urlopen.return_value = _mock_urlopen_200()

    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="test",
        machine="m1",
        software="app",
        token="test-token",
    )
    client._start_time = 1000.0
    client.send_heartbeat()

    mock_urlopen.assert_called_once()
    req = mock_urlopen.call_args[0][0]
    assert req.full_url.endswith("/heartbeat")
    assert req.get_header("Authorization") == "Bearer test-token"
    assert req.get_header("Content-type") == "application/json"

    body = json.loads(req.data)
    assert "ts" in body
    assert "version" in body
    assert "uptime_seconds" in body
    assert "pid" in body
    assert "ip_addresses" in body


@patch("brinkhaustools.common.fleet.client.urllib.request.urlopen")
def test_send_diagnostics_uses_items_key(mock_urlopen):
    mock_urlopen.return_value = _mock_urlopen_200()

    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="test",
        machine="m1",
        software="app",
    )
    client.send_diagnostics([(100, "Test error", 2), (0, "All OK", 0)])

    req = mock_urlopen.call_args[0][0]
    body = json.loads(req.data)
    assert "items" in body
    assert "diagnostics" not in body
    assert len(body["items"]) == 2
    assert body["items"][0] == {"code": 100, "message": "Test error", "severity": 2}


@patch("brinkhaustools.common.fleet.client.urllib.request.urlopen")
def test_send_status(mock_urlopen):
    mock_urlopen.return_value = _mock_urlopen_200()

    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="test",
        machine="m1",
        software="app",
    )
    client.send_status("running", "All OK")

    req = mock_urlopen.call_args[0][0]
    body = json.loads(req.data)
    assert body["status"] == "running"
    assert body["message"] == "All OK"
    assert req.full_url.endswith("/status")


@patch("brinkhaustools.common.fleet.client.urllib.request.urlopen")
def test_send_snapshot_full(mock_urlopen):
    mock_urlopen.return_value = _mock_urlopen_200()

    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="test",
        machine="m1",
        software="app",
    )
    snapshot = {"SelfDiagnosis": {"System Status": True}, "Version": {"tag": "1.0"}}
    client.send_snapshot(snapshot, status="running", message="All OK")

    req = mock_urlopen.call_args[0][0]
    body = json.loads(req.data)
    assert req.full_url.endswith("/status")
    assert body["snapshot"] == snapshot
    assert body["status"] == "running"
    assert body["message"] == "All OK"
    assert "ts" in body


@patch("brinkhaustools.common.fleet.client.urllib.request.urlopen")
def test_send_snapshot_minimal(mock_urlopen):
    mock_urlopen.return_value = _mock_urlopen_200()

    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="test",
        machine="m1",
        software="app",
    )
    client.send_snapshot({})

    req = mock_urlopen.call_args[0][0]
    body = json.loads(req.data)
    assert body["snapshot"] == {}
    assert "ts" in body
    assert "status" not in body
    assert "message" not in body


@patch("brinkhaustools.common.fleet.client.urllib.request.urlopen")
def test_send_snapshot_only_status(mock_urlopen):
    mock_urlopen.return_value = _mock_urlopen_200()

    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="test",
        machine="m1",
        software="app",
    )
    client.send_snapshot({"data": 1}, status="degraded")

    req = mock_urlopen.call_args[0][0]
    body = json.loads(req.data)
    assert body["status"] == "degraded"
    assert "message" not in body


@patch("brinkhaustools.common.fleet.client.urllib.request.urlopen")
def test_no_auth_header_without_token(mock_urlopen):
    mock_urlopen.return_value = _mock_urlopen_200()

    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="test",
        machine="m1",
        software="app",
        token=None,
    )
    client.send_status("running", "test")

    req = mock_urlopen.call_args[0][0]
    assert req.get_header("Authorization") is None


# -- Connection state tests ------------------------------------------------


@patch("brinkhaustools.common.fleet.client.urllib.request.urlopen")
def test_successful_post_sets_connected(mock_urlopen):
    mock_urlopen.return_value = _mock_urlopen_200()

    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="test",
        machine="m1",
        software="app",
    )
    assert not client.is_connected
    client.send_status("running", "test")
    assert client.is_connected
    assert client._last_send_success is not None
    assert client._last_connect_error is None


@patch("brinkhaustools.common.fleet.client.urllib.request.urlopen")
def test_http_error_sets_disconnected(mock_urlopen):
    mock_urlopen.side_effect = urllib.error.HTTPError(
        "https://example.com", 401, "Unauthorized", {}, None
    )

    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="test",
        machine="m1",
        software="app",
    )
    client._connected = True  # Simulate was connected
    client.send_status("running", "test")
    assert not client.is_connected
    assert "401" in client._last_connect_error


@patch("brinkhaustools.common.fleet.client.urllib.request.urlopen")
def test_network_error_sets_disconnected(mock_urlopen):
    mock_urlopen.side_effect = ConnectionError("Connection refused")

    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="test",
        machine="m1",
        software="app",
    )
    client._connected = True
    client.send_status("running", "test")
    assert not client.is_connected
    assert "Connection refused" in client._last_connect_error


@patch("brinkhaustools.common.fleet.client.urllib.request.urlopen")
def test_connection_change_callback_connected(mock_urlopen):
    mock_urlopen.return_value = _mock_urlopen_200()

    changes = []
    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="test",
        machine="m1",
        software="app",
    )
    client._on_connection_change = lambda c: changes.append(c)
    client.send_status("running", "test")
    assert changes == [True]


@patch("brinkhaustools.common.fleet.client.urllib.request.urlopen")
def test_connection_change_callback_disconnected(mock_urlopen):
    mock_urlopen.side_effect = urllib.error.HTTPError(
        "https://example.com", 500, "Server Error", {}, None
    )

    changes = []
    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="test",
        machine="m1",
        software="app",
    )
    client._connected = True
    client._on_connection_change = lambda c: changes.append(c)
    client.send_status("running", "test")
    assert changes == [False]


@patch("brinkhaustools.common.fleet.client.urllib.request.urlopen")
def test_no_callback_when_state_unchanged(mock_urlopen):
    mock_urlopen.return_value = _mock_urlopen_200()

    changes = []
    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="test",
        machine="m1",
        software="app",
    )
    client._connected = True  # Already connected
    client._on_connection_change = lambda c: changes.append(c)
    client.send_status("running", "test")
    assert changes == []  # No state change


# -- Lifecycle tests -------------------------------------------------------


@patch("brinkhaustools.common.fleet.client.urllib.request.urlopen")
def test_start_and_stop(mock_urlopen):
    mock_urlopen.return_value = _mock_urlopen_200()

    client = FleetMonitorClient(
        base_url="https://fleet.example.com",
        customer="test",
        machine="m1",
        software="app",
        heartbeat_interval=1,
    )
    client.start()
    assert client._heartbeat_thread is not None
    assert client._heartbeat_thread.is_alive()
    assert client._start_time is not None
    client.stop()
    assert not client._heartbeat_thread.is_alive()
