"""
This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences)
"""

from unittest.mock import MagicMock, patch

import pytest
from fastapi import FastAPI, HTTPException
from fastapi.testclient import TestClient
from starlette.responses import StreamingResponse

from control_backend.api.v1.endpoints import logs


@pytest.fixture
def client():
    """TestClient with logs router included."""
    app = FastAPI()
    app.include_router(logs.router)
    return TestClient(app)


@pytest.mark.asyncio
async def test_log_stream_endpoint_lines(client):
    """Call /logs/stream with a mocked ZMQ socket to cover all lines."""

    # Dummy socket to mock ZMQ behavior
    class DummySocket:
        def __init__(self):
            self.subscribed = []
            self.connected = False
            self.recv_count = 0

        def subscribe(self, topic):
            self.subscribed.append(topic)

        def connect(self, addr):
            self.connected = True

        async def recv_multipart(self):
            # Return one message, then stop generator
            if self.recv_count == 0:
                self.recv_count += 1
                return (b"INFO", b"test message")
            else:
                raise StopAsyncIteration

    dummy_socket = DummySocket()

    # Patch Context.instance().socket() to return dummy socket
    with patch("control_backend.api.v1.endpoints.logs.Context.instance") as mock_context:
        mock_context.return_value.socket.return_value = dummy_socket

        # Call the endpoint directly
        response = await logs.log_stream()
        assert isinstance(response, StreamingResponse)

        # Fetch one chunk from the generator
        gen = response.body_iterator
        chunk = await gen.__anext__()
        if isinstance(chunk, bytes):
            chunk = chunk.decode("utf-8")
        assert "data:" in chunk

        # Optional: assert subscribe/connect were called
        assert dummy_socket.subscribed  # at least some log levels subscribed
        assert dummy_socket.connected  # connect was called


@patch("control_backend.api.v1.endpoints.logs.LOGGING_DIR")
def test_files_endpoint(LOGGING_DIR, client):
    file_1, file_2 = MagicMock(), MagicMock()
    file_1.name = "file_1"
    file_2.name = "file_2"
    LOGGING_DIR.glob.return_value = [file_1, file_2]
    result = client.get("/api/logs/files")

    assert result.status_code == 200
    assert result.json() == ["file_1", "file_2"]


@patch("control_backend.api.v1.endpoints.logs.FileResponse")
@patch("control_backend.api.v1.endpoints.logs.LOGGING_DIR")
def test_log_file_endpoint_success(LOGGING_DIR, MockFileResponse, client):
    mock_file_path = MagicMock()
    mock_file_path.is_relative_to.return_value = True
    mock_file_path.is_file.return_value = True
    mock_file_path.name = "test.log"

    LOGGING_DIR.__truediv__ = MagicMock(return_value=mock_file_path)
    mock_file_path.resolve.return_value = mock_file_path

    MockFileResponse.return_value = MagicMock()

    result = client.get("/api/logs/files/test.log")

    assert result.status_code == 200
    MockFileResponse.assert_called_once_with(mock_file_path, filename="test.log")


@pytest.mark.asyncio
@patch("control_backend.api.v1.endpoints.logs.LOGGING_DIR")
async def test_log_file_endpoint_path_traversal(LOGGING_DIR):
    from control_backend.api.v1.endpoints.logs import log_file

    mock_file_path = MagicMock()
    mock_file_path.is_relative_to.return_value = False

    LOGGING_DIR.__truediv__ = MagicMock(return_value=mock_file_path)
    mock_file_path.resolve.return_value = mock_file_path

    with pytest.raises(HTTPException) as exc_info:
        await log_file("../secret.txt")

    assert exc_info.value.status_code == 400
    assert exc_info.value.detail == "Invalid filename."


@patch("control_backend.api.v1.endpoints.logs.LOGGING_DIR")
def test_log_file_endpoint_file_not_found(LOGGING_DIR, client):
    mock_file_path = MagicMock()
    mock_file_path.is_relative_to.return_value = True
    mock_file_path.is_file.return_value = False

    LOGGING_DIR.__truediv__ = MagicMock(return_value=mock_file_path)
    mock_file_path.resolve.return_value = mock_file_path

    result = client.get("/api/logs/files/nonexistent.log")

    assert result.status_code == 404
    assert result.json()["detail"] == "File not found."
