# Copyright (c) Microsoft. All rights reserved.

import json
from unittest.mock import Mock

import pytest

from semantic_kernel.connectors.ai.bedrock.services.model_provider.bedrock_model_provider import BedrockModelProvider
from semantic_kernel.contents.chat_history import ChatHistory


@pytest.fixture()
def model_id(request) -> str:
    if hasattr(request, "param"):
        return request.param
    return "test_model_id"


@pytest.fixture()
def service_id() -> str:
    return "test_service_id"


@pytest.fixture()
def chat_history() -> ChatHistory:
    chat_history = ChatHistory(system_message="You are a helpful assistant.")

    chat_history.add_user_message("Hello!")
    chat_history.add_assistant_message("Hi! How can I help you today?")
    chat_history.add_system_message("Be polite and respectful.")
    chat_history.add_user_message("I need help with a task.")

    return chat_history


@pytest.fixture()
def bedrock_unit_test_env(monkeypatch, exclude_list, override_env_param_dict):
    """Fixture to set environment variables for Amazon Bedrock AI connector unit tests."""
    if exclude_list is None:
        exclude_list = []

    if override_env_param_dict is None:
        override_env_param_dict = {}

    env_vars = {
        "BEDROCK_TEXT_MODEL_ID": "env_test_text_model_id",
        "BEDROCK_CHAT_MODEL_ID": "env_test_chat_model_id",
        "BEDROCK_EMBEDDING_MODEL_ID": "env_test_embedding_model_id",
        "BEDROCK_MODEL_PROVIDER": "amazon",
    }

    env_vars.update(override_env_param_dict)

    for key, value in env_vars.items():
        if key not in exclude_list:
            monkeypatch.setenv(key, value)
        else:
            monkeypatch.delenv(key, raising=False)

    return env_vars


class MockBedrockClient(Mock):
    def __init__(self, *args, **kwargs):
        pass

    def get_foundation_model(self, *args, **kwargs):
        return {
            "modelDetails": {
                "responseStreamingSupported": True,
                "inputModalities": ["TEXT"],
                "outputModalities": ["TEXT", "EMBEDDING"],
            }
        }


class MockBedrockRuntimeClient(Mock):
    def __init__(self, *args, **kwargs):
        pass

    def converse(self, *args, **kwargs):
        pass

    def converse_stream(self, *args, **kwargs):
        pass

    def invoke_model(self, *args, **kwargs):
        pass

    def invoke_model_with_response_stream(self, *args, **kwargs):
        pass


# region mock chat completion responses


@pytest.fixture()
def mock_bedrock_chat_completion_response():
    # https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-call.html#conversation-inference-call-response
    return {
        "output": {
            "message": {
                "role": "assistant",
                "content": [
                    {
                        "text": "Hi! How can I help you today?",
                    }
                ],
            }
        },
        "stopReason": "end_turn",
        "usage": {
            "inputTokens": 125,
            "outputTokens": 60,
            "totalTokens": 185,
        },
    }


@pytest.fixture()
def mock_bedrock_streaming_chat_completion_response():
    # https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-call.html#conversation-inference-call-response
    events = [
        {"messageStart": {"role": "assistant"}},
        {"contentBlockStart": {"contentBlockIndex": 0, "start": {}}},
        {"contentBlockDelta": {"contentBlockIndex": 0, "delta": {"text": "Hi! "}}},
        {"contentBlockDelta": {"contentBlockIndex": 0, "delta": {"text": "How can "}}},
        {"contentBlockDelta": {"contentBlockIndex": 0, "delta": {"text": "I help you today?"}}},
        {"contentBlockStop": {"contentBlockIndex": 0}},
        {"messageStop": {"stopReason": "end_turn"}},
        {
            "metadata": {
                "metrics": {"latencyMs": 1000},
                "usage": {"inputTokens": 125, "outputTokens": 60, "totalTokens": 185},
            }
        },
    ]

    def event_stream(events):
        yield from events

    return {"stream": event_stream(events)}


