"""Tests for StatusMonitor."""

import json
import os
import tempfile
from unittest.mock import MagicMock, patch

from brinkhaustools.common.diagnosis import SelfDiagnosisEngine
from brinkhaustools.common.fleet.monitor import StatusMonitor


def _make_config(config_data: dict) -> str:
    """Write a temporary config file and return its path."""
    fd, path = tempfile.mkstemp(suffix=".json")
    os.close(fd)
    with open(path, "w") as f:
        json.dump(config_data, f)
    return path


# -- Config reading tests --------------------------------------------------


def test_read_config_with_base_url():
    path = _make_config({
        "fleetmanagement": {
            "base_url": "https://custom.server.com",
            "customer_name": "testKunde",
            "machine": "testMachine",
            "token": "test-token",
        }
    })
    try:
        monitor = StatusMonitor(config_path=path, software_name="test-app")
        assert monitor._client.base_url == "https://custom.server.com"
        assert monitor._server_display == "https://custom.server.com"
    finally:
        os.unlink(path)


def test_read_config_fallback_to_broker():
    path = _make_config({
        "fleetmanagement": {
            "broker": "fleet.example.com",
            "customer_name": "testKunde",
            "machine": "testMachine",
            "token": "test-token",
        }
    })
    try:
        monitor = StatusMonitor(config_path=path, software_name="test-app")
        assert monitor._client.base_url == "https://fleet.example.com"
        assert "fleet.example.com" in monitor._server_display
    finally:
        os.unlink(path)


def test_read_config_customer_and_machine():
    path = _make_config({
        "fleetmanagement": {
            "customer_name": "My Customer",
            "machine": "Maschine 1",
        }
    })
    try:
        monitor = StatusMonitor(config_path=path, software_name="test-app")
        assert monitor._client.customer == "my-customer"
        assert monitor._client.machine == "maschine-1"
    finally:
        os.unlink(path)


def test_read_config_token():
    path = _make_config({
        "fleetmanagement": {
            "token": "fmt_test123",
        }
    })
    try:
        monitor = StatusMonitor(config_path=path, software_name="test-app")
        assert monitor._client.token == "fmt_test123"
    finally:
        os.unlink(path)


def test_env_overrides(monkeypatch):
    monkeypatch.setenv("STATUS_MONITOR_CUSTOMERID", "env-customer")
    monkeypatch.setenv("STATUS_MONITOR_MACHINEID", "env-machine")
    path = _make_config({
        "fleetmanagement": {
            "customer_name": "file-customer",
            "machine": "file-machine",
        }
    })
    try:
        monitor = StatusMonitor(config_path=path, software_name="test-app")
        assert monitor._client.customer == "env-customer"
        assert monitor._client.machine == "env-machine"
    finally:
        os.unlink(path)


def test_missing_config_uses_defaults():
    monitor = StatusMonitor(
        config_path="/nonexistent/config.json",
        software_name="test-app",
    )
    assert monitor._client.base_url == "https://fleet.brinkhaus-gmbh.de"


def test_no_config_path():
    monitor = StatusMonitor(config_path=None, software_name="test-app")
    assert monitor._client.base_url == "https://fleet.brinkhaus-gmbh.de"


def test_missing_config_notifies_diagnosis():
    diag = SelfDiagnosisEngine()
    diag.reset_for_testing()
    StatusMonitor(
        config_path="/nonexistent/config.json",
        diagnosis=diag,
        software_name="test-app",
    )
    msgs = diag.get_messages()
    codes = [m.code for m in msgs]
    assert 7001 in codes


# -- StatusSource interface tests ------------------------------------------


def test_get_status_fields():
    monitor = StatusMonitor(config_path=None, software_name="test-app")
    status = monitor.get_status()
    assert "Connected" in status
    assert "Server" in status
    assert status["Connected"] is False


def test_get_name():
    monitor = StatusMonitor(config_path=None, software_name="test-app")
    assert monitor.get_name() == "FleetManager"


# -- Diagnosis mapping tests -----------------------------------------------


def test_map_diagnosis_no_diagnosis():
    monitor = StatusMonitor(config_path=None, software_name="test-app")
    result = monitor._map_diagnosis()
    assert len(result) == 1
    assert result[0] == (0, "All self tests passed", 0)


def test_map_diagnosis_no_messages():
    diag = SelfDiagnosisEngine()
    diag.reset_for_testing()
    monitor = StatusMonitor(
        config_path=None,
        diagnosis=diag,
        software_name="test-app",
    )
    result = monitor._map_diagnosis()
    assert len(result) == 1
    assert result[0] == (0, "All self tests passed", 0)


