import json
from unittest.mock import AsyncMock

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient

from control_backend.api.v1.endpoints import program
from control_backend.schemas.program import Program


@pytest.fixture
def app():
    """Create a FastAPI app with the /program route and mock socket."""
    app = FastAPI()
    app.include_router(program.router)
    return app


@pytest.fixture
def client(app):
    """Create a TestClient."""
    return TestClient(app)


def make_valid_program_dict():
    """Helper to create a valid Program JSON structure."""
    return {
        "phases": [
            {
                "id": "phase1",
                "label": "basephase",
                "norms": [{"id": "n1", "label": "norm", "norm": "be nice"}],
                "goals": [
                    {"id": "g1", "label": "goal", "description": "test goal", "achieved": False}
                ],
                "triggers": [
                    {
                        "id": "t1",
                        "label": "trigger",
                        "type": "keywords",
                        "keywords": [
                            {"id": "kw1", "keyword": "keyword1"},
                            {"id": "kw2", "keyword": "keyword2"},
                        ],
                    },
                ],
            }
        ]
    }


def test_receive_program_success(client):
    """Valid Program JSON should be parsed and sent through the socket."""
    mock_pub_socket = AsyncMock()
    client.app.state.endpoints_pub_socket = mock_pub_socket

    program_dict = make_valid_program_dict()

    response = client.post("/program", json=program_dict)

    assert response.status_code == 202
    assert response.json() == {"status": "Program parsed"}

    # Verify socket call
    mock_pub_socket.send_multipart.assert_awaited_once()
    args, kwargs = mock_pub_socket.send_multipart.await_args

    assert args[0][0] == b"program"

    sent_bytes = args[0][1]
    sent_obj = json.loads(sent_bytes.decode())

    expected_obj = Program.model_validate(program_dict).model_dump()
    assert sent_obj == expected_obj


def test_receive_program_invalid_json(client):
    """
    Invalid JSON (malformed) -> FastAPI never calls endpoint.
    It returns a 422 Unprocessable Entity.
    """
    mock_pub_socket = AsyncMock()
    client.app.state.endpoints_pub_socket = mock_pub_socket

    # FastAPI only accepts valid JSON bodies, so send raw string
    response = client.post("/program", content="{invalid json}")

    assert response.status_code == 422
    mock_pub_socket.send_multipart.assert_not_called()


def test_receive_program_invalid_deep_structure(client):
    """
    Valid JSON but schema invalid -> Pydantic throws validation error -> 422.
    """
    mock_pub_socket = AsyncMock()
    client.app.state.endpoints_pub_socket = mock_pub_socket

    # Missing "value" in norms element
    bad_program = {
        "phases": [
            {
                "id": "phase1",
                "name": "deepfail",
                "nextPhaseId": "phase2",
                "phaseData": {
                    "norms": [
                        {"id": "n1", "name": "norm"}  # INVALID: missing "value"
                    ],
                    "goals": [
                        {"id": "g1", "name": "goal", "description": "desc", "achieved": False}
                    ],
                    "triggers": [
                        {"id": "t1", "label": "trigger", "type": "keyword", "value": ["start"]}
                    ],
                },
            }
        ]
    }

    response = client.post("/program", json=bad_program)

    assert response.status_code == 422
    mock_pub_socket.send_multipart.assert_not_called()