@pytest.fixture()
def mock_bedrock_streaming_chat_completion_invalid_response():
    events = [
        {"unknown": {}},
    ]

    def event_stream(events):
        yield from events

    return {"stream": event_stream(events)}


# endregion

# region mock text completion responses


@pytest.fixture()
def output_text():
    return "Hi! How can I help you today?"


@pytest.fixture()
def model_provider():
    return BedrockModelProvider.AMAZON


@pytest.fixture()
def mock_bedrock_text_completion_response(
    model_id: str,
    output_text: str,
    request,
):
    # Check if model_provider fixture is requested by the test
    model_provider = None
    if "model_provider" in request.fixturenames:
        model_provider = request.getfixturevalue("model_provider")
    else:
        model_provider = BedrockModelProvider.to_model_provider(model_id)

    match model_provider:
        case BedrockModelProvider.AMAZON:
            body = {
                "inputTextTokenCount": 10,
                "results": [
                    {
                        "tokenCount": 10,
                        "outputText": output_text,
                        "completionReason": "FINISHED ",
                    }
                ],
            }
        case BedrockModelProvider.ANTHROPIC:
            body = {
                "completion": output_text,
                "stop_reason": "stop_sequence",
                "stop": "",
            }
        case BedrockModelProvider.COHERE:
            body = {
                "generations": [
                    {
                        "text": output_text,
                    }
                ],
            }
        case BedrockModelProvider.AI21LABS:
            body = {
                "completions": [
                    {
                        "data": {
                            "text": output_text,
                        }
                    }
                ],
            }
        case BedrockModelProvider.META:
            body = {
                "generation": output_text,
                "prompt_token_count": 10,
                "generation_token_count": 10,
            }
        case BedrockModelProvider.MISTRALAI:
            body = {"outputs": [{"text": output_text}]}

    mock = Mock()
    mock.read.return_value = json.dumps(body)

    return {"body": mock}


@pytest.fixture()
def mock_bedrock_streaming_text_completion_response(
    model_id: str,
    output_text: str,
    request,
):
    # Check if model_provider fixture is requested by the test
    model_provider = None
    if "model_provider" in request.fixturenames:
        model_provider = request.getfixturevalue("model_provider")
    else:
        model_provider = BedrockModelProvider.to_model_provider(model_id)

    match model_provider:
        case BedrockModelProvider.AMAZON:
            chunks = [
                {
                    "chunk": {
                        "bytes": json.dumps({
                            "inputTextTokenCount": 10,
                            "totalOutputTextTokenCount": 10,
                            "outputText": chunk,
                        }).encode(),
                    }
                }
                for chunk in [output_text[i : i + 3] for i in range(0, len(output_text), 3)]
            ]

    def event_stream(events):
        yield from events

    return {"body": event_stream(chunks)}


# endregion


# region mock text embedding responses


@pytest.fixture()
def mock_bedrock_text_embedding_response(
    model_id: str,
    request,
):
    # Check if model_provider fixture is requested by the test
    model_provider = None
    if "model_provider" in request.fixturenames:
        model_provider = request.getfixturevalue("model_provider")
    else:
        model_provider = BedrockModelProvider.to_model_provider(model_id)

    match model_provider:
        case BedrockModelProvider.AMAZON:
            body = {
                "embedding": [0.1, 0.2, 0.3],
            }
        case BedrockModelProvider.COHERE:
            body = {
                "embeddings": [[0.1, 0.2, 0.3]],
            }

    mock = Mock()
    mock.read.return_value = json.dumps(body)

    return {"body": mock}


@pytest.fixture()
def mock_bedrock_text_embedding_invalid_response(model_id: str):
    model_provider = BedrockModelProvider.to_model_provider(model_id)

    match model_provider:
        case BedrockModelProvider.AMAZON:
            body = {"embedding": 0.1}
        case BedrockModelProvider.COHERE:
            body = {"embeddings": 0.1}

    mock = Mock()
    mock.read.return_value = json.dumps(body)

    return {"body": mock}


# endregion
