#
# Copyright (c) 2024-2026, Daily
#
# SPDX-License-Identifier: BSD 2-Clause License
#

"""Tests for the Genesys AudioHook serializer."""

import json

import pytest

from pipecat.frames.frames import InputDTMFFrame, OutputTransportMessageUrgentFrame
from pipecat.serializers.genesys import AudioHookChannel, GenesysAudioHookSerializer


class TestGenesysAudioHookSerializer:
    """Tests for GenesysAudioHookSerializer."""

    # ==================== Initialization Tests ====================

    def test_create_serializer_default_params(self):
        """Test creating serializer with default parameters."""
        serializer = GenesysAudioHookSerializer()

        # session_id is auto-generated as UUID
        assert serializer.session_id != ""
        assert len(serializer.session_id) == 36  # UUID format
        assert serializer.is_open is False
        assert serializer.is_paused is False

    def test_create_serializer_with_custom_params(self):
        """Test creating serializer with custom parameters."""
        params = GenesysAudioHookSerializer.InputParams(
            channel=AudioHookChannel.BOTH,
            sample_rate=16000,
            supported_languages=["es-ES", "en-US"],
            selected_language="es-ES",
            start_paused=True,
        )
        serializer = GenesysAudioHookSerializer(params=params)

        assert serializer.session_id != ""

    # ==================== Response Creation Tests ====================

    def test_create_opened_response(self):
        """Test creating an opened response message."""
        serializer = GenesysAudioHookSerializer()

        msg = serializer.create_opened_response()

        assert msg["type"] == "opened"
        assert msg["version"] == "2"
        assert msg["id"] == serializer.session_id
        assert "parameters" in msg
        assert serializer.is_open is True

    def test_create_opened_response_with_languages(self):
        """Test creating an opened response with language options."""
        serializer = GenesysAudioHookSerializer()

        msg = serializer.create_opened_response(
            supported_languages=["es", "en", "fr"],
            selected_language="es",
        )

        assert msg["parameters"]["supportedLanguages"] == ["es", "en", "fr"]
        assert msg["parameters"]["selectedLanguage"] == "es"

    def test_create_pong_response(self):
        """Test creating a pong response message."""
        serializer = GenesysAudioHookSerializer()

        msg = serializer.create_pong_response()

        assert msg["type"] == "pong"
        assert msg["id"] == serializer.session_id
        assert msg["parameters"] == {}

    def test_create_closed_response(self):
        """Test creating a closed response message."""
        serializer = GenesysAudioHookSerializer()
        serializer._is_open = True

        msg = serializer.create_closed_response()

        assert msg["type"] == "closed"
        assert serializer.is_open is False
        assert msg["parameters"] == {}  # Empty parameters when no output_variables

    def test_create_closed_response_with_output_variables(self):
        """Test creating a closed response with custom output variables."""
        serializer = GenesysAudioHookSerializer()
        serializer._is_open = True

        msg = serializer.create_closed_response(
            output_variables={
                "intent": "billing_inquiry",
                "customer_verified": True,
                "summary": "Customer asked about their bill",
            }
        )

        assert msg["type"] == "closed"
        assert msg["parameters"]["outputVariables"]["intent"] == "billing_inquiry"
        assert msg["parameters"]["outputVariables"]["customer_verified"] is True
        assert msg["parameters"]["outputVariables"]["summary"] == "Customer asked about their bill"

    def test_create_resumed_response(self):
        """Test creating a resumed response message."""
        serializer = GenesysAudioHookSerializer()
        serializer._is_paused = True

        msg = serializer.create_resumed_response()

        assert msg["type"] == "resumed"
        assert serializer.is_paused is False

    def test_create_disconnect_message(self):
        """Test creating a disconnect message."""
        serializer = GenesysAudioHookSerializer()

        msg = serializer.create_disconnect_message(
            reason="completed",
            action="transfer",
        )

        assert msg["type"] == "disconnect"
        assert msg["parameters"]["reason"] == "completed"
        assert msg["parameters"]["outputVariables"]["action"] == "transfer"

    def test_create_disconnect_message_with_output_variables(self):
        """Test creating a disconnect message with custom output variables."""
        serializer = GenesysAudioHookSerializer()

        msg = serializer.create_disconnect_message(
            reason="completed",
            action="finished",
            output_variables={"result": "success", "code": "123"},
        )

        assert msg["parameters"]["outputVariables"]["result"] == "success"
        assert msg["parameters"]["outputVariables"]["code"] == "123"

    def test_create_error_message(self):
        """Test creating an error message."""
        serializer = GenesysAudioHookSerializer()

        msg = serializer.create_error_message(
            code=500,
            message="Internal error",
            retryable=True,
        )

        assert msg["type"] == "error"
        assert msg["parameters"]["code"] == 500
        assert msg["parameters"]["message"] == "Internal error"
        assert msg["parameters"]["retryable"] is True

    # ==================== Message Handling Tests ====================

    @pytest.mark.asyncio
    async def test_handle_open_message(self, sample_open_message):
        """Test handling an open message returns opened frame."""
        serializer = GenesysAudioHookSerializer()

        result = await serializer.deserialize(json.dumps(sample_open_message))

        # Now returns OutputTransportMessageUrgentFrame with opened response
        assert isinstance(result, OutputTransportMessageUrgentFrame)
        assert result.message["type"] == "opened"
        assert serializer.session_id == "test-session-123"
        assert serializer.conversation_id == "conv-456"

    @pytest.mark.asyncio
    async def test_handle_open_message_extracts_participant(self, sample_open_message):
        """Test that open message extracts participant info."""
        serializer = GenesysAudioHookSerializer()

        await serializer.deserialize(json.dumps(sample_open_message))

        assert serializer.participant is not None
        assert serializer.participant["ani"] == "+1234567890"
        assert serializer.participant["dnis"] == "+0987654321"

    @pytest.mark.asyncio
    async def test_handle_open_message_uses_params(self, sample_open_message):
        """Test that open message uses InputParams for response."""
        params = GenesysAudioHookSerializer.InputParams(
            supported_languages=["es-ES", "en-US"],
            selected_language="es-ES",
            start_paused=True,
        )
        serializer = GenesysAudioHookSerializer(params=params)

        result = await serializer.deserialize(json.dumps(sample_open_message))

        assert isinstance(result, OutputTransportMessageUrgentFrame)
        assert result.message["parameters"]["supportedLanguages"] == ["es-ES", "en-US"]
        assert result.message["parameters"]["selectedLanguage"] == "es-ES"
        assert result.message["parameters"]["startPaused"] is True

    @pytest.mark.asyncio
    async def test_handle_open_message_extracts_input_variables(
        self, sample_open_message_with_input_variables
    ):
        """Test that open message extracts inputVariables from Genesys."""
        serializer = GenesysAudioHookSerializer()

        await serializer.deserialize(json.dumps(sample_open_message_with_input_variables))

        assert serializer.input_variables is not None
        assert serializer.input_variables["customer_id"] == "cust-789"
        assert serializer.input_variables["queue_name"] == "billing"
        assert serializer.input_variables["priority"] == "high"
        assert serializer.input_variables["language"] == "es-ES"

    @pytest.mark.asyncio
    async def test_handle_ping_message(self, sample_ping_message):
        """Test handling a ping message returns pong frame."""
        serializer = GenesysAudioHookSerializer()

        result = await serializer.deserialize(json.dumps(sample_ping_message))

        assert isinstance(result, OutputTransportMessageUrgentFrame)
        assert result.message["type"] == "pong"

    @pytest.mark.asyncio
    async def test_handle_close_message(self, sample_close_message):
        """Test handling a close message returns closed frame."""
        serializer = GenesysAudioHookSerializer()
        serializer._is_open = True

        result = await serializer.deserialize(json.dumps(sample_close_message))

        assert isinstance(result, OutputTransportMessageUrgentFrame)
        assert result.message["type"] == "closed"
        assert serializer.is_open is False

    @pytest.mark.asyncio
    async def test_handle_close_message_includes_output_variables(self, sample_close_message):
        """Test that close response includes output variables when set."""
        serializer = GenesysAudioHookSerializer()
        serializer._is_open = True

        # Set output variables before close
        serializer.set_output_variables(
            {"intent": "support", "resolved": True, "transfer_to": "agent_queue"}
        )

        result = await serializer.deserialize(json.dumps(sample_close_message))

        assert isinstance(result, OutputTransportMessageUrgentFrame)
        assert result.message["type"] == "closed"
        assert result.message["parameters"]["outputVariables"]["intent"] == "support"
        assert result.message["parameters"]["outputVariables"]["resolved"] is True
        assert result.message["parameters"]["outputVariables"]["transfer_to"] == "agent_queue"

    # ==================== Output Variables Tests ====================

    def test_set_output_variables(self):
        """Test setting output variables."""
        serializer = GenesysAudioHookSerializer()

        assert serializer.output_variables is None

        serializer.set_output_variables({"intent": "billing", "score": 0.95})

        assert serializer.output_variables is not None
        assert serializer.output_variables["intent"] == "billing"
        assert serializer.output_variables["score"] == 0.95

    def test_set_output_variables_overwrites(self):
        """Test that setting output variables overwrites previous values."""
        serializer = GenesysAudioHookSerializer()

        serializer.set_output_variables({"first": "value"})
        serializer.set_output_variables({"second": "value"})

        assert "first" not in serializer.output_variables
        assert serializer.output_variables["second"] == "value"

    @pytest.mark.asyncio
    async def test_handle_pause_message(self, sample_pause_message):
        """Test handling a pause message."""
        serializer = GenesysAudioHookSerializer()
        serializer._is_open = True

        result = await serializer.deserialize(json.dumps(sample_pause_message))

        assert result is None  # Pause is handled internally
        assert serializer.is_paused is True

    @pytest.mark.asyncio
    async def test_handle_update_message(self, sample_update_message):
        """Test handling an update message."""
        serializer = GenesysAudioHookSerializer()

        result = await serializer.deserialize(json.dumps(sample_update_message))

        assert result is None  # Update is handled internally
        assert serializer.participant is not None
        assert serializer.participant["name"] == "John Doe"

    @pytest.mark.asyncio
    async def test_handle_error_message(self, sample_error_message):
        """Test handling an error message."""
        serializer = GenesysAudioHookSerializer()

        result = await serializer.deserialize(json.dumps(sample_error_message))

        assert result is None  # Error is logged but returns None

    # ==================== DTMF Tests ====================

    @pytest.mark.asyncio
    async def test_handle_dtmf_digit(self, sample_dtmf_message):
        """Test handling a DTMF digit message."""
        serializer = GenesysAudioHookSerializer()

        result = await serializer.deserialize(json.dumps(sample_dtmf_message))

        assert isinstance(result, InputDTMFFrame)
        assert result.button.value == "5"

    @pytest.mark.asyncio
    async def test_handle_dtmf_star(self, sample_dtmf_star_message):
        """Test handling a DTMF star (*) message."""
        serializer = GenesysAudioHookSerializer()

        result = await serializer.deserialize(json.dumps(sample_dtmf_star_message))

        assert isinstance(result, InputDTMFFrame)
        assert result.button.value == "*"

    @pytest.mark.asyncio
    async def test_handle_dtmf_hash(self, sample_dtmf_hash_message):
        """Test handling a DTMF hash (#) message."""
        serializer = GenesysAudioHookSerializer()

        result = await serializer.deserialize(json.dumps(sample_dtmf_hash_message))

        assert isinstance(result, InputDTMFFrame)
        assert result.button.value == "#"

    @pytest.mark.asyncio
    async def test_handle_dtmf_empty_digit(self):
        """Test handling a DTMF message without digit."""
        serializer = GenesysAudioHookSerializer()

        dtmf_msg = {
            "version": "2",
            "type": "dtmf",
            "seq": 6,
            "id": "test-session-123",
            "parameters": {},
        }

        result = await serializer.deserialize(json.dumps(dtmf_msg))

        assert result is None  # No digit provided

    # ==================== Sequence Number Tests ====================

    def test_sequence_numbers_increment(self):
        """Test that sequence numbers increment correctly."""
        serializer = GenesysAudioHookSerializer()

        response1 = serializer.create_pong_response()
        response2 = serializer.create_pong_response()
        response3 = serializer.create_pong_response()

        assert response1["seq"] == 1
        assert response2["seq"] == 2
        assert response3["seq"] == 3
