"""Tests for LangfusePromptProvider integration."""

from __future__ import annotations

from typing import ClassVar

from pydantic import BaseModel

import pytest

from dr_langfuse.errors.codes import ErrorCode
from dr_langfuse.errors.langfuse_error import LangfuseError
from dr_langfuse.prompts.langfuse_prompt_provider import LangfusePromptProvider
from dr_langfuse.prompts.prompt_fetch_request import PromptFetchRequest


class _ApiErrorLike(RuntimeError):
    def __init__(self, status_code: int) -> None:
        super().__init__(f"api status {status_code}")
        self.status_code = status_code


class _PromptResult(BaseModel):
    prompt: list[dict[str, str]]
    labels: list[str] | None = None
    version: int = 7

    def compile(self, **variables: object) -> list[dict[str, str]]:
        topic = str(variables.get("topic", ""))
        return [
            {"role": "system", "content": "You are concise."},
            {"role": "user", "content": f"Summarize {topic}."},
        ]


class _FakeLangfuseClient:
    def get_prompt(
        self, name: str, label: str | None = None, version: int | None = None
    ) -> _PromptResult:
        del name, label, version
        return _PromptResult(
            prompt=[
                {"role": "system", "content": "You are concise."},
                {"role": "user", "content": "Summarize {{topic}}."},
            ],
            labels=["prod"],
        )


def test_prompt_provider_success_path() -> None:
    provider = LangfusePromptProvider(client=_FakeLangfuseClient())
    payload = provider.fetch_prompt(
        PromptFetchRequest(prompt_name="summarize", variables={"topic": "the incident"})
    )

    assert payload.prompt_name == "summarize"
    assert payload.system_content == "You are concise."
    assert payload.task_content == "Summarize the incident."
    assert payload.label == "prod"
    assert payload.version == 7


def test_prompt_provider_uses_request_label_when_set() -> None:
    provider = LangfusePromptProvider(client=_FakeLangfuseClient())
    payload = provider.fetch_prompt(
        PromptFetchRequest(
            prompt_name="summarize",
            label="staging",
            variables={"topic": "the incident"},
        )
    )

    assert payload.label == "staging"


def test_prompt_provider_maps_auth_errors() -> None:
    class _AuthFailClient:
        def get_prompt(
            self, name: str, label: str | None = None, version: int | None = None
        ) -> None:
            del name, label, version
            raise _ApiErrorLike(401)

    provider = LangfusePromptProvider(client=_AuthFailClient())

    with pytest.raises(LangfuseError) as exc_info:
        provider.fetch_prompt(PromptFetchRequest(prompt_name="x"))

    assert exc_info.value.error.code == ErrorCode.AUTH
    assert exc_info.value.error.retriable is False
    assert exc_info.value.error.details.get("status_code") == 401


def test_prompt_provider_does_not_misclassify_author_text_as_auth() -> None:
    class _Client:
        def get_prompt(
            self, name: str, label: str | None = None, version: int | None = None
        ) -> None:
            del name, label, version
            raise RuntimeError("author field missing in response")

    provider = LangfusePromptProvider(client=_Client())

    with pytest.raises(LangfuseError) as exc_info:
        provider.fetch_prompt(PromptFetchRequest(prompt_name="x"))

    assert exc_info.value.error.code == ErrorCode.INTERNAL_ERROR


def test_prompt_provider_accepts_compile_with_named_kwargs_only() -> None:
    class _Prompt:
        labels: ClassVar[list[str]] = ["prod"]
        version = 3

        def compile(self, *, topic: object) -> list[dict[str, str]]:
            return [
                {"role": "user", "content": f"Summarize {topic}."},
            ]

    class _Client:
        def get_prompt(
            self, name: str, label: str | None = None, version: int | None = None
        ) -> _Prompt:
            del name, label, version
            return _Prompt()

    provider = LangfusePromptProvider(client=_Client())
    payload = provider.fetch_prompt(
        PromptFetchRequest(prompt_name="summarize", variables={"topic": "incident"})
    )

    assert payload.task_content == "Summarize incident."
    assert payload.version == 3


