import os
from typing import Literal
from unittest.mock import patch

import pytest
from openai.types.shared.reasoning import Reasoning

from agents import Agent
from agents.model_settings import ModelSettings
from agents.models import (
    get_default_model,
    get_default_model_settings,
    gpt_5_reasoning_settings_required,
    is_gpt_5_default,
)


def _gpt_5_default_settings(
    reasoning_effort: Literal["none", "low", "medium"] | None,
) -> ModelSettings:
    if reasoning_effort is None:
        return ModelSettings(verbosity="low")
    return ModelSettings(reasoning=Reasoning(effort=reasoning_effort), verbosity="low")


def test_default_model_is_gpt_5_4_mini():
    assert get_default_model() == "gpt-5.4-mini"
    assert is_gpt_5_default() is True
    assert gpt_5_reasoning_settings_required(get_default_model()) is True
    assert get_default_model_settings() == _gpt_5_default_settings("none")


@patch.dict(os.environ, {"OPENAI_DEFAULT_MODEL": "gpt-5.4"})
def test_is_gpt_5_default_with_real_model_name():
    assert get_default_model() == "gpt-5.4"
    assert is_gpt_5_default() is True


@patch.dict(os.environ, {"OPENAI_DEFAULT_MODEL": "gpt-4.1"})
def test_is_gpt_5_default_returns_false_for_non_gpt_5_default_model():
    assert get_default_model() == "gpt-4.1"
    assert is_gpt_5_default() is False


def test_gpt_5_reasoning_settings_required_detects_gpt_5_models_while_ignoring_chat_latest():
    assert gpt_5_reasoning_settings_required("gpt-5") is True
    assert gpt_5_reasoning_settings_required("gpt-5.1") is True
    assert gpt_5_reasoning_settings_required("gpt-5.2") is True
    assert gpt_5_reasoning_settings_required("gpt-5.2-codex") is True
    assert gpt_5_reasoning_settings_required("gpt-5.2-pro") is True
    assert gpt_5_reasoning_settings_required("gpt-5.4-pro") is True
    assert gpt_5_reasoning_settings_required("gpt-5.5") is True
    assert gpt_5_reasoning_settings_required("gpt-5-mini") is True
    assert gpt_5_reasoning_settings_required("gpt-5-nano") is True
    assert gpt_5_reasoning_settings_required("gpt-5-chat-latest") is False
    assert gpt_5_reasoning_settings_required("gpt-5.1-chat-latest") is False
    assert gpt_5_reasoning_settings_required("gpt-5.2-chat-latest") is False
    assert gpt_5_reasoning_settings_required("gpt-5.3-chat-latest") is False


def test_gpt_5_reasoning_settings_required_returns_false_for_non_gpt_5_models():
    assert gpt_5_reasoning_settings_required("gpt-4.1") is False


def test_get_default_model_settings_returns_none_reasoning_defaults_for_gpt_5_1_models():
    assert get_default_model_settings("gpt-5.1") == _gpt_5_default_settings("none")
    assert get_default_model_settings("gpt-5.1-2025-11-13") == _gpt_5_default_settings("none")


def test_get_default_model_settings_returns_none_reasoning_defaults_for_gpt_5_2_models():
    assert get_default_model_settings("gpt-5.2") == _gpt_5_default_settings("none")
    assert get_default_model_settings("gpt-5.2-2025-12-11") == _gpt_5_default_settings("none")


def test_get_default_model_settings_returns_none_reasoning_defaults_for_gpt_5_3_codex_models():
    assert get_default_model_settings("gpt-5.3-codex") == _gpt_5_default_settings("none")


def test_get_default_model_settings_returns_none_reasoning_defaults_for_gpt_5_4_models():
    assert get_default_model_settings("gpt-5.4") == _gpt_5_default_settings("none")


def test_get_default_model_settings_returns_none_reasoning_defaults_for_gpt_5_4_snapshot_families():
    assert get_default_model_settings("gpt-5.4-2026-03-05") == _gpt_5_default_settings("none")
    assert get_default_model_settings("gpt-5.4-mini-2026-03-17") == _gpt_5_default_settings("none")
    assert get_default_model_settings("gpt-5.4-nano-2026-03-17") == _gpt_5_default_settings("none")


def test_get_default_model_settings_returns_none_reasoning_defaults_for_gpt_5_4_mini_and_nano():
    assert get_default_model_settings("gpt-5.4-mini") == _gpt_5_default_settings("none")
    assert get_default_model_settings("gpt-5.4-nano") == _gpt_5_default_settings("none")


def test_get_default_model_settings_returns_none_reasoning_defaults_for_gpt_5_5_models():
    assert get_default_model_settings("gpt-5.5") == _gpt_5_default_settings("none")
    assert get_default_model_settings("gpt-5.5-2026-04-23") == _gpt_5_default_settings("none")


@pytest.mark.parametrize("model", ["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"])
def test_get_default_model_settings_returns_none_reasoning_defaults_for_gpt_5_6_models(
    model: str,
):
    assert get_default_model_settings(model) == _gpt_5_default_settings("none")