def test_map_diagnosis_critical_is_severity_2():
    diag = SelfDiagnosisEngine()
    diag.reset_for_testing()
    diag.notify(200, "Critical error", critical=True)

    monitor = StatusMonitor(
        config_path=None,
        diagnosis=diag,
        software_name="test-app",
    )
    result = monitor._map_diagnosis()
    assert len(result) == 1
    assert result[0][0] == 200
    assert result[0][2] == 2  # severity


def test_map_diagnosis_information_is_severity_0():
    diag = SelfDiagnosisEngine()
    diag.reset_for_testing()
    diag.notify(100, "Info message", information=True)

    monitor = StatusMonitor(
        config_path=None,
        diagnosis=diag,
        software_name="test-app",
    )
    result = monitor._map_diagnosis()
    assert len(result) == 1
    assert result[0][0] == 100
    assert result[0][2] == 0  # severity


def test_map_diagnosis_warning_is_severity_1():
    diag = SelfDiagnosisEngine()
    diag.reset_for_testing()
    diag.notify(300, "Warning message")

    monitor = StatusMonitor(
        config_path=None,
        diagnosis=diag,
        software_name="test-app",
    )
    result = monitor._map_diagnosis()
    assert len(result) == 1
    assert result[0][0] == 300
    assert result[0][2] == 1  # severity


def test_map_diagnosis_multiple_messages():
    diag = SelfDiagnosisEngine()
    diag.reset_for_testing()
    diag.notify(100, "Info", information=True)
    diag.notify(200, "Critical", critical=True)
    diag.notify(300, "Warning")

    monitor = StatusMonitor(
        config_path=None,
        diagnosis=diag,
        software_name="test-app",
    )
    result = monitor._map_diagnosis()
    assert len(result) == 3
    severities = {code: sev for code, _, sev in result}
    assert severities[100] == 0
    assert severities[200] == 2
    assert severities[300] == 1


# -- Connection diagnosis tests --------------------------------------------


def test_update_diagnosis_connected():
    diag = SelfDiagnosisEngine()
    diag.reset_for_testing()
    monitor = StatusMonitor(
        config_path=None,
        diagnosis=diag,
        software_name="test-app",
    )
    monitor._client._connected = True
    monitor._update_diagnosis()
    codes = [m.code for m in diag.get_messages()]
    assert 7002 not in codes


def test_update_diagnosis_disconnected():
    diag = SelfDiagnosisEngine()
    diag.reset_for_testing()
    monitor = StatusMonitor(
        config_path=None,
        diagnosis=diag,
        software_name="test-app",
    )
    monitor._client._connected = False
    monitor._client._last_connect_error = "Connection refused"
    monitor._update_diagnosis()
    codes = [m.code for m in diag.get_messages()]
    assert 7002 in codes


# -- StatusEngine integration tests ----------------------------------------


def test_status_engine_wired():
    engine = MagicMock()
    monitor = StatusMonitor(
        config_path=None,
        software_name="test-app",
        status_engine=engine,
    )
    assert monitor._status_engine is engine


def test_controller_sends_snapshot_when_engine_available():
    engine = MagicMock()
    engine.get_last_status.return_value = {"SelfDiagnosis": {"System Status": True}}

    monitor = StatusMonitor(
        config_path=None,
        software_name="test-app",
        status_engine=engine,
    )
    monitor._client.send_diagnostics = MagicMock()
    monitor._client.send_snapshot = MagicMock()
    monitor._client.send_status = MagicMock()

    # Execute the controller body logic directly
    monitor._update_diagnosis()
    diagnostics = monitor._map_diagnosis()
    monitor._client.send_diagnostics(diagnostics)

    status_str = "running"
    message = "All OK"
    snapshot = monitor._status_engine.get_last_status()
    monitor._client.send_snapshot(snapshot, status=status_str, message=message)

    monitor._client.send_snapshot.assert_called_once_with(
        {"SelfDiagnosis": {"System Status": True}},
        status="running",
        message="All OK",
    )
    monitor._client.send_status.assert_not_called()


def test_controller_sends_status_when_no_engine():
    monitor = StatusMonitor(
        config_path=None,
        software_name="test-app",
        status_engine=None,
    )
    monitor._client.send_diagnostics = MagicMock()
    monitor._client.send_snapshot = MagicMock()
    monitor._client.send_status = MagicMock()

    # Execute the controller body logic directly (no engine path)
    monitor._update_diagnosis()
    diagnostics = monitor._map_diagnosis()
    monitor._client.send_diagnostics(diagnostics)
    monitor._client.send_status("running", "All OK")

    monitor._client.send_status.assert_called_once_with("running", "All OK")
    monitor._client.send_snapshot.assert_not_called()