def test_prompt_provider_reports_unknown_compile_variables() -> None:
    class _Prompt:
        version = 1

        def compile(self, *, topic: object) -> list[dict[str, str]]:
            return [
                {"role": "user", "content": f"Summarize {topic}."},
            ]

    class _Client:
        def get_prompt(
            self, name: str, label: str | None = None, version: int | None = None
        ) -> _Prompt:
            del name, label, version
            return _Prompt()

    provider = LangfusePromptProvider(client=_Client())
    with pytest.raises(LangfuseError) as exc_info:
        provider.fetch_prompt(
            PromptFetchRequest(
                prompt_name="summarize",
                variables={"topic": "incident", "extra": "x"},
            )
        )

    assert exc_info.value.error.code == ErrorCode.MALFORMED_REQUEST
    assert exc_info.value.error.details.get("reason") == "unknown_compile_variables"


def test_prompt_provider_wraps_invalid_payload_shape_in_error() -> None:
    class _BadVersionClient:
        def get_prompt(
            self, name: str, label: str | None = None, version: int | None = None
        ) -> object:
            del name, label, version

            class _Prompt:
                prompt = "hello"
                version = "v1"

            return _Prompt()

    provider = LangfusePromptProvider(client=_BadVersionClient())

    with pytest.raises(LangfuseError) as exc_info:
        provider.fetch_prompt(PromptFetchRequest(prompt_name="x"))

    assert exc_info.value.error.code == ErrorCode.MALFORMED_REQUEST
    assert exc_info.value.error.details.get("reason") == "missing_compile_method"


def test_prompt_provider_maps_compile_type_error_to_malformed_request() -> None:
    class _BadCompilePrompt:
        labels: ClassVar[list[str]] = ["prod"]
        version = 1

        def compile(self, *args: object, **kwargs: object) -> str:
            if kwargs:
                raise TypeError("template compile failed")
            del args
            return "incorrect-fallback-path"

    class _CompileTypeErrorClient:
        def get_prompt(
            self, name: str, label: str | None = None, version: int | None = None
        ) -> _BadCompilePrompt:
            del name, label, version
            return _BadCompilePrompt()

    provider = LangfusePromptProvider(client=_CompileTypeErrorClient())

    with pytest.raises(LangfuseError) as exc_info:
        provider.fetch_prompt(
            PromptFetchRequest(prompt_name="summarize", variables={"topic": "incident"})
        )

    assert exc_info.value.error.code == ErrorCode.MALFORMED_REQUEST
    assert exc_info.value.error.details.get("reason") == "compile_invocation_type_error"


def test_prompt_provider_fails_fast_when_task_content_missing() -> None:
    class _SystemOnlyPrompt:
        labels: ClassVar[list[str]] = ["prod"]
        version = 1

        def compile(self, **variables: object) -> list[dict[str, str]]:
            del variables
            return [{"role": "system", "content": "system only"}]

    class _Client:
        def get_prompt(
            self, name: str, label: str | None = None, version: int | None = None
        ) -> _SystemOnlyPrompt:
            del name, label, version
            return _SystemOnlyPrompt()

    provider = LangfusePromptProvider(client=_Client())

    with pytest.raises(LangfuseError) as exc_info:
        provider.fetch_prompt(PromptFetchRequest(prompt_name="x"))

    assert exc_info.value.error.code == ErrorCode.MALFORMED_REQUEST
    assert exc_info.value.error.details.get("reason") == "invalid_user_message_count"