@pytest.mark.parametrize("model", ["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"])
def test_agent_uses_gpt_5_6_model_settings_from_default_model_env(
    model: str, monkeypatch: pytest.MonkeyPatch
):
    monkeypatch.setenv("OPENAI_DEFAULT_MODEL", model.upper())

    agent = Agent(name="test")

    assert get_default_model() == model
    assert agent.model is None
    assert agent.model_settings == _gpt_5_default_settings("none")


def test_get_default_model_settings_returns_low_reasoning_defaults_for_base_gpt_5():
    assert get_default_model_settings("gpt-5") == _gpt_5_default_settings("low")
    assert get_default_model_settings("gpt-5-2025-08-07") == _gpt_5_default_settings("low")


def test_get_default_model_settings_returns_low_reasoning_defaults_for_gpt_5_2_codex():
    assert get_default_model_settings("gpt-5.2-codex") == _gpt_5_default_settings("low")


def test_get_default_model_settings_returns_medium_reasoning_defaults_for_gpt_5_pro_models():
    assert get_default_model_settings("gpt-5.2-pro") == _gpt_5_default_settings("medium")
    assert get_default_model_settings("gpt-5.2-pro-2025-12-11") == _gpt_5_default_settings("medium")
    assert get_default_model_settings("gpt-5.4-pro") == _gpt_5_default_settings("medium")
    assert get_default_model_settings("gpt-5.4-pro-2026-03-05") == _gpt_5_default_settings("medium")


def test_get_default_model_settings_omits_reasoning_for_unconfirmed_gpt_5_variants():
    assert get_default_model_settings("gpt-5-mini") == _gpt_5_default_settings(None)
    assert get_default_model_settings("gpt-5-mini-2025-08-07") == _gpt_5_default_settings(None)
    assert get_default_model_settings("gpt-5-nano") == _gpt_5_default_settings(None)
    assert get_default_model_settings("gpt-5-nano-2025-08-07") == _gpt_5_default_settings(None)
    assert get_default_model_settings("gpt-5.1-codex") == _gpt_5_default_settings(None)


def test_get_default_model_settings_returns_empty_settings_for_gpt_5_chat_latest_aliases():
    assert get_default_model_settings("gpt-5-chat-latest") == ModelSettings()
    assert get_default_model_settings("gpt-5.1-chat-latest") == ModelSettings()
    assert get_default_model_settings("gpt-5.2-chat-latest") == ModelSettings()
    assert get_default_model_settings("gpt-5.3-chat-latest") == ModelSettings()


def test_get_default_model_settings_returns_empty_settings_for_non_gpt_5_models():
    assert get_default_model_settings("gpt-4.1") == ModelSettings()


@patch.dict(os.environ, {"OPENAI_DEFAULT_MODEL": "gpt-5"})
def test_agent_uses_gpt_5_default_model_settings():
    """Agent should inherit GPT-5 default model settings."""
    agent = Agent(name="test")
    assert agent.model is None
    assert agent.model_settings.reasoning.effort == "low"  # type: ignore[union-attr]
    assert agent.model_settings.verbosity == "low"


def test_agent_uses_model_specific_settings_for_explicit_gpt_5_models():
    """Agent should not apply the fallback model's GPT-5 settings to explicit GPT-5 models."""
    agent = Agent(name="test", model="gpt-5")
    assert agent.model == "gpt-5"
    assert agent.model_settings == get_default_model_settings("gpt-5")
    assert agent.model_settings.reasoning.effort == "low"  # type: ignore[union-attr]


def test_agent_uses_empty_settings_for_explicit_non_gpt_5_models():
    """Agent should not apply GPT-5 defaults to explicit non-GPT-5 models."""
    agent = Agent(name="test", model="gpt-4.1")
    assert agent.model == "gpt-4.1"
    assert agent.model_settings == ModelSettings()


def test_agent_clone_recomputes_implicit_settings_when_model_changes():
    """Agent.clone should keep implicit model settings aligned with the cloned model."""
    agent = Agent(name="test", model="gpt-5")
    cloned = agent.clone(model="gpt-5.4-mini")
    assert cloned.model == "gpt-5.4-mini"
    assert cloned.model_settings == get_default_model_settings("gpt-5.4-mini")
    assert cloned.model_settings.reasoning.effort == "none"  # type: ignore[union-attr]


def test_agent_clone_preserves_explicit_settings_when_model_changes():
    """Agent.clone should not recompute model settings that were explicitly customized."""
    model_settings = ModelSettings(temperature=0.3)
    agent = Agent(name="test", model="gpt-5", model_settings=model_settings)
    cloned = agent.clone(model="gpt-5.4-mini")
    assert cloned.model == "gpt-5.4-mini"
    assert cloned.model_settings == model_settings


@patch.dict(os.environ, {"OPENAI_DEFAULT_MODEL": "gpt-5"})
def test_agent_resets_model_settings_for_non_gpt_5_models():
    """Agent should reset default GPT-5 settings when using a non-GPT-5 model."""
    agent = Agent(name="test", model="gpt-4.1")
    assert agent.model == "gpt-4.1"
    assert agent.model_settings == ModelSettings()