def test_prompt_provider_rejects_multiple_user_messages_even_if_one_is_blank() -> None:
    class _Prompt:
        labels: ClassVar[list[str]] = ["prod"]
        version = 1

        def compile(self, **variables: object) -> list[dict[str, str]]:
            del variables
            return [
                {"role": "user", "content": "real task"},
                {"role": "user", "content": "   "},
            ]

    class _Client:
        def get_prompt(
            self, name: str, label: str | None = None, version: int | None = None
        ) -> _Prompt:
            del name, label, version
            return _Prompt()

    provider = LangfusePromptProvider(client=_Client())

    with pytest.raises(LangfuseError) as exc_info:
        provider.fetch_prompt(PromptFetchRequest(prompt_name="x"))

    assert exc_info.value.error.code == ErrorCode.MALFORMED_REQUEST
    assert exc_info.value.error.details.get("reason") == "invalid_user_message_count"


def test_prompt_provider_rejects_blank_user_message_content() -> None:
    class _Prompt:
        labels: ClassVar[list[str]] = ["prod"]
        version = 1

        def compile(self, **variables: object) -> list[dict[str, str]]:
            del variables
            return [
                {"role": "user", "content": "   "},
            ]

    class _Client:
        def get_prompt(
            self, name: str, label: str | None = None, version: int | None = None
        ) -> _Prompt:
            del name, label, version
            return _Prompt()

    provider = LangfusePromptProvider(client=_Client())

    with pytest.raises(LangfuseError) as exc_info:
        provider.fetch_prompt(PromptFetchRequest(prompt_name="x"))

    assert exc_info.value.error.code == ErrorCode.MALFORMED_REQUEST
    assert exc_info.value.error.details.get("reason") == "empty_user_message_content"


def test_prompt_provider_allows_missing_system_content() -> None:
    class _TaskOnlyPrompt:
        labels: ClassVar[list[str]] = ["prod"]
        version = 1

        def compile(self, **variables: object) -> list[dict[str, str]]:
            del variables
            return [{"role": "user", "content": "execute task"}]

    class _Client:
        def get_prompt(
            self, name: str, label: str | None = None, version: int | None = None
        ) -> _TaskOnlyPrompt:
            del name, label, version
            return _TaskOnlyPrompt()

    provider = LangfusePromptProvider(client=_Client())
    payload = provider.fetch_prompt(PromptFetchRequest(prompt_name="x"))

    assert payload.system_content == ""
    assert payload.task_content == "execute task"


def test_prompt_provider_rejects_non_chat_prompt_shape() -> None:
    class _Prompt:
        labels: ClassVar[list[str]] = ["prod"]
        version = 1

        def compile(self, **variables: object) -> dict[str, str]:
            del variables
            return {"role": "user", "content": "execute task"}

    class _Client:
        def get_prompt(
            self, name: str, label: str | None = None, version: int | None = None
        ) -> _Prompt:
            del name, label, version
            return _Prompt()

    provider = LangfusePromptProvider(client=_Client())
    with pytest.raises(LangfuseError) as exc_info:
        provider.fetch_prompt(PromptFetchRequest(prompt_name="x"))

    assert exc_info.value.error.code == ErrorCode.MALFORMED_REQUEST
    assert exc_info.value.error.details.get("reason") == "invalid_prompt_shape"


def test_prompt_provider_rejects_compile_without_kwargs_support() -> None:
    class _Prompt:
        labels: ClassVar[list[str]] = ["prod"]
        version = 2

        def compile(self) -> list[dict[str, str]]:
            return [{"role": "user", "content": "task from zero arg compile"}]

    class _Client:
        def get_prompt(
            self, name: str, label: str | None = None, version: int | None = None
        ) -> _Prompt:
            del name, label, version
            return _Prompt()

    provider = LangfusePromptProvider(client=_Client())
    with pytest.raises(LangfuseError) as exc_info:
        provider.fetch_prompt(
            PromptFetchRequest(prompt_name="x", variables={"topic": "ignored"})
        )

    assert exc_info.value.error.code == ErrorCode.MALFORMED_REQUEST
    assert exc_info.value.error.details.get("reason") == "unknown_compile_variables"
