// Code generated by cmd/genapi. DO NOT EDIT.

//go:build !ignore_autogenerated

package api

import (
	"bytes"
	"context"
	"errors"
	"io"
	"net/http"
	"strings"
	"testing"

	"github.com/lukaszraczylo/go-telegram/client"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
)

// genTestMockDoer is a testify-mock HTTPDoer used by generated tests only.
type genTestMockDoer struct{ mock.Mock }

func (m *genTestMockDoer) Do(r *http.Request) (*http.Response, error) {
	args := m.Called(r)
	if v := args.Get(0); v != nil {
		return v.(*http.Response), args.Error(1)
	}
	return nil, args.Error(1)
}

func genTestResp(status int, body string) *http.Response {
	return &http.Response{
		StatusCode: status,
		Body:       io.NopCloser(bytes.NewBufferString(body)),
		Header:     http.Header{"Content-Type": []string{"application/json"}},
	}
}

func Test_GetUpdates_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getUpdates")
	})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUpdatesParams{}
	_, err := GetUpdates(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetUpdates_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUpdatesParams{}
	_, err := GetUpdates(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetUpdates_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUpdatesParams{}
	_, err := GetUpdates(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetUpdates_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUpdatesParams{}
	_, err := GetUpdates(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetUpdates_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUpdatesParams{}
	_, err := GetUpdates(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetUpdates_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetUpdates_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetUpdates(context.Background(), bot, &GetUpdatesParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetUpdates_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetUpdates_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUpdatesParams{}
	_, err := GetUpdates(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetUpdates_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetUpdates_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUpdatesParams{}
	_, err := GetUpdates(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetWebhook_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setWebhook")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetWebhookParams{
		URL: "test_value",
	}
	_, err := SetWebhook(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetWebhook_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetWebhookParams{
		URL: "test_value",
	}
	_, err := SetWebhook(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetWebhook_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetWebhookParams{
		URL: "test_value",
	}
	_, err := SetWebhook(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetWebhook_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetWebhookParams{
		URL: "test_value",
	}
	_, err := SetWebhook(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetWebhook_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetWebhookParams{
		URL: "test_value",
	}
	_, err := SetWebhook(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetWebhook_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetWebhook_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetWebhook(context.Background(), bot, &SetWebhookParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetWebhook_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetWebhook_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetWebhookParams{
		URL: "test_value",
	}
	_, err := SetWebhook(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetWebhook_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetWebhook_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetWebhookParams{
		URL: "test_value",
	}
	_, err := SetWebhook(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_DeleteWebhook_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/deleteWebhook")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteWebhookParams{}
	_, err := DeleteWebhook(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_DeleteWebhook_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteWebhookParams{}
	_, err := DeleteWebhook(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_DeleteWebhook_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteWebhookParams{}
	_, err := DeleteWebhook(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_DeleteWebhook_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteWebhookParams{}
	_, err := DeleteWebhook(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_DeleteWebhook_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteWebhookParams{}
	_, err := DeleteWebhook(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_DeleteWebhook_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteWebhook_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := DeleteWebhook(context.Background(), bot, &DeleteWebhookParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteWebhook_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteWebhook_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteWebhookParams{}
	_, err := DeleteWebhook(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteWebhook_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteWebhook_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteWebhookParams{}
	_, err := DeleteWebhook(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetWebhookInfo_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getWebhookInfo")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetWebhookInfo(context.Background(), bot, &GetWebhookInfoParams{})
	require.NoError(t, err)
}

func Test_GetWebhookInfo_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetWebhookInfo(context.Background(), bot, &GetWebhookInfoParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetWebhookInfo_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetWebhookInfo(context.Background(), bot, &GetWebhookInfoParams{})
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetWebhookInfo_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetWebhookInfo(context.Background(), bot, &GetWebhookInfoParams{})
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetWebhookInfo_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetWebhookInfo(ctx, bot, &GetWebhookInfoParams{})
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetWebhookInfo_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetWebhookInfo_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetWebhookInfo(context.Background(), bot, &GetWebhookInfoParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetWebhookInfo_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetWebhookInfo_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetWebhookInfo(context.Background(), bot, &GetWebhookInfoParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetWebhookInfo_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetWebhookInfo_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetWebhookInfo(context.Background(), bot, &GetWebhookInfoParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetMe_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getMe")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetMe(context.Background(), bot, &GetMeParams{})
	require.NoError(t, err)
}

func Test_GetMe_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetMe(context.Background(), bot, &GetMeParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetMe_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetMe(context.Background(), bot, &GetMeParams{})
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetMe_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetMe(context.Background(), bot, &GetMeParams{})
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetMe_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetMe(ctx, bot, &GetMeParams{})
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetMe_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetMe_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetMe(context.Background(), bot, &GetMeParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetMe_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetMe_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetMe(context.Background(), bot, &GetMeParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetMe_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetMe_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetMe(context.Background(), bot, &GetMeParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_LogOut_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/logOut")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := LogOut(context.Background(), bot, &LogOutParams{})
	require.NoError(t, err)
}

func Test_LogOut_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := LogOut(context.Background(), bot, &LogOutParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_LogOut_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := LogOut(context.Background(), bot, &LogOutParams{})
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_LogOut_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := LogOut(context.Background(), bot, &LogOutParams{})
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_LogOut_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := LogOut(ctx, bot, &LogOutParams{})
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_LogOut_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_LogOut_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := LogOut(context.Background(), bot, &LogOutParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_LogOut_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_LogOut_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := LogOut(context.Background(), bot, &LogOutParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_LogOut_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_LogOut_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := LogOut(context.Background(), bot, &LogOutParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_Close_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/close")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := Close(context.Background(), bot, &CloseParams{})
	require.NoError(t, err)
}

func Test_Close_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := Close(context.Background(), bot, &CloseParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_Close_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := Close(context.Background(), bot, &CloseParams{})
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_Close_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := Close(context.Background(), bot, &CloseParams{})
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_Close_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := Close(ctx, bot, &CloseParams{})
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_Close_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_Close_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := Close(context.Background(), bot, &CloseParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_Close_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_Close_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := Close(context.Background(), bot, &CloseParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_Close_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_Close_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := Close(context.Background(), bot, &CloseParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendMessage_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendMessage")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMessageParams{
		ChatID: ChatIDFromInt(123),
		Text:   "test_value",
	}
	_, err := SendMessage(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendMessage_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMessageParams{
		ChatID: ChatIDFromInt(123),
		Text:   "test_value",
	}
	_, err := SendMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendMessage_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMessageParams{
		ChatID: ChatIDFromInt(123),
		Text:   "test_value",
	}
	_, err := SendMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendMessage_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMessageParams{
		ChatID: ChatIDFromInt(123),
		Text:   "test_value",
	}
	_, err := SendMessage(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendMessage_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMessageParams{
		ChatID: ChatIDFromInt(123),
		Text:   "test_value",
	}
	_, err := SendMessage(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendMessage_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendMessage(context.Background(), bot, &SendMessageParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendMessage_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMessageParams{
		ChatID: ChatIDFromInt(123),
		Text:   "test_value",
	}
	_, err := SendMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendMessage_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMessageParams{
		ChatID: ChatIDFromInt(123),
		Text:   "test_value",
	}
	_, err := SendMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_ForwardMessage_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/forwardMessage")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ForwardMessageParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageID:  42,
	}
	_, err := ForwardMessage(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_ForwardMessage_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ForwardMessageParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageID:  42,
	}
	_, err := ForwardMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_ForwardMessage_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ForwardMessageParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageID:  42,
	}
	_, err := ForwardMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_ForwardMessage_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ForwardMessageParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageID:  42,
	}
	_, err := ForwardMessage(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_ForwardMessage_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ForwardMessageParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageID:  42,
	}
	_, err := ForwardMessage(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_ForwardMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ForwardMessage_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := ForwardMessage(context.Background(), bot, &ForwardMessageParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_ForwardMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ForwardMessage_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ForwardMessageParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageID:  42,
	}
	_, err := ForwardMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_ForwardMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ForwardMessage_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ForwardMessageParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageID:  42,
	}
	_, err := ForwardMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_ForwardMessages_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/forwardMessages")
	})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ForwardMessagesParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := ForwardMessages(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_ForwardMessages_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ForwardMessagesParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := ForwardMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_ForwardMessages_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ForwardMessagesParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := ForwardMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_ForwardMessages_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ForwardMessagesParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := ForwardMessages(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_ForwardMessages_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ForwardMessagesParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := ForwardMessages(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_ForwardMessages_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ForwardMessages_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := ForwardMessages(context.Background(), bot, &ForwardMessagesParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_ForwardMessages_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ForwardMessages_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ForwardMessagesParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := ForwardMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_ForwardMessages_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ForwardMessages_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ForwardMessagesParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := ForwardMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_CopyMessage_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/copyMessage")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CopyMessageParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageID:  42,
	}
	_, err := CopyMessage(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_CopyMessage_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CopyMessageParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageID:  42,
	}
	_, err := CopyMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_CopyMessage_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CopyMessageParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageID:  42,
	}
	_, err := CopyMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_CopyMessage_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CopyMessageParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageID:  42,
	}
	_, err := CopyMessage(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_CopyMessage_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CopyMessageParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageID:  42,
	}
	_, err := CopyMessage(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_CopyMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CopyMessage_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := CopyMessage(context.Background(), bot, &CopyMessageParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_CopyMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CopyMessage_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CopyMessageParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageID:  42,
	}
	_, err := CopyMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_CopyMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CopyMessage_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CopyMessageParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageID:  42,
	}
	_, err := CopyMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_CopyMessages_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/copyMessages")
	})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CopyMessagesParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := CopyMessages(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_CopyMessages_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CopyMessagesParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := CopyMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_CopyMessages_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CopyMessagesParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := CopyMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_CopyMessages_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CopyMessagesParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := CopyMessages(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_CopyMessages_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CopyMessagesParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := CopyMessages(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_CopyMessages_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CopyMessages_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := CopyMessages(context.Background(), bot, &CopyMessagesParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_CopyMessages_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CopyMessages_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CopyMessagesParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := CopyMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_CopyMessages_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CopyMessages_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CopyMessagesParams{
		ChatID:     ChatIDFromInt(123),
		FromChatID: ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := CopyMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendPhoto_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendPhoto")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPhotoParams{
		ChatID: ChatIDFromInt(123),
		Photo:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendPhoto(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendPhoto_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPhotoParams{
		ChatID: ChatIDFromInt(123),
		Photo:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendPhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendPhoto_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPhotoParams{
		ChatID: ChatIDFromInt(123),
		Photo:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendPhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendPhoto_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPhotoParams{
		ChatID: ChatIDFromInt(123),
		Photo:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendPhoto(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendPhoto_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPhotoParams{
		ChatID: ChatIDFromInt(123),
		Photo:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendPhoto(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendPhoto_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendPhoto_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendPhoto(context.Background(), bot, &SendPhotoParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendPhoto_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendPhoto_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPhotoParams{
		ChatID: ChatIDFromInt(123),
		Photo:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendPhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendPhoto_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendPhoto_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPhotoParams{
		ChatID: ChatIDFromInt(123),
		Photo:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendPhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendLivePhoto_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendLivePhoto")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendLivePhotoParams{
		ChatID:    ChatIDFromInt(123),
		LivePhoto: &InputFile{PathOrID: "file_id_test"},
		Photo:     &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendLivePhoto(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendLivePhoto_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendLivePhotoParams{
		ChatID:    ChatIDFromInt(123),
		LivePhoto: &InputFile{PathOrID: "file_id_test"},
		Photo:     &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendLivePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendLivePhoto_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendLivePhotoParams{
		ChatID:    ChatIDFromInt(123),
		LivePhoto: &InputFile{PathOrID: "file_id_test"},
		Photo:     &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendLivePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendLivePhoto_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendLivePhotoParams{
		ChatID:    ChatIDFromInt(123),
		LivePhoto: &InputFile{PathOrID: "file_id_test"},
		Photo:     &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendLivePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendLivePhoto_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendLivePhotoParams{
		ChatID:    ChatIDFromInt(123),
		LivePhoto: &InputFile{PathOrID: "file_id_test"},
		Photo:     &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendLivePhoto(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendLivePhoto_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendLivePhoto_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendLivePhoto(context.Background(), bot, &SendLivePhotoParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendLivePhoto_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendLivePhoto_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendLivePhotoParams{
		ChatID:    ChatIDFromInt(123),
		LivePhoto: &InputFile{PathOrID: "file_id_test"},
		Photo:     &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendLivePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendLivePhoto_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendLivePhoto_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendLivePhotoParams{
		ChatID:    ChatIDFromInt(123),
		LivePhoto: &InputFile{PathOrID: "file_id_test"},
		Photo:     &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendLivePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendAudio_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendAudio")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendAudioParams{
		ChatID: ChatIDFromInt(123),
		Audio:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendAudio(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendAudio_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendAudioParams{
		ChatID: ChatIDFromInt(123),
		Audio:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendAudio(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendAudio_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendAudioParams{
		ChatID: ChatIDFromInt(123),
		Audio:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendAudio(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendAudio_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendAudioParams{
		ChatID: ChatIDFromInt(123),
		Audio:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendAudio(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendAudio_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendAudioParams{
		ChatID: ChatIDFromInt(123),
		Audio:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendAudio(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendAudio_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendAudio_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendAudio(context.Background(), bot, &SendAudioParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendAudio_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendAudio_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendAudioParams{
		ChatID: ChatIDFromInt(123),
		Audio:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendAudio(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendAudio_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendAudio_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendAudioParams{
		ChatID: ChatIDFromInt(123),
		Audio:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendAudio(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendDocument_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendDocument")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendDocumentParams{
		ChatID:   ChatIDFromInt(123),
		Document: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendDocument(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendDocument_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendDocumentParams{
		ChatID:   ChatIDFromInt(123),
		Document: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendDocument(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendDocument_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendDocumentParams{
		ChatID:   ChatIDFromInt(123),
		Document: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendDocument(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendDocument_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendDocumentParams{
		ChatID:   ChatIDFromInt(123),
		Document: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendDocument(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendDocument_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendDocumentParams{
		ChatID:   ChatIDFromInt(123),
		Document: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendDocument(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendDocument_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendDocument_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendDocument(context.Background(), bot, &SendDocumentParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendDocument_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendDocument_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendDocumentParams{
		ChatID:   ChatIDFromInt(123),
		Document: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendDocument(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendDocument_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendDocument_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendDocumentParams{
		ChatID:   ChatIDFromInt(123),
		Document: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendDocument(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendVideo_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendVideo")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVideoParams{
		ChatID: ChatIDFromInt(123),
		Video:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVideo(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendVideo_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVideoParams{
		ChatID: ChatIDFromInt(123),
		Video:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVideo(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendVideo_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVideoParams{
		ChatID: ChatIDFromInt(123),
		Video:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVideo(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendVideo_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVideoParams{
		ChatID: ChatIDFromInt(123),
		Video:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVideo(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendVideo_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVideoParams{
		ChatID: ChatIDFromInt(123),
		Video:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVideo(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendVideo_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendVideo_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendVideo(context.Background(), bot, &SendVideoParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendVideo_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendVideo_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVideoParams{
		ChatID: ChatIDFromInt(123),
		Video:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVideo(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendVideo_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendVideo_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVideoParams{
		ChatID: ChatIDFromInt(123),
		Video:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVideo(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendAnimation_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendAnimation")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendAnimationParams{
		ChatID:    ChatIDFromInt(123),
		Animation: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendAnimation(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendAnimation_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendAnimationParams{
		ChatID:    ChatIDFromInt(123),
		Animation: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendAnimation(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendAnimation_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendAnimationParams{
		ChatID:    ChatIDFromInt(123),
		Animation: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendAnimation(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendAnimation_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendAnimationParams{
		ChatID:    ChatIDFromInt(123),
		Animation: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendAnimation(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendAnimation_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendAnimationParams{
		ChatID:    ChatIDFromInt(123),
		Animation: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendAnimation(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendAnimation_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendAnimation_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendAnimation(context.Background(), bot, &SendAnimationParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendAnimation_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendAnimation_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendAnimationParams{
		ChatID:    ChatIDFromInt(123),
		Animation: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendAnimation(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendAnimation_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendAnimation_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendAnimationParams{
		ChatID:    ChatIDFromInt(123),
		Animation: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendAnimation(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendVoice_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendVoice")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVoiceParams{
		ChatID: ChatIDFromInt(123),
		Voice:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVoice(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendVoice_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVoiceParams{
		ChatID: ChatIDFromInt(123),
		Voice:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVoice(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendVoice_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVoiceParams{
		ChatID: ChatIDFromInt(123),
		Voice:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVoice(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendVoice_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVoiceParams{
		ChatID: ChatIDFromInt(123),
		Voice:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVoice(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendVoice_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVoiceParams{
		ChatID: ChatIDFromInt(123),
		Voice:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVoice(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendVoice_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendVoice_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendVoice(context.Background(), bot, &SendVoiceParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendVoice_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendVoice_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVoiceParams{
		ChatID: ChatIDFromInt(123),
		Voice:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVoice(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendVoice_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendVoice_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVoiceParams{
		ChatID: ChatIDFromInt(123),
		Voice:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVoice(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendVideoNote_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendVideoNote")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVideoNoteParams{
		ChatID:    ChatIDFromInt(123),
		VideoNote: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVideoNote(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendVideoNote_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVideoNoteParams{
		ChatID:    ChatIDFromInt(123),
		VideoNote: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVideoNote(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendVideoNote_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVideoNoteParams{
		ChatID:    ChatIDFromInt(123),
		VideoNote: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVideoNote(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendVideoNote_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVideoNoteParams{
		ChatID:    ChatIDFromInt(123),
		VideoNote: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVideoNote(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendVideoNote_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVideoNoteParams{
		ChatID:    ChatIDFromInt(123),
		VideoNote: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVideoNote(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendVideoNote_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendVideoNote_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendVideoNote(context.Background(), bot, &SendVideoNoteParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendVideoNote_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendVideoNote_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVideoNoteParams{
		ChatID:    ChatIDFromInt(123),
		VideoNote: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVideoNote(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendVideoNote_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendVideoNote_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVideoNoteParams{
		ChatID:    ChatIDFromInt(123),
		VideoNote: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendVideoNote(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendPaidMedia_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendPaidMedia")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPaidMediaParams{
		ChatID:    ChatIDFromInt(123),
		StarCount: 42,
		Media:     nil,
	}
	_, err := SendPaidMedia(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendPaidMedia_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPaidMediaParams{
		ChatID:    ChatIDFromInt(123),
		StarCount: 42,
		Media:     nil,
	}
	_, err := SendPaidMedia(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendPaidMedia_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPaidMediaParams{
		ChatID:    ChatIDFromInt(123),
		StarCount: 42,
		Media:     nil,
	}
	_, err := SendPaidMedia(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendPaidMedia_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPaidMediaParams{
		ChatID:    ChatIDFromInt(123),
		StarCount: 42,
		Media:     nil,
	}
	_, err := SendPaidMedia(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendPaidMedia_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPaidMediaParams{
		ChatID:    ChatIDFromInt(123),
		StarCount: 42,
		Media:     nil,
	}
	_, err := SendPaidMedia(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendPaidMedia_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendPaidMedia_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendPaidMedia(context.Background(), bot, &SendPaidMediaParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendPaidMedia_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendPaidMedia_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPaidMediaParams{
		ChatID:    ChatIDFromInt(123),
		StarCount: 42,
		Media:     nil,
	}
	_, err := SendPaidMedia(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendPaidMedia_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendPaidMedia_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPaidMediaParams{
		ChatID:    ChatIDFromInt(123),
		StarCount: 42,
		Media:     nil,
	}
	_, err := SendPaidMedia(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendMediaGroup_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendMediaGroup")
	})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMediaGroupParams{
		ChatID: ChatIDFromInt(123),
		Media:  nil,
	}
	_, err := SendMediaGroup(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendMediaGroup_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMediaGroupParams{
		ChatID: ChatIDFromInt(123),
		Media:  nil,
	}
	_, err := SendMediaGroup(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendMediaGroup_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMediaGroupParams{
		ChatID: ChatIDFromInt(123),
		Media:  nil,
	}
	_, err := SendMediaGroup(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendMediaGroup_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMediaGroupParams{
		ChatID: ChatIDFromInt(123),
		Media:  nil,
	}
	_, err := SendMediaGroup(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendMediaGroup_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMediaGroupParams{
		ChatID: ChatIDFromInt(123),
		Media:  nil,
	}
	_, err := SendMediaGroup(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendMediaGroup_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendMediaGroup_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendMediaGroup(context.Background(), bot, &SendMediaGroupParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendMediaGroup_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendMediaGroup_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMediaGroupParams{
		ChatID: ChatIDFromInt(123),
		Media:  nil,
	}
	_, err := SendMediaGroup(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendMediaGroup_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendMediaGroup_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMediaGroupParams{
		ChatID: ChatIDFromInt(123),
		Media:  nil,
	}
	_, err := SendMediaGroup(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendLocation_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendLocation")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendLocationParams{
		ChatID:    ChatIDFromInt(123),
		Latitude:  1.0,
		Longitude: 1.0,
	}
	_, err := SendLocation(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendLocation_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendLocationParams{
		ChatID:    ChatIDFromInt(123),
		Latitude:  1.0,
		Longitude: 1.0,
	}
	_, err := SendLocation(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendLocation_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendLocationParams{
		ChatID:    ChatIDFromInt(123),
		Latitude:  1.0,
		Longitude: 1.0,
	}
	_, err := SendLocation(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendLocation_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendLocationParams{
		ChatID:    ChatIDFromInt(123),
		Latitude:  1.0,
		Longitude: 1.0,
	}
	_, err := SendLocation(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendLocation_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendLocationParams{
		ChatID:    ChatIDFromInt(123),
		Latitude:  1.0,
		Longitude: 1.0,
	}
	_, err := SendLocation(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendLocation_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendLocation_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendLocation(context.Background(), bot, &SendLocationParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendLocation_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendLocation_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendLocationParams{
		ChatID:    ChatIDFromInt(123),
		Latitude:  1.0,
		Longitude: 1.0,
	}
	_, err := SendLocation(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendLocation_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendLocation_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendLocationParams{
		ChatID:    ChatIDFromInt(123),
		Latitude:  1.0,
		Longitude: 1.0,
	}
	_, err := SendLocation(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendVenue_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendVenue")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVenueParams{
		ChatID:    ChatIDFromInt(123),
		Latitude:  1.0,
		Longitude: 1.0,
		Title:     "test_value",
		Address:   "test_value",
	}
	_, err := SendVenue(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendVenue_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVenueParams{
		ChatID:    ChatIDFromInt(123),
		Latitude:  1.0,
		Longitude: 1.0,
		Title:     "test_value",
		Address:   "test_value",
	}
	_, err := SendVenue(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendVenue_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVenueParams{
		ChatID:    ChatIDFromInt(123),
		Latitude:  1.0,
		Longitude: 1.0,
		Title:     "test_value",
		Address:   "test_value",
	}
	_, err := SendVenue(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendVenue_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVenueParams{
		ChatID:    ChatIDFromInt(123),
		Latitude:  1.0,
		Longitude: 1.0,
		Title:     "test_value",
		Address:   "test_value",
	}
	_, err := SendVenue(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendVenue_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVenueParams{
		ChatID:    ChatIDFromInt(123),
		Latitude:  1.0,
		Longitude: 1.0,
		Title:     "test_value",
		Address:   "test_value",
	}
	_, err := SendVenue(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendVenue_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendVenue_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendVenue(context.Background(), bot, &SendVenueParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendVenue_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendVenue_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVenueParams{
		ChatID:    ChatIDFromInt(123),
		Latitude:  1.0,
		Longitude: 1.0,
		Title:     "test_value",
		Address:   "test_value",
	}
	_, err := SendVenue(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendVenue_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendVenue_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendVenueParams{
		ChatID:    ChatIDFromInt(123),
		Latitude:  1.0,
		Longitude: 1.0,
		Title:     "test_value",
		Address:   "test_value",
	}
	_, err := SendVenue(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendContact_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendContact")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendContactParams{
		ChatID:      ChatIDFromInt(123),
		PhoneNumber: "test_value",
		FirstName:   "test_value",
	}
	_, err := SendContact(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendContact_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendContactParams{
		ChatID:      ChatIDFromInt(123),
		PhoneNumber: "test_value",
		FirstName:   "test_value",
	}
	_, err := SendContact(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendContact_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendContactParams{
		ChatID:      ChatIDFromInt(123),
		PhoneNumber: "test_value",
		FirstName:   "test_value",
	}
	_, err := SendContact(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendContact_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendContactParams{
		ChatID:      ChatIDFromInt(123),
		PhoneNumber: "test_value",
		FirstName:   "test_value",
	}
	_, err := SendContact(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendContact_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendContactParams{
		ChatID:      ChatIDFromInt(123),
		PhoneNumber: "test_value",
		FirstName:   "test_value",
	}
	_, err := SendContact(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendContact_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendContact_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendContact(context.Background(), bot, &SendContactParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendContact_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendContact_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendContactParams{
		ChatID:      ChatIDFromInt(123),
		PhoneNumber: "test_value",
		FirstName:   "test_value",
	}
	_, err := SendContact(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendContact_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendContact_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendContactParams{
		ChatID:      ChatIDFromInt(123),
		PhoneNumber: "test_value",
		FirstName:   "test_value",
	}
	_, err := SendContact(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendPoll_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendPoll")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPollParams{
		ChatID:   ChatIDFromInt(123),
		Question: "test_value",
		Options:  nil,
	}
	_, err := SendPoll(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendPoll_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPollParams{
		ChatID:   ChatIDFromInt(123),
		Question: "test_value",
		Options:  nil,
	}
	_, err := SendPoll(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendPoll_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPollParams{
		ChatID:   ChatIDFromInt(123),
		Question: "test_value",
		Options:  nil,
	}
	_, err := SendPoll(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendPoll_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPollParams{
		ChatID:   ChatIDFromInt(123),
		Question: "test_value",
		Options:  nil,
	}
	_, err := SendPoll(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendPoll_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPollParams{
		ChatID:   ChatIDFromInt(123),
		Question: "test_value",
		Options:  nil,
	}
	_, err := SendPoll(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendPoll_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendPoll_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendPoll(context.Background(), bot, &SendPollParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendPoll_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendPoll_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPollParams{
		ChatID:   ChatIDFromInt(123),
		Question: "test_value",
		Options:  nil,
	}
	_, err := SendPoll(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendPoll_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendPoll_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendPollParams{
		ChatID:   ChatIDFromInt(123),
		Question: "test_value",
		Options:  nil,
	}
	_, err := SendPoll(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendChecklist_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendChecklist")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChecklistParams{
		BusinessConnectionID: "test_value",
		ChatID:               ChatIDFromInt(123),
		Checklist:            InputChecklist{},
	}
	_, err := SendChecklist(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendChecklist_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChecklistParams{
		BusinessConnectionID: "test_value",
		ChatID:               ChatIDFromInt(123),
		Checklist:            InputChecklist{},
	}
	_, err := SendChecklist(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendChecklist_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChecklistParams{
		BusinessConnectionID: "test_value",
		ChatID:               ChatIDFromInt(123),
		Checklist:            InputChecklist{},
	}
	_, err := SendChecklist(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendChecklist_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChecklistParams{
		BusinessConnectionID: "test_value",
		ChatID:               ChatIDFromInt(123),
		Checklist:            InputChecklist{},
	}
	_, err := SendChecklist(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendChecklist_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChecklistParams{
		BusinessConnectionID: "test_value",
		ChatID:               ChatIDFromInt(123),
		Checklist:            InputChecklist{},
	}
	_, err := SendChecklist(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendChecklist_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendChecklist_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendChecklist(context.Background(), bot, &SendChecklistParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendChecklist_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendChecklist_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChecklistParams{
		BusinessConnectionID: "test_value",
		ChatID:               ChatIDFromInt(123),
		Checklist:            InputChecklist{},
	}
	_, err := SendChecklist(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendChecklist_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendChecklist_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChecklistParams{
		BusinessConnectionID: "test_value",
		ChatID:               ChatIDFromInt(123),
		Checklist:            InputChecklist{},
	}
	_, err := SendChecklist(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendDice_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendDice")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendDiceParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := SendDice(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendDice_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendDiceParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := SendDice(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendDice_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendDiceParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := SendDice(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendDice_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendDiceParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := SendDice(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendDice_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendDiceParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := SendDice(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendDice_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendDice_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendDice(context.Background(), bot, &SendDiceParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendDice_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendDice_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendDiceParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := SendDice(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendDice_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendDice_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendDiceParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := SendDice(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendMessageDraft_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendMessageDraft")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMessageDraftParams{
		ChatID:  42,
		DraftID: 42,
	}
	_, err := SendMessageDraft(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendMessageDraft_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMessageDraftParams{
		ChatID:  42,
		DraftID: 42,
	}
	_, err := SendMessageDraft(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendMessageDraft_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMessageDraftParams{
		ChatID:  42,
		DraftID: 42,
	}
	_, err := SendMessageDraft(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendMessageDraft_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMessageDraftParams{
		ChatID:  42,
		DraftID: 42,
	}
	_, err := SendMessageDraft(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendMessageDraft_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMessageDraftParams{
		ChatID:  42,
		DraftID: 42,
	}
	_, err := SendMessageDraft(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendMessageDraft_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendMessageDraft_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendMessageDraft(context.Background(), bot, &SendMessageDraftParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendMessageDraft_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendMessageDraft_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMessageDraftParams{
		ChatID:  42,
		DraftID: 42,
	}
	_, err := SendMessageDraft(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendMessageDraft_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendMessageDraft_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendMessageDraftParams{
		ChatID:  42,
		DraftID: 42,
	}
	_, err := SendMessageDraft(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendChatAction_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendChatAction")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChatActionParams{
		ChatID: ChatIDFromInt(123),
		Action: "test_value",
	}
	_, err := SendChatAction(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendChatAction_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChatActionParams{
		ChatID: ChatIDFromInt(123),
		Action: "test_value",
	}
	_, err := SendChatAction(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendChatAction_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChatActionParams{
		ChatID: ChatIDFromInt(123),
		Action: "test_value",
	}
	_, err := SendChatAction(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendChatAction_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChatActionParams{
		ChatID: ChatIDFromInt(123),
		Action: "test_value",
	}
	_, err := SendChatAction(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendChatAction_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChatActionParams{
		ChatID: ChatIDFromInt(123),
		Action: "test_value",
	}
	_, err := SendChatAction(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendChatAction_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendChatAction_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendChatAction(context.Background(), bot, &SendChatActionParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendChatAction_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendChatAction_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChatActionParams{
		ChatID: ChatIDFromInt(123),
		Action: "test_value",
	}
	_, err := SendChatAction(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendChatAction_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendChatAction_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChatActionParams{
		ChatID: ChatIDFromInt(123),
		Action: "test_value",
	}
	_, err := SendChatAction(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetMessageReaction_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setMessageReaction")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMessageReactionParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := SetMessageReaction(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetMessageReaction_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMessageReactionParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := SetMessageReaction(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetMessageReaction_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMessageReactionParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := SetMessageReaction(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetMessageReaction_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMessageReactionParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := SetMessageReaction(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetMessageReaction_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMessageReactionParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := SetMessageReaction(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetMessageReaction_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetMessageReaction_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetMessageReaction(context.Background(), bot, &SetMessageReactionParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetMessageReaction_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetMessageReaction_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMessageReactionParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := SetMessageReaction(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetMessageReaction_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetMessageReaction_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMessageReactionParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := SetMessageReaction(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetUserProfilePhotos_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getUserProfilePhotos")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserProfilePhotosParams{
		UserID: 42,
	}
	_, err := GetUserProfilePhotos(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetUserProfilePhotos_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserProfilePhotosParams{
		UserID: 42,
	}
	_, err := GetUserProfilePhotos(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetUserProfilePhotos_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserProfilePhotosParams{
		UserID: 42,
	}
	_, err := GetUserProfilePhotos(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetUserProfilePhotos_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserProfilePhotosParams{
		UserID: 42,
	}
	_, err := GetUserProfilePhotos(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetUserProfilePhotos_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserProfilePhotosParams{
		UserID: 42,
	}
	_, err := GetUserProfilePhotos(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetUserProfilePhotos_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetUserProfilePhotos_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetUserProfilePhotos(context.Background(), bot, &GetUserProfilePhotosParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetUserProfilePhotos_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetUserProfilePhotos_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserProfilePhotosParams{
		UserID: 42,
	}
	_, err := GetUserProfilePhotos(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetUserProfilePhotos_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetUserProfilePhotos_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserProfilePhotosParams{
		UserID: 42,
	}
	_, err := GetUserProfilePhotos(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetUserProfileAudios_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getUserProfileAudios")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserProfileAudiosParams{
		UserID: 42,
	}
	_, err := GetUserProfileAudios(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetUserProfileAudios_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserProfileAudiosParams{
		UserID: 42,
	}
	_, err := GetUserProfileAudios(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetUserProfileAudios_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserProfileAudiosParams{
		UserID: 42,
	}
	_, err := GetUserProfileAudios(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetUserProfileAudios_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserProfileAudiosParams{
		UserID: 42,
	}
	_, err := GetUserProfileAudios(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetUserProfileAudios_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserProfileAudiosParams{
		UserID: 42,
	}
	_, err := GetUserProfileAudios(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetUserProfileAudios_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetUserProfileAudios_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetUserProfileAudios(context.Background(), bot, &GetUserProfileAudiosParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetUserProfileAudios_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetUserProfileAudios_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserProfileAudiosParams{
		UserID: 42,
	}
	_, err := GetUserProfileAudios(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetUserProfileAudios_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetUserProfileAudios_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserProfileAudiosParams{
		UserID: 42,
	}
	_, err := GetUserProfileAudios(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetUserEmojiStatus_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setUserEmojiStatus")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetUserEmojiStatusParams{
		UserID: 42,
	}
	_, err := SetUserEmojiStatus(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetUserEmojiStatus_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetUserEmojiStatusParams{
		UserID: 42,
	}
	_, err := SetUserEmojiStatus(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetUserEmojiStatus_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetUserEmojiStatusParams{
		UserID: 42,
	}
	_, err := SetUserEmojiStatus(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetUserEmojiStatus_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetUserEmojiStatusParams{
		UserID: 42,
	}
	_, err := SetUserEmojiStatus(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetUserEmojiStatus_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetUserEmojiStatusParams{
		UserID: 42,
	}
	_, err := SetUserEmojiStatus(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetUserEmojiStatus_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetUserEmojiStatus_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetUserEmojiStatus(context.Background(), bot, &SetUserEmojiStatusParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetUserEmojiStatus_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetUserEmojiStatus_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetUserEmojiStatusParams{
		UserID: 42,
	}
	_, err := SetUserEmojiStatus(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetUserEmojiStatus_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetUserEmojiStatus_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetUserEmojiStatusParams{
		UserID: 42,
	}
	_, err := SetUserEmojiStatus(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetFile_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getFile")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetFileParams{
		FileID: "test_value",
	}
	_, err := GetFile(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetFile_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetFileParams{
		FileID: "test_value",
	}
	_, err := GetFile(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetFile_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetFileParams{
		FileID: "test_value",
	}
	_, err := GetFile(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetFile_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetFileParams{
		FileID: "test_value",
	}
	_, err := GetFile(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetFile_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetFileParams{
		FileID: "test_value",
	}
	_, err := GetFile(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetFile_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetFile_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetFile(context.Background(), bot, &GetFileParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetFile_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetFile_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetFileParams{
		FileID: "test_value",
	}
	_, err := GetFile(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetFile_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetFile_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetFileParams{
		FileID: "test_value",
	}
	_, err := GetFile(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_BanChatMember_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/banChatMember")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &BanChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := BanChatMember(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_BanChatMember_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &BanChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := BanChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_BanChatMember_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &BanChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := BanChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_BanChatMember_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &BanChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := BanChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_BanChatMember_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &BanChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := BanChatMember(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_BanChatMember_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_BanChatMember_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := BanChatMember(context.Background(), bot, &BanChatMemberParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_BanChatMember_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_BanChatMember_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &BanChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := BanChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_BanChatMember_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_BanChatMember_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &BanChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := BanChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_UnbanChatMember_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/unbanChatMember")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnbanChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := UnbanChatMember(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_UnbanChatMember_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnbanChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := UnbanChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_UnbanChatMember_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnbanChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := UnbanChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_UnbanChatMember_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnbanChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := UnbanChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_UnbanChatMember_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnbanChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := UnbanChatMember(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_UnbanChatMember_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UnbanChatMember_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := UnbanChatMember(context.Background(), bot, &UnbanChatMemberParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_UnbanChatMember_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UnbanChatMember_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnbanChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := UnbanChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_UnbanChatMember_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UnbanChatMember_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnbanChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := UnbanChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_RestrictChatMember_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/restrictChatMember")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RestrictChatMemberParams{
		ChatID:      ChatIDFromInt(123),
		UserID:      42,
		Permissions: ChatPermissions{},
	}
	_, err := RestrictChatMember(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_RestrictChatMember_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RestrictChatMemberParams{
		ChatID:      ChatIDFromInt(123),
		UserID:      42,
		Permissions: ChatPermissions{},
	}
	_, err := RestrictChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_RestrictChatMember_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RestrictChatMemberParams{
		ChatID:      ChatIDFromInt(123),
		UserID:      42,
		Permissions: ChatPermissions{},
	}
	_, err := RestrictChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_RestrictChatMember_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RestrictChatMemberParams{
		ChatID:      ChatIDFromInt(123),
		UserID:      42,
		Permissions: ChatPermissions{},
	}
	_, err := RestrictChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_RestrictChatMember_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RestrictChatMemberParams{
		ChatID:      ChatIDFromInt(123),
		UserID:      42,
		Permissions: ChatPermissions{},
	}
	_, err := RestrictChatMember(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_RestrictChatMember_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_RestrictChatMember_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := RestrictChatMember(context.Background(), bot, &RestrictChatMemberParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_RestrictChatMember_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_RestrictChatMember_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RestrictChatMemberParams{
		ChatID:      ChatIDFromInt(123),
		UserID:      42,
		Permissions: ChatPermissions{},
	}
	_, err := RestrictChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_RestrictChatMember_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_RestrictChatMember_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RestrictChatMemberParams{
		ChatID:      ChatIDFromInt(123),
		UserID:      42,
		Permissions: ChatPermissions{},
	}
	_, err := RestrictChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_PromoteChatMember_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/promoteChatMember")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PromoteChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := PromoteChatMember(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_PromoteChatMember_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PromoteChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := PromoteChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_PromoteChatMember_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PromoteChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := PromoteChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_PromoteChatMember_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PromoteChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := PromoteChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_PromoteChatMember_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PromoteChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := PromoteChatMember(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_PromoteChatMember_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_PromoteChatMember_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := PromoteChatMember(context.Background(), bot, &PromoteChatMemberParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_PromoteChatMember_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_PromoteChatMember_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PromoteChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := PromoteChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_PromoteChatMember_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_PromoteChatMember_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PromoteChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := PromoteChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetChatAdministratorCustomTitle_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setChatAdministratorCustomTitle")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatAdministratorCustomTitleParams{
		ChatID:      ChatIDFromInt(123),
		UserID:      42,
		CustomTitle: "test_value",
	}
	_, err := SetChatAdministratorCustomTitle(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetChatAdministratorCustomTitle_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatAdministratorCustomTitleParams{
		ChatID:      ChatIDFromInt(123),
		UserID:      42,
		CustomTitle: "test_value",
	}
	_, err := SetChatAdministratorCustomTitle(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetChatAdministratorCustomTitle_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatAdministratorCustomTitleParams{
		ChatID:      ChatIDFromInt(123),
		UserID:      42,
		CustomTitle: "test_value",
	}
	_, err := SetChatAdministratorCustomTitle(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetChatAdministratorCustomTitle_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatAdministratorCustomTitleParams{
		ChatID:      ChatIDFromInt(123),
		UserID:      42,
		CustomTitle: "test_value",
	}
	_, err := SetChatAdministratorCustomTitle(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetChatAdministratorCustomTitle_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatAdministratorCustomTitleParams{
		ChatID:      ChatIDFromInt(123),
		UserID:      42,
		CustomTitle: "test_value",
	}
	_, err := SetChatAdministratorCustomTitle(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetChatAdministratorCustomTitle_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetChatAdministratorCustomTitle_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetChatAdministratorCustomTitle(context.Background(), bot, &SetChatAdministratorCustomTitleParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetChatAdministratorCustomTitle_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetChatAdministratorCustomTitle_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatAdministratorCustomTitleParams{
		ChatID:      ChatIDFromInt(123),
		UserID:      42,
		CustomTitle: "test_value",
	}
	_, err := SetChatAdministratorCustomTitle(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetChatAdministratorCustomTitle_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetChatAdministratorCustomTitle_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatAdministratorCustomTitleParams{
		ChatID:      ChatIDFromInt(123),
		UserID:      42,
		CustomTitle: "test_value",
	}
	_, err := SetChatAdministratorCustomTitle(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetChatMemberTag_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setChatMemberTag")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatMemberTagParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := SetChatMemberTag(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetChatMemberTag_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatMemberTagParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := SetChatMemberTag(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetChatMemberTag_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatMemberTagParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := SetChatMemberTag(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetChatMemberTag_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatMemberTagParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := SetChatMemberTag(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetChatMemberTag_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatMemberTagParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := SetChatMemberTag(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetChatMemberTag_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetChatMemberTag_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetChatMemberTag(context.Background(), bot, &SetChatMemberTagParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetChatMemberTag_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetChatMemberTag_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatMemberTagParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := SetChatMemberTag(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetChatMemberTag_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetChatMemberTag_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatMemberTagParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := SetChatMemberTag(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_BanChatSenderChat_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/banChatSenderChat")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &BanChatSenderChatParams{
		ChatID:       ChatIDFromInt(123),
		SenderChatID: 42,
	}
	_, err := BanChatSenderChat(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_BanChatSenderChat_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &BanChatSenderChatParams{
		ChatID:       ChatIDFromInt(123),
		SenderChatID: 42,
	}
	_, err := BanChatSenderChat(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_BanChatSenderChat_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &BanChatSenderChatParams{
		ChatID:       ChatIDFromInt(123),
		SenderChatID: 42,
	}
	_, err := BanChatSenderChat(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_BanChatSenderChat_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &BanChatSenderChatParams{
		ChatID:       ChatIDFromInt(123),
		SenderChatID: 42,
	}
	_, err := BanChatSenderChat(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_BanChatSenderChat_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &BanChatSenderChatParams{
		ChatID:       ChatIDFromInt(123),
		SenderChatID: 42,
	}
	_, err := BanChatSenderChat(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_BanChatSenderChat_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_BanChatSenderChat_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := BanChatSenderChat(context.Background(), bot, &BanChatSenderChatParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_BanChatSenderChat_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_BanChatSenderChat_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &BanChatSenderChatParams{
		ChatID:       ChatIDFromInt(123),
		SenderChatID: 42,
	}
	_, err := BanChatSenderChat(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_BanChatSenderChat_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_BanChatSenderChat_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &BanChatSenderChatParams{
		ChatID:       ChatIDFromInt(123),
		SenderChatID: 42,
	}
	_, err := BanChatSenderChat(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_UnbanChatSenderChat_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/unbanChatSenderChat")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnbanChatSenderChatParams{
		ChatID:       ChatIDFromInt(123),
		SenderChatID: 42,
	}
	_, err := UnbanChatSenderChat(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_UnbanChatSenderChat_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnbanChatSenderChatParams{
		ChatID:       ChatIDFromInt(123),
		SenderChatID: 42,
	}
	_, err := UnbanChatSenderChat(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_UnbanChatSenderChat_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnbanChatSenderChatParams{
		ChatID:       ChatIDFromInt(123),
		SenderChatID: 42,
	}
	_, err := UnbanChatSenderChat(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_UnbanChatSenderChat_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnbanChatSenderChatParams{
		ChatID:       ChatIDFromInt(123),
		SenderChatID: 42,
	}
	_, err := UnbanChatSenderChat(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_UnbanChatSenderChat_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnbanChatSenderChatParams{
		ChatID:       ChatIDFromInt(123),
		SenderChatID: 42,
	}
	_, err := UnbanChatSenderChat(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_UnbanChatSenderChat_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UnbanChatSenderChat_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := UnbanChatSenderChat(context.Background(), bot, &UnbanChatSenderChatParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_UnbanChatSenderChat_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UnbanChatSenderChat_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnbanChatSenderChatParams{
		ChatID:       ChatIDFromInt(123),
		SenderChatID: 42,
	}
	_, err := UnbanChatSenderChat(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_UnbanChatSenderChat_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UnbanChatSenderChat_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnbanChatSenderChatParams{
		ChatID:       ChatIDFromInt(123),
		SenderChatID: 42,
	}
	_, err := UnbanChatSenderChat(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetChatPermissions_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setChatPermissions")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatPermissionsParams{
		ChatID:      ChatIDFromInt(123),
		Permissions: ChatPermissions{},
	}
	_, err := SetChatPermissions(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetChatPermissions_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatPermissionsParams{
		ChatID:      ChatIDFromInt(123),
		Permissions: ChatPermissions{},
	}
	_, err := SetChatPermissions(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetChatPermissions_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatPermissionsParams{
		ChatID:      ChatIDFromInt(123),
		Permissions: ChatPermissions{},
	}
	_, err := SetChatPermissions(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetChatPermissions_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatPermissionsParams{
		ChatID:      ChatIDFromInt(123),
		Permissions: ChatPermissions{},
	}
	_, err := SetChatPermissions(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetChatPermissions_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatPermissionsParams{
		ChatID:      ChatIDFromInt(123),
		Permissions: ChatPermissions{},
	}
	_, err := SetChatPermissions(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetChatPermissions_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetChatPermissions_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetChatPermissions(context.Background(), bot, &SetChatPermissionsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetChatPermissions_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetChatPermissions_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatPermissionsParams{
		ChatID:      ChatIDFromInt(123),
		Permissions: ChatPermissions{},
	}
	_, err := SetChatPermissions(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetChatPermissions_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetChatPermissions_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatPermissionsParams{
		ChatID:      ChatIDFromInt(123),
		Permissions: ChatPermissions{},
	}
	_, err := SetChatPermissions(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_ExportChatInviteLink_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/exportChatInviteLink")
	})).Return(genTestResp(200, `{"ok":true,"result":""}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ExportChatInviteLinkParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := ExportChatInviteLink(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_ExportChatInviteLink_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ExportChatInviteLinkParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := ExportChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_ExportChatInviteLink_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ExportChatInviteLinkParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := ExportChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_ExportChatInviteLink_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ExportChatInviteLinkParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := ExportChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_ExportChatInviteLink_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ExportChatInviteLinkParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := ExportChatInviteLink(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_ExportChatInviteLink_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ExportChatInviteLink_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := ExportChatInviteLink(context.Background(), bot, &ExportChatInviteLinkParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_ExportChatInviteLink_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ExportChatInviteLink_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ExportChatInviteLinkParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := ExportChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_ExportChatInviteLink_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ExportChatInviteLink_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ExportChatInviteLinkParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := ExportChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_CreateChatInviteLink_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/createChatInviteLink")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateChatInviteLinkParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := CreateChatInviteLink(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_CreateChatInviteLink_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateChatInviteLinkParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := CreateChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_CreateChatInviteLink_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateChatInviteLinkParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := CreateChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_CreateChatInviteLink_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateChatInviteLinkParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := CreateChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_CreateChatInviteLink_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateChatInviteLinkParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := CreateChatInviteLink(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_CreateChatInviteLink_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CreateChatInviteLink_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := CreateChatInviteLink(context.Background(), bot, &CreateChatInviteLinkParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_CreateChatInviteLink_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CreateChatInviteLink_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateChatInviteLinkParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := CreateChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_CreateChatInviteLink_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CreateChatInviteLink_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateChatInviteLinkParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := CreateChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_EditChatInviteLink_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/editChatInviteLink")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditChatInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := EditChatInviteLink(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_EditChatInviteLink_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditChatInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := EditChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_EditChatInviteLink_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditChatInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := EditChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_EditChatInviteLink_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditChatInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := EditChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_EditChatInviteLink_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditChatInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := EditChatInviteLink(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_EditChatInviteLink_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditChatInviteLink_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := EditChatInviteLink(context.Background(), bot, &EditChatInviteLinkParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_EditChatInviteLink_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditChatInviteLink_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditChatInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := EditChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_EditChatInviteLink_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditChatInviteLink_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditChatInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := EditChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_CreateChatSubscriptionInviteLink_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/createChatSubscriptionInviteLink")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateChatSubscriptionInviteLinkParams{
		ChatID:             ChatIDFromInt(123),
		SubscriptionPeriod: 42,
		SubscriptionPrice:  42,
	}
	_, err := CreateChatSubscriptionInviteLink(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_CreateChatSubscriptionInviteLink_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateChatSubscriptionInviteLinkParams{
		ChatID:             ChatIDFromInt(123),
		SubscriptionPeriod: 42,
		SubscriptionPrice:  42,
	}
	_, err := CreateChatSubscriptionInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_CreateChatSubscriptionInviteLink_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateChatSubscriptionInviteLinkParams{
		ChatID:             ChatIDFromInt(123),
		SubscriptionPeriod: 42,
		SubscriptionPrice:  42,
	}
	_, err := CreateChatSubscriptionInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_CreateChatSubscriptionInviteLink_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateChatSubscriptionInviteLinkParams{
		ChatID:             ChatIDFromInt(123),
		SubscriptionPeriod: 42,
		SubscriptionPrice:  42,
	}
	_, err := CreateChatSubscriptionInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_CreateChatSubscriptionInviteLink_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateChatSubscriptionInviteLinkParams{
		ChatID:             ChatIDFromInt(123),
		SubscriptionPeriod: 42,
		SubscriptionPrice:  42,
	}
	_, err := CreateChatSubscriptionInviteLink(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_CreateChatSubscriptionInviteLink_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CreateChatSubscriptionInviteLink_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := CreateChatSubscriptionInviteLink(context.Background(), bot, &CreateChatSubscriptionInviteLinkParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_CreateChatSubscriptionInviteLink_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CreateChatSubscriptionInviteLink_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateChatSubscriptionInviteLinkParams{
		ChatID:             ChatIDFromInt(123),
		SubscriptionPeriod: 42,
		SubscriptionPrice:  42,
	}
	_, err := CreateChatSubscriptionInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_CreateChatSubscriptionInviteLink_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CreateChatSubscriptionInviteLink_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateChatSubscriptionInviteLinkParams{
		ChatID:             ChatIDFromInt(123),
		SubscriptionPeriod: 42,
		SubscriptionPrice:  42,
	}
	_, err := CreateChatSubscriptionInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_EditChatSubscriptionInviteLink_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/editChatSubscriptionInviteLink")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditChatSubscriptionInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := EditChatSubscriptionInviteLink(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_EditChatSubscriptionInviteLink_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditChatSubscriptionInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := EditChatSubscriptionInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_EditChatSubscriptionInviteLink_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditChatSubscriptionInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := EditChatSubscriptionInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_EditChatSubscriptionInviteLink_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditChatSubscriptionInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := EditChatSubscriptionInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_EditChatSubscriptionInviteLink_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditChatSubscriptionInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := EditChatSubscriptionInviteLink(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_EditChatSubscriptionInviteLink_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditChatSubscriptionInviteLink_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := EditChatSubscriptionInviteLink(context.Background(), bot, &EditChatSubscriptionInviteLinkParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_EditChatSubscriptionInviteLink_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditChatSubscriptionInviteLink_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditChatSubscriptionInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := EditChatSubscriptionInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_EditChatSubscriptionInviteLink_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditChatSubscriptionInviteLink_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditChatSubscriptionInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := EditChatSubscriptionInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_RevokeChatInviteLink_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/revokeChatInviteLink")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RevokeChatInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := RevokeChatInviteLink(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_RevokeChatInviteLink_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RevokeChatInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := RevokeChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_RevokeChatInviteLink_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RevokeChatInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := RevokeChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_RevokeChatInviteLink_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RevokeChatInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := RevokeChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_RevokeChatInviteLink_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RevokeChatInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := RevokeChatInviteLink(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_RevokeChatInviteLink_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_RevokeChatInviteLink_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := RevokeChatInviteLink(context.Background(), bot, &RevokeChatInviteLinkParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_RevokeChatInviteLink_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_RevokeChatInviteLink_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RevokeChatInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := RevokeChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_RevokeChatInviteLink_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_RevokeChatInviteLink_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RevokeChatInviteLinkParams{
		ChatID:     ChatIDFromInt(123),
		InviteLink: "test_value",
	}
	_, err := RevokeChatInviteLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_ApproveChatJoinRequest_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/approveChatJoinRequest")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ApproveChatJoinRequestParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := ApproveChatJoinRequest(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_ApproveChatJoinRequest_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ApproveChatJoinRequestParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := ApproveChatJoinRequest(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_ApproveChatJoinRequest_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ApproveChatJoinRequestParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := ApproveChatJoinRequest(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_ApproveChatJoinRequest_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ApproveChatJoinRequestParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := ApproveChatJoinRequest(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_ApproveChatJoinRequest_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ApproveChatJoinRequestParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := ApproveChatJoinRequest(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_ApproveChatJoinRequest_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ApproveChatJoinRequest_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := ApproveChatJoinRequest(context.Background(), bot, &ApproveChatJoinRequestParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_ApproveChatJoinRequest_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ApproveChatJoinRequest_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ApproveChatJoinRequestParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := ApproveChatJoinRequest(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_ApproveChatJoinRequest_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ApproveChatJoinRequest_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ApproveChatJoinRequestParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := ApproveChatJoinRequest(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_DeclineChatJoinRequest_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/declineChatJoinRequest")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeclineChatJoinRequestParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := DeclineChatJoinRequest(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_DeclineChatJoinRequest_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeclineChatJoinRequestParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := DeclineChatJoinRequest(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_DeclineChatJoinRequest_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeclineChatJoinRequestParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := DeclineChatJoinRequest(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_DeclineChatJoinRequest_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeclineChatJoinRequestParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := DeclineChatJoinRequest(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_DeclineChatJoinRequest_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeclineChatJoinRequestParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := DeclineChatJoinRequest(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_DeclineChatJoinRequest_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeclineChatJoinRequest_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := DeclineChatJoinRequest(context.Background(), bot, &DeclineChatJoinRequestParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_DeclineChatJoinRequest_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeclineChatJoinRequest_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeclineChatJoinRequestParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := DeclineChatJoinRequest(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_DeclineChatJoinRequest_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeclineChatJoinRequest_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeclineChatJoinRequestParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := DeclineChatJoinRequest(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_AnswerChatJoinRequestQuery_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/answerChatJoinRequestQuery")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerChatJoinRequestQueryParams{
		ChatJoinRequestQueryID: "test_value",
		Result:                 ResultApprove,
	}
	_, err := AnswerChatJoinRequestQuery(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_AnswerChatJoinRequestQuery_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerChatJoinRequestQueryParams{
		ChatJoinRequestQueryID: "test_value",
		Result:                 ResultApprove,
	}
	_, err := AnswerChatJoinRequestQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_AnswerChatJoinRequestQuery_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerChatJoinRequestQueryParams{
		ChatJoinRequestQueryID: "test_value",
		Result:                 ResultApprove,
	}
	_, err := AnswerChatJoinRequestQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_AnswerChatJoinRequestQuery_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerChatJoinRequestQueryParams{
		ChatJoinRequestQueryID: "test_value",
		Result:                 ResultApprove,
	}
	_, err := AnswerChatJoinRequestQuery(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_AnswerChatJoinRequestQuery_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerChatJoinRequestQueryParams{
		ChatJoinRequestQueryID: "test_value",
		Result:                 ResultApprove,
	}
	_, err := AnswerChatJoinRequestQuery(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_AnswerChatJoinRequestQuery_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_AnswerChatJoinRequestQuery_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := AnswerChatJoinRequestQuery(context.Background(), bot, &AnswerChatJoinRequestQueryParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_AnswerChatJoinRequestQuery_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_AnswerChatJoinRequestQuery_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerChatJoinRequestQueryParams{
		ChatJoinRequestQueryID: "test_value",
		Result:                 ResultApprove,
	}
	_, err := AnswerChatJoinRequestQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_AnswerChatJoinRequestQuery_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_AnswerChatJoinRequestQuery_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerChatJoinRequestQueryParams{
		ChatJoinRequestQueryID: "test_value",
		Result:                 ResultApprove,
	}
	_, err := AnswerChatJoinRequestQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendChatJoinRequestWebApp_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendChatJoinRequestWebApp")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChatJoinRequestWebAppParams{
		ChatJoinRequestQueryID: "test_value",
		WebAppURL:              "test_value",
	}
	_, err := SendChatJoinRequestWebApp(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendChatJoinRequestWebApp_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChatJoinRequestWebAppParams{
		ChatJoinRequestQueryID: "test_value",
		WebAppURL:              "test_value",
	}
	_, err := SendChatJoinRequestWebApp(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendChatJoinRequestWebApp_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChatJoinRequestWebAppParams{
		ChatJoinRequestQueryID: "test_value",
		WebAppURL:              "test_value",
	}
	_, err := SendChatJoinRequestWebApp(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendChatJoinRequestWebApp_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChatJoinRequestWebAppParams{
		ChatJoinRequestQueryID: "test_value",
		WebAppURL:              "test_value",
	}
	_, err := SendChatJoinRequestWebApp(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendChatJoinRequestWebApp_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChatJoinRequestWebAppParams{
		ChatJoinRequestQueryID: "test_value",
		WebAppURL:              "test_value",
	}
	_, err := SendChatJoinRequestWebApp(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendChatJoinRequestWebApp_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendChatJoinRequestWebApp_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendChatJoinRequestWebApp(context.Background(), bot, &SendChatJoinRequestWebAppParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendChatJoinRequestWebApp_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendChatJoinRequestWebApp_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChatJoinRequestWebAppParams{
		ChatJoinRequestQueryID: "test_value",
		WebAppURL:              "test_value",
	}
	_, err := SendChatJoinRequestWebApp(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendChatJoinRequestWebApp_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendChatJoinRequestWebApp_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendChatJoinRequestWebAppParams{
		ChatJoinRequestQueryID: "test_value",
		WebAppURL:              "test_value",
	}
	_, err := SendChatJoinRequestWebApp(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetChatPhoto_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setChatPhoto")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatPhotoParams{
		ChatID: ChatIDFromInt(123),
		Photo:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SetChatPhoto(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetChatPhoto_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatPhotoParams{
		ChatID: ChatIDFromInt(123),
		Photo:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SetChatPhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetChatPhoto_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatPhotoParams{
		ChatID: ChatIDFromInt(123),
		Photo:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SetChatPhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetChatPhoto_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatPhotoParams{
		ChatID: ChatIDFromInt(123),
		Photo:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SetChatPhoto(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetChatPhoto_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatPhotoParams{
		ChatID: ChatIDFromInt(123),
		Photo:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SetChatPhoto(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetChatPhoto_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetChatPhoto_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetChatPhoto(context.Background(), bot, &SetChatPhotoParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetChatPhoto_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetChatPhoto_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatPhotoParams{
		ChatID: ChatIDFromInt(123),
		Photo:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SetChatPhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetChatPhoto_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetChatPhoto_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatPhotoParams{
		ChatID: ChatIDFromInt(123),
		Photo:  &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SetChatPhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_DeleteChatPhoto_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/deleteChatPhoto")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteChatPhotoParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteChatPhoto(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_DeleteChatPhoto_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteChatPhotoParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteChatPhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_DeleteChatPhoto_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteChatPhotoParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteChatPhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_DeleteChatPhoto_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteChatPhotoParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteChatPhoto(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_DeleteChatPhoto_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteChatPhotoParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteChatPhoto(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_DeleteChatPhoto_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteChatPhoto_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := DeleteChatPhoto(context.Background(), bot, &DeleteChatPhotoParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteChatPhoto_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteChatPhoto_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteChatPhotoParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteChatPhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteChatPhoto_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteChatPhoto_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteChatPhotoParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteChatPhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetChatTitle_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setChatTitle")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatTitleParams{
		ChatID: ChatIDFromInt(123),
		Title:  "test_value",
	}
	_, err := SetChatTitle(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetChatTitle_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatTitleParams{
		ChatID: ChatIDFromInt(123),
		Title:  "test_value",
	}
	_, err := SetChatTitle(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetChatTitle_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatTitleParams{
		ChatID: ChatIDFromInt(123),
		Title:  "test_value",
	}
	_, err := SetChatTitle(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetChatTitle_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatTitleParams{
		ChatID: ChatIDFromInt(123),
		Title:  "test_value",
	}
	_, err := SetChatTitle(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetChatTitle_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatTitleParams{
		ChatID: ChatIDFromInt(123),
		Title:  "test_value",
	}
	_, err := SetChatTitle(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetChatTitle_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetChatTitle_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetChatTitle(context.Background(), bot, &SetChatTitleParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetChatTitle_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetChatTitle_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatTitleParams{
		ChatID: ChatIDFromInt(123),
		Title:  "test_value",
	}
	_, err := SetChatTitle(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetChatTitle_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetChatTitle_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatTitleParams{
		ChatID: ChatIDFromInt(123),
		Title:  "test_value",
	}
	_, err := SetChatTitle(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetChatDescription_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setChatDescription")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatDescriptionParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := SetChatDescription(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetChatDescription_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatDescriptionParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := SetChatDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetChatDescription_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatDescriptionParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := SetChatDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetChatDescription_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatDescriptionParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := SetChatDescription(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetChatDescription_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatDescriptionParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := SetChatDescription(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetChatDescription_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetChatDescription_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetChatDescription(context.Background(), bot, &SetChatDescriptionParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetChatDescription_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetChatDescription_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatDescriptionParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := SetChatDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetChatDescription_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetChatDescription_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatDescriptionParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := SetChatDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_PinChatMessage_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/pinChatMessage")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PinChatMessageParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := PinChatMessage(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_PinChatMessage_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PinChatMessageParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := PinChatMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_PinChatMessage_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PinChatMessageParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := PinChatMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_PinChatMessage_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PinChatMessageParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := PinChatMessage(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_PinChatMessage_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PinChatMessageParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := PinChatMessage(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_PinChatMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_PinChatMessage_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := PinChatMessage(context.Background(), bot, &PinChatMessageParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_PinChatMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_PinChatMessage_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PinChatMessageParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := PinChatMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_PinChatMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_PinChatMessage_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PinChatMessageParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := PinChatMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_UnpinChatMessage_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/unpinChatMessage")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinChatMessageParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinChatMessage(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_UnpinChatMessage_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinChatMessageParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinChatMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_UnpinChatMessage_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinChatMessageParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinChatMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_UnpinChatMessage_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinChatMessageParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinChatMessage(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_UnpinChatMessage_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinChatMessageParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinChatMessage(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_UnpinChatMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UnpinChatMessage_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := UnpinChatMessage(context.Background(), bot, &UnpinChatMessageParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_UnpinChatMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UnpinChatMessage_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinChatMessageParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinChatMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_UnpinChatMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UnpinChatMessage_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinChatMessageParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinChatMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_UnpinAllChatMessages_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/unpinAllChatMessages")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllChatMessagesParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinAllChatMessages(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_UnpinAllChatMessages_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllChatMessagesParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinAllChatMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_UnpinAllChatMessages_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllChatMessagesParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinAllChatMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_UnpinAllChatMessages_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllChatMessagesParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinAllChatMessages(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_UnpinAllChatMessages_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllChatMessagesParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinAllChatMessages(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_UnpinAllChatMessages_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UnpinAllChatMessages_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := UnpinAllChatMessages(context.Background(), bot, &UnpinAllChatMessagesParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_UnpinAllChatMessages_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UnpinAllChatMessages_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllChatMessagesParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinAllChatMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_UnpinAllChatMessages_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UnpinAllChatMessages_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllChatMessagesParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinAllChatMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_LeaveChat_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/leaveChat")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &LeaveChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := LeaveChat(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_LeaveChat_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &LeaveChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := LeaveChat(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_LeaveChat_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &LeaveChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := LeaveChat(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_LeaveChat_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &LeaveChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := LeaveChat(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_LeaveChat_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &LeaveChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := LeaveChat(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_LeaveChat_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_LeaveChat_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := LeaveChat(context.Background(), bot, &LeaveChatParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_LeaveChat_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_LeaveChat_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &LeaveChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := LeaveChat(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_LeaveChat_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_LeaveChat_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &LeaveChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := LeaveChat(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetChat_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getChat")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChat(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetChat_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChat(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetChat_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChat(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetChat_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChat(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetChat_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChat(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetChat_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetChat_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetChat(context.Background(), bot, &GetChatParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetChat_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetChat_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChat(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetChat_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetChat_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChat(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetChatAdministrators_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getChatAdministrators")
	})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatAdministratorsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatAdministrators(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetChatAdministrators_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatAdministratorsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatAdministrators(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetChatAdministrators_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatAdministratorsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatAdministrators(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetChatAdministrators_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatAdministratorsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatAdministrators(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetChatAdministrators_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatAdministratorsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatAdministrators(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetChatAdministrators_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetChatAdministrators_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetChatAdministrators(context.Background(), bot, &GetChatAdministratorsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetChatAdministrators_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetChatAdministrators_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatAdministratorsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatAdministrators(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetChatAdministrators_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetChatAdministrators_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatAdministratorsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatAdministrators(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetChatMemberCount_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getChatMemberCount")
	})).Return(genTestResp(200, `{"ok":true,"result":0}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMemberCountParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatMemberCount(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetChatMemberCount_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMemberCountParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatMemberCount(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetChatMemberCount_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMemberCountParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatMemberCount(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetChatMemberCount_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMemberCountParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatMemberCount(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetChatMemberCount_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMemberCountParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatMemberCount(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetChatMemberCount_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetChatMemberCount_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetChatMemberCount(context.Background(), bot, &GetChatMemberCountParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetChatMemberCount_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetChatMemberCount_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMemberCountParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatMemberCount(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetChatMemberCount_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetChatMemberCount_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMemberCountParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatMemberCount(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetChatMember_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getChatMember")
	})).Return(genTestResp(200, `{"ok":true,"result":{"status":"administrator"}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := GetChatMember(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetChatMember_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := GetChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetChatMember_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := GetChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetChatMember_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := GetChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetChatMember_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := GetChatMember(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetChatMember_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetChatMember_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetChatMember(context.Background(), bot, &GetChatMemberParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetChatMember_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetChatMember_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := GetChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetChatMember_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetChatMember_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMemberParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := GetChatMember(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetUserPersonalChatMessages_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getUserPersonalChatMessages")
	})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserPersonalChatMessagesParams{
		UserID: 42,
		Limit:  42,
	}
	_, err := GetUserPersonalChatMessages(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetUserPersonalChatMessages_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserPersonalChatMessagesParams{
		UserID: 42,
		Limit:  42,
	}
	_, err := GetUserPersonalChatMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetUserPersonalChatMessages_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserPersonalChatMessagesParams{
		UserID: 42,
		Limit:  42,
	}
	_, err := GetUserPersonalChatMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetUserPersonalChatMessages_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserPersonalChatMessagesParams{
		UserID: 42,
		Limit:  42,
	}
	_, err := GetUserPersonalChatMessages(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetUserPersonalChatMessages_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserPersonalChatMessagesParams{
		UserID: 42,
		Limit:  42,
	}
	_, err := GetUserPersonalChatMessages(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetUserPersonalChatMessages_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetUserPersonalChatMessages_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetUserPersonalChatMessages(context.Background(), bot, &GetUserPersonalChatMessagesParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetUserPersonalChatMessages_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetUserPersonalChatMessages_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserPersonalChatMessagesParams{
		UserID: 42,
		Limit:  42,
	}
	_, err := GetUserPersonalChatMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetUserPersonalChatMessages_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetUserPersonalChatMessages_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserPersonalChatMessagesParams{
		UserID: 42,
		Limit:  42,
	}
	_, err := GetUserPersonalChatMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetChatStickerSet_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setChatStickerSet")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatStickerSetParams{
		ChatID:         ChatIDFromInt(123),
		StickerSetName: "test_value",
	}
	_, err := SetChatStickerSet(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetChatStickerSet_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatStickerSetParams{
		ChatID:         ChatIDFromInt(123),
		StickerSetName: "test_value",
	}
	_, err := SetChatStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetChatStickerSet_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatStickerSetParams{
		ChatID:         ChatIDFromInt(123),
		StickerSetName: "test_value",
	}
	_, err := SetChatStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetChatStickerSet_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatStickerSetParams{
		ChatID:         ChatIDFromInt(123),
		StickerSetName: "test_value",
	}
	_, err := SetChatStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetChatStickerSet_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatStickerSetParams{
		ChatID:         ChatIDFromInt(123),
		StickerSetName: "test_value",
	}
	_, err := SetChatStickerSet(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetChatStickerSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetChatStickerSet_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetChatStickerSet(context.Background(), bot, &SetChatStickerSetParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetChatStickerSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetChatStickerSet_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatStickerSetParams{
		ChatID:         ChatIDFromInt(123),
		StickerSetName: "test_value",
	}
	_, err := SetChatStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetChatStickerSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetChatStickerSet_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatStickerSetParams{
		ChatID:         ChatIDFromInt(123),
		StickerSetName: "test_value",
	}
	_, err := SetChatStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_DeleteChatStickerSet_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/deleteChatStickerSet")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteChatStickerSetParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteChatStickerSet(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_DeleteChatStickerSet_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteChatStickerSetParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteChatStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_DeleteChatStickerSet_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteChatStickerSetParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteChatStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_DeleteChatStickerSet_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteChatStickerSetParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteChatStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_DeleteChatStickerSet_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteChatStickerSetParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteChatStickerSet(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_DeleteChatStickerSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteChatStickerSet_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := DeleteChatStickerSet(context.Background(), bot, &DeleteChatStickerSetParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteChatStickerSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteChatStickerSet_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteChatStickerSetParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteChatStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteChatStickerSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteChatStickerSet_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteChatStickerSetParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteChatStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetForumTopicIconStickers_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getForumTopicIconStickers")
	})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetForumTopicIconStickers(context.Background(), bot, &GetForumTopicIconStickersParams{})
	require.NoError(t, err)
}

func Test_GetForumTopicIconStickers_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetForumTopicIconStickers(context.Background(), bot, &GetForumTopicIconStickersParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetForumTopicIconStickers_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetForumTopicIconStickers(context.Background(), bot, &GetForumTopicIconStickersParams{})
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetForumTopicIconStickers_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetForumTopicIconStickers(context.Background(), bot, &GetForumTopicIconStickersParams{})
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetForumTopicIconStickers_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetForumTopicIconStickers(ctx, bot, &GetForumTopicIconStickersParams{})
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetForumTopicIconStickers_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetForumTopicIconStickers_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetForumTopicIconStickers(context.Background(), bot, &GetForumTopicIconStickersParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetForumTopicIconStickers_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetForumTopicIconStickers_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetForumTopicIconStickers(context.Background(), bot, &GetForumTopicIconStickersParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetForumTopicIconStickers_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetForumTopicIconStickers_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetForumTopicIconStickers(context.Background(), bot, &GetForumTopicIconStickersParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_CreateForumTopic_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/createForumTopic")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateForumTopicParams{
		ChatID: ChatIDFromInt(123),
		Name:   "test_value",
	}
	_, err := CreateForumTopic(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_CreateForumTopic_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateForumTopicParams{
		ChatID: ChatIDFromInt(123),
		Name:   "test_value",
	}
	_, err := CreateForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_CreateForumTopic_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateForumTopicParams{
		ChatID: ChatIDFromInt(123),
		Name:   "test_value",
	}
	_, err := CreateForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_CreateForumTopic_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateForumTopicParams{
		ChatID: ChatIDFromInt(123),
		Name:   "test_value",
	}
	_, err := CreateForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_CreateForumTopic_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateForumTopicParams{
		ChatID: ChatIDFromInt(123),
		Name:   "test_value",
	}
	_, err := CreateForumTopic(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_CreateForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CreateForumTopic_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := CreateForumTopic(context.Background(), bot, &CreateForumTopicParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_CreateForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CreateForumTopic_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateForumTopicParams{
		ChatID: ChatIDFromInt(123),
		Name:   "test_value",
	}
	_, err := CreateForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_CreateForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CreateForumTopic_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateForumTopicParams{
		ChatID: ChatIDFromInt(123),
		Name:   "test_value",
	}
	_, err := CreateForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_EditForumTopic_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/editForumTopic")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := EditForumTopic(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_EditForumTopic_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := EditForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_EditForumTopic_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := EditForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_EditForumTopic_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := EditForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_EditForumTopic_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := EditForumTopic(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_EditForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditForumTopic_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := EditForumTopic(context.Background(), bot, &EditForumTopicParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_EditForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditForumTopic_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := EditForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_EditForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditForumTopic_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := EditForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_CloseForumTopic_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/closeForumTopic")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CloseForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := CloseForumTopic(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_CloseForumTopic_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CloseForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := CloseForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_CloseForumTopic_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CloseForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := CloseForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_CloseForumTopic_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CloseForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := CloseForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_CloseForumTopic_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CloseForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := CloseForumTopic(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_CloseForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CloseForumTopic_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := CloseForumTopic(context.Background(), bot, &CloseForumTopicParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_CloseForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CloseForumTopic_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CloseForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := CloseForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_CloseForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CloseForumTopic_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CloseForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := CloseForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_ReopenForumTopic_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/reopenForumTopic")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReopenForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := ReopenForumTopic(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_ReopenForumTopic_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReopenForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := ReopenForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_ReopenForumTopic_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReopenForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := ReopenForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_ReopenForumTopic_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReopenForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := ReopenForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_ReopenForumTopic_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReopenForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := ReopenForumTopic(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_ReopenForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ReopenForumTopic_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := ReopenForumTopic(context.Background(), bot, &ReopenForumTopicParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_ReopenForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ReopenForumTopic_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReopenForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := ReopenForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_ReopenForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ReopenForumTopic_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReopenForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := ReopenForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_DeleteForumTopic_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/deleteForumTopic")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := DeleteForumTopic(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_DeleteForumTopic_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := DeleteForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_DeleteForumTopic_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := DeleteForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_DeleteForumTopic_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := DeleteForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_DeleteForumTopic_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := DeleteForumTopic(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_DeleteForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteForumTopic_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := DeleteForumTopic(context.Background(), bot, &DeleteForumTopicParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteForumTopic_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := DeleteForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteForumTopic_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteForumTopicParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := DeleteForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_UnpinAllForumTopicMessages_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/unpinAllForumTopicMessages")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllForumTopicMessagesParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := UnpinAllForumTopicMessages(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_UnpinAllForumTopicMessages_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllForumTopicMessagesParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := UnpinAllForumTopicMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_UnpinAllForumTopicMessages_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllForumTopicMessagesParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := UnpinAllForumTopicMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_UnpinAllForumTopicMessages_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllForumTopicMessagesParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := UnpinAllForumTopicMessages(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_UnpinAllForumTopicMessages_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllForumTopicMessagesParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := UnpinAllForumTopicMessages(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_UnpinAllForumTopicMessages_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UnpinAllForumTopicMessages_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := UnpinAllForumTopicMessages(context.Background(), bot, &UnpinAllForumTopicMessagesParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_UnpinAllForumTopicMessages_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UnpinAllForumTopicMessages_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllForumTopicMessagesParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := UnpinAllForumTopicMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_UnpinAllForumTopicMessages_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UnpinAllForumTopicMessages_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllForumTopicMessagesParams{
		ChatID:          ChatIDFromInt(123),
		MessageThreadID: 42,
	}
	_, err := UnpinAllForumTopicMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_EditGeneralForumTopic_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/editGeneralForumTopic")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
		Name:   "test_value",
	}
	_, err := EditGeneralForumTopic(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_EditGeneralForumTopic_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
		Name:   "test_value",
	}
	_, err := EditGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_EditGeneralForumTopic_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
		Name:   "test_value",
	}
	_, err := EditGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_EditGeneralForumTopic_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
		Name:   "test_value",
	}
	_, err := EditGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_EditGeneralForumTopic_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
		Name:   "test_value",
	}
	_, err := EditGeneralForumTopic(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_EditGeneralForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditGeneralForumTopic_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := EditGeneralForumTopic(context.Background(), bot, &EditGeneralForumTopicParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_EditGeneralForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditGeneralForumTopic_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
		Name:   "test_value",
	}
	_, err := EditGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_EditGeneralForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditGeneralForumTopic_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
		Name:   "test_value",
	}
	_, err := EditGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_CloseGeneralForumTopic_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/closeGeneralForumTopic")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CloseGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := CloseGeneralForumTopic(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_CloseGeneralForumTopic_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CloseGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := CloseGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_CloseGeneralForumTopic_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CloseGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := CloseGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_CloseGeneralForumTopic_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CloseGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := CloseGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_CloseGeneralForumTopic_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CloseGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := CloseGeneralForumTopic(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_CloseGeneralForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CloseGeneralForumTopic_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := CloseGeneralForumTopic(context.Background(), bot, &CloseGeneralForumTopicParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_CloseGeneralForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CloseGeneralForumTopic_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CloseGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := CloseGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_CloseGeneralForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CloseGeneralForumTopic_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CloseGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := CloseGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_ReopenGeneralForumTopic_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/reopenGeneralForumTopic")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReopenGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := ReopenGeneralForumTopic(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_ReopenGeneralForumTopic_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReopenGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := ReopenGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_ReopenGeneralForumTopic_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReopenGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := ReopenGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_ReopenGeneralForumTopic_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReopenGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := ReopenGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_ReopenGeneralForumTopic_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReopenGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := ReopenGeneralForumTopic(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_ReopenGeneralForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ReopenGeneralForumTopic_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := ReopenGeneralForumTopic(context.Background(), bot, &ReopenGeneralForumTopicParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_ReopenGeneralForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ReopenGeneralForumTopic_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReopenGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := ReopenGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_ReopenGeneralForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ReopenGeneralForumTopic_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReopenGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := ReopenGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_HideGeneralForumTopic_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/hideGeneralForumTopic")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &HideGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := HideGeneralForumTopic(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_HideGeneralForumTopic_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &HideGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := HideGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_HideGeneralForumTopic_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &HideGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := HideGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_HideGeneralForumTopic_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &HideGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := HideGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_HideGeneralForumTopic_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &HideGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := HideGeneralForumTopic(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_HideGeneralForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_HideGeneralForumTopic_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := HideGeneralForumTopic(context.Background(), bot, &HideGeneralForumTopicParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_HideGeneralForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_HideGeneralForumTopic_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &HideGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := HideGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_HideGeneralForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_HideGeneralForumTopic_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &HideGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := HideGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_UnhideGeneralForumTopic_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/unhideGeneralForumTopic")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnhideGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnhideGeneralForumTopic(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_UnhideGeneralForumTopic_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnhideGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnhideGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_UnhideGeneralForumTopic_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnhideGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnhideGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_UnhideGeneralForumTopic_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnhideGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnhideGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_UnhideGeneralForumTopic_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnhideGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnhideGeneralForumTopic(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_UnhideGeneralForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UnhideGeneralForumTopic_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := UnhideGeneralForumTopic(context.Background(), bot, &UnhideGeneralForumTopicParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_UnhideGeneralForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UnhideGeneralForumTopic_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnhideGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnhideGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_UnhideGeneralForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UnhideGeneralForumTopic_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnhideGeneralForumTopicParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnhideGeneralForumTopic(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_UnpinAllGeneralForumTopicMessages_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/unpinAllGeneralForumTopicMessages")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllGeneralForumTopicMessagesParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinAllGeneralForumTopicMessages(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_UnpinAllGeneralForumTopicMessages_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllGeneralForumTopicMessagesParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinAllGeneralForumTopicMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_UnpinAllGeneralForumTopicMessages_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllGeneralForumTopicMessagesParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinAllGeneralForumTopicMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_UnpinAllGeneralForumTopicMessages_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllGeneralForumTopicMessagesParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinAllGeneralForumTopicMessages(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_UnpinAllGeneralForumTopicMessages_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllGeneralForumTopicMessagesParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinAllGeneralForumTopicMessages(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_UnpinAllGeneralForumTopicMessages_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UnpinAllGeneralForumTopicMessages_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := UnpinAllGeneralForumTopicMessages(context.Background(), bot, &UnpinAllGeneralForumTopicMessagesParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_UnpinAllGeneralForumTopicMessages_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UnpinAllGeneralForumTopicMessages_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllGeneralForumTopicMessagesParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinAllGeneralForumTopicMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_UnpinAllGeneralForumTopicMessages_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UnpinAllGeneralForumTopicMessages_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UnpinAllGeneralForumTopicMessagesParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := UnpinAllGeneralForumTopicMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_AnswerCallbackQuery_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/answerCallbackQuery")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerCallbackQueryParams{
		CallbackQueryID: "test_value",
	}
	_, err := AnswerCallbackQuery(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_AnswerCallbackQuery_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerCallbackQueryParams{
		CallbackQueryID: "test_value",
	}
	_, err := AnswerCallbackQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_AnswerCallbackQuery_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerCallbackQueryParams{
		CallbackQueryID: "test_value",
	}
	_, err := AnswerCallbackQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_AnswerCallbackQuery_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerCallbackQueryParams{
		CallbackQueryID: "test_value",
	}
	_, err := AnswerCallbackQuery(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_AnswerCallbackQuery_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerCallbackQueryParams{
		CallbackQueryID: "test_value",
	}
	_, err := AnswerCallbackQuery(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_AnswerCallbackQuery_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_AnswerCallbackQuery_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := AnswerCallbackQuery(context.Background(), bot, &AnswerCallbackQueryParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_AnswerCallbackQuery_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_AnswerCallbackQuery_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerCallbackQueryParams{
		CallbackQueryID: "test_value",
	}
	_, err := AnswerCallbackQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_AnswerCallbackQuery_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_AnswerCallbackQuery_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerCallbackQueryParams{
		CallbackQueryID: "test_value",
	}
	_, err := AnswerCallbackQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_AnswerGuestQuery_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/answerGuestQuery")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerGuestQueryParams{
		GuestQueryID: "test_value",
		Result:       nil,
	}
	_, err := AnswerGuestQuery(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_AnswerGuestQuery_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerGuestQueryParams{
		GuestQueryID: "test_value",
		Result:       nil,
	}
	_, err := AnswerGuestQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_AnswerGuestQuery_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerGuestQueryParams{
		GuestQueryID: "test_value",
		Result:       nil,
	}
	_, err := AnswerGuestQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_AnswerGuestQuery_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerGuestQueryParams{
		GuestQueryID: "test_value",
		Result:       nil,
	}
	_, err := AnswerGuestQuery(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_AnswerGuestQuery_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerGuestQueryParams{
		GuestQueryID: "test_value",
		Result:       nil,
	}
	_, err := AnswerGuestQuery(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_AnswerGuestQuery_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_AnswerGuestQuery_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := AnswerGuestQuery(context.Background(), bot, &AnswerGuestQueryParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_AnswerGuestQuery_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_AnswerGuestQuery_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerGuestQueryParams{
		GuestQueryID: "test_value",
		Result:       nil,
	}
	_, err := AnswerGuestQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_AnswerGuestQuery_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_AnswerGuestQuery_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerGuestQueryParams{
		GuestQueryID: "test_value",
		Result:       nil,
	}
	_, err := AnswerGuestQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetUserChatBoosts_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getUserChatBoosts")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserChatBoostsParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := GetUserChatBoosts(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetUserChatBoosts_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserChatBoostsParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := GetUserChatBoosts(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetUserChatBoosts_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserChatBoostsParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := GetUserChatBoosts(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetUserChatBoosts_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserChatBoostsParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := GetUserChatBoosts(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetUserChatBoosts_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserChatBoostsParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := GetUserChatBoosts(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetUserChatBoosts_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetUserChatBoosts_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetUserChatBoosts(context.Background(), bot, &GetUserChatBoostsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetUserChatBoosts_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetUserChatBoosts_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserChatBoostsParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := GetUserChatBoosts(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetUserChatBoosts_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetUserChatBoosts_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserChatBoostsParams{
		ChatID: ChatIDFromInt(123),
		UserID: 42,
	}
	_, err := GetUserChatBoosts(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetBusinessConnection_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getBusinessConnection")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessConnectionParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessConnection(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetBusinessConnection_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessConnectionParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessConnection(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetBusinessConnection_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessConnectionParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessConnection(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetBusinessConnection_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessConnectionParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessConnection(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetBusinessConnection_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessConnectionParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessConnection(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetBusinessConnection_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetBusinessConnection_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetBusinessConnection(context.Background(), bot, &GetBusinessConnectionParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetBusinessConnection_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetBusinessConnection_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessConnectionParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessConnection(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetBusinessConnection_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetBusinessConnection_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessConnectionParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessConnection(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetManagedBotToken_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getManagedBotToken")
	})).Return(genTestResp(200, `{"ok":true,"result":""}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetManagedBotTokenParams{
		UserID: 42,
	}
	_, err := GetManagedBotToken(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetManagedBotToken_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetManagedBotTokenParams{
		UserID: 42,
	}
	_, err := GetManagedBotToken(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetManagedBotToken_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetManagedBotTokenParams{
		UserID: 42,
	}
	_, err := GetManagedBotToken(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetManagedBotToken_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetManagedBotTokenParams{
		UserID: 42,
	}
	_, err := GetManagedBotToken(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetManagedBotToken_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetManagedBotTokenParams{
		UserID: 42,
	}
	_, err := GetManagedBotToken(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetManagedBotToken_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetManagedBotToken_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetManagedBotToken(context.Background(), bot, &GetManagedBotTokenParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetManagedBotToken_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetManagedBotToken_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetManagedBotTokenParams{
		UserID: 42,
	}
	_, err := GetManagedBotToken(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetManagedBotToken_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetManagedBotToken_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetManagedBotTokenParams{
		UserID: 42,
	}
	_, err := GetManagedBotToken(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_ReplaceManagedBotToken_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/replaceManagedBotToken")
	})).Return(genTestResp(200, `{"ok":true,"result":""}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReplaceManagedBotTokenParams{
		UserID: 42,
	}
	_, err := ReplaceManagedBotToken(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_ReplaceManagedBotToken_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReplaceManagedBotTokenParams{
		UserID: 42,
	}
	_, err := ReplaceManagedBotToken(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_ReplaceManagedBotToken_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReplaceManagedBotTokenParams{
		UserID: 42,
	}
	_, err := ReplaceManagedBotToken(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_ReplaceManagedBotToken_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReplaceManagedBotTokenParams{
		UserID: 42,
	}
	_, err := ReplaceManagedBotToken(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_ReplaceManagedBotToken_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReplaceManagedBotTokenParams{
		UserID: 42,
	}
	_, err := ReplaceManagedBotToken(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_ReplaceManagedBotToken_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ReplaceManagedBotToken_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := ReplaceManagedBotToken(context.Background(), bot, &ReplaceManagedBotTokenParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_ReplaceManagedBotToken_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ReplaceManagedBotToken_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReplaceManagedBotTokenParams{
		UserID: 42,
	}
	_, err := ReplaceManagedBotToken(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_ReplaceManagedBotToken_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ReplaceManagedBotToken_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReplaceManagedBotTokenParams{
		UserID: 42,
	}
	_, err := ReplaceManagedBotToken(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetManagedBotAccessSettings_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getManagedBotAccessSettings")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetManagedBotAccessSettingsParams{
		UserID: 42,
	}
	_, err := GetManagedBotAccessSettings(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetManagedBotAccessSettings_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetManagedBotAccessSettingsParams{
		UserID: 42,
	}
	_, err := GetManagedBotAccessSettings(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetManagedBotAccessSettings_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetManagedBotAccessSettingsParams{
		UserID: 42,
	}
	_, err := GetManagedBotAccessSettings(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetManagedBotAccessSettings_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetManagedBotAccessSettingsParams{
		UserID: 42,
	}
	_, err := GetManagedBotAccessSettings(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetManagedBotAccessSettings_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetManagedBotAccessSettingsParams{
		UserID: 42,
	}
	_, err := GetManagedBotAccessSettings(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetManagedBotAccessSettings_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetManagedBotAccessSettings_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetManagedBotAccessSettings(context.Background(), bot, &GetManagedBotAccessSettingsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetManagedBotAccessSettings_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetManagedBotAccessSettings_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetManagedBotAccessSettingsParams{
		UserID: 42,
	}
	_, err := GetManagedBotAccessSettings(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetManagedBotAccessSettings_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetManagedBotAccessSettings_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetManagedBotAccessSettingsParams{
		UserID: 42,
	}
	_, err := GetManagedBotAccessSettings(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetManagedBotAccessSettings_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setManagedBotAccessSettings")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetManagedBotAccessSettingsParams{
		UserID:             42,
		IsAccessRestricted: true,
	}
	_, err := SetManagedBotAccessSettings(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetManagedBotAccessSettings_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetManagedBotAccessSettingsParams{
		UserID:             42,
		IsAccessRestricted: true,
	}
	_, err := SetManagedBotAccessSettings(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetManagedBotAccessSettings_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetManagedBotAccessSettingsParams{
		UserID:             42,
		IsAccessRestricted: true,
	}
	_, err := SetManagedBotAccessSettings(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetManagedBotAccessSettings_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetManagedBotAccessSettingsParams{
		UserID:             42,
		IsAccessRestricted: true,
	}
	_, err := SetManagedBotAccessSettings(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetManagedBotAccessSettings_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetManagedBotAccessSettingsParams{
		UserID:             42,
		IsAccessRestricted: true,
	}
	_, err := SetManagedBotAccessSettings(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetManagedBotAccessSettings_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetManagedBotAccessSettings_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetManagedBotAccessSettings(context.Background(), bot, &SetManagedBotAccessSettingsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetManagedBotAccessSettings_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetManagedBotAccessSettings_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetManagedBotAccessSettingsParams{
		UserID:             42,
		IsAccessRestricted: true,
	}
	_, err := SetManagedBotAccessSettings(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetManagedBotAccessSettings_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetManagedBotAccessSettings_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetManagedBotAccessSettingsParams{
		UserID:             42,
		IsAccessRestricted: true,
	}
	_, err := SetManagedBotAccessSettings(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetMyCommands_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setMyCommands")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyCommandsParams{
		Commands: nil,
	}
	_, err := SetMyCommands(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetMyCommands_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyCommandsParams{
		Commands: nil,
	}
	_, err := SetMyCommands(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetMyCommands_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyCommandsParams{
		Commands: nil,
	}
	_, err := SetMyCommands(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetMyCommands_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyCommandsParams{
		Commands: nil,
	}
	_, err := SetMyCommands(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetMyCommands_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyCommandsParams{
		Commands: nil,
	}
	_, err := SetMyCommands(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetMyCommands_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetMyCommands_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetMyCommands(context.Background(), bot, &SetMyCommandsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetMyCommands_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetMyCommands_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyCommandsParams{
		Commands: nil,
	}
	_, err := SetMyCommands(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetMyCommands_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetMyCommands_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyCommandsParams{
		Commands: nil,
	}
	_, err := SetMyCommands(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_DeleteMyCommands_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/deleteMyCommands")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMyCommandsParams{}
	_, err := DeleteMyCommands(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_DeleteMyCommands_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMyCommandsParams{}
	_, err := DeleteMyCommands(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_DeleteMyCommands_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMyCommandsParams{}
	_, err := DeleteMyCommands(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_DeleteMyCommands_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMyCommandsParams{}
	_, err := DeleteMyCommands(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_DeleteMyCommands_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMyCommandsParams{}
	_, err := DeleteMyCommands(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_DeleteMyCommands_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteMyCommands_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := DeleteMyCommands(context.Background(), bot, &DeleteMyCommandsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteMyCommands_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteMyCommands_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMyCommandsParams{}
	_, err := DeleteMyCommands(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteMyCommands_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteMyCommands_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMyCommandsParams{}
	_, err := DeleteMyCommands(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetMyCommands_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getMyCommands")
	})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyCommandsParams{}
	_, err := GetMyCommands(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetMyCommands_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyCommandsParams{}
	_, err := GetMyCommands(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetMyCommands_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyCommandsParams{}
	_, err := GetMyCommands(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetMyCommands_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyCommandsParams{}
	_, err := GetMyCommands(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetMyCommands_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyCommandsParams{}
	_, err := GetMyCommands(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetMyCommands_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetMyCommands_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetMyCommands(context.Background(), bot, &GetMyCommandsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetMyCommands_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetMyCommands_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyCommandsParams{}
	_, err := GetMyCommands(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetMyCommands_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetMyCommands_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyCommandsParams{}
	_, err := GetMyCommands(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetMyName_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setMyName")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyNameParams{}
	_, err := SetMyName(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetMyName_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyNameParams{}
	_, err := SetMyName(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetMyName_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyNameParams{}
	_, err := SetMyName(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetMyName_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyNameParams{}
	_, err := SetMyName(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetMyName_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyNameParams{}
	_, err := SetMyName(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetMyName_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetMyName_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetMyName(context.Background(), bot, &SetMyNameParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetMyName_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetMyName_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyNameParams{}
	_, err := SetMyName(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetMyName_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetMyName_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyNameParams{}
	_, err := SetMyName(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetMyName_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getMyName")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyNameParams{}
	_, err := GetMyName(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetMyName_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyNameParams{}
	_, err := GetMyName(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetMyName_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyNameParams{}
	_, err := GetMyName(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetMyName_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyNameParams{}
	_, err := GetMyName(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetMyName_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyNameParams{}
	_, err := GetMyName(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetMyName_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetMyName_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetMyName(context.Background(), bot, &GetMyNameParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetMyName_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetMyName_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyNameParams{}
	_, err := GetMyName(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetMyName_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetMyName_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyNameParams{}
	_, err := GetMyName(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetMyDescription_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setMyDescription")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyDescriptionParams{}
	_, err := SetMyDescription(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetMyDescription_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyDescriptionParams{}
	_, err := SetMyDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetMyDescription_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyDescriptionParams{}
	_, err := SetMyDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetMyDescription_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyDescriptionParams{}
	_, err := SetMyDescription(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetMyDescription_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyDescriptionParams{}
	_, err := SetMyDescription(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetMyDescription_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetMyDescription_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetMyDescription(context.Background(), bot, &SetMyDescriptionParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetMyDescription_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetMyDescription_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyDescriptionParams{}
	_, err := SetMyDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetMyDescription_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetMyDescription_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyDescriptionParams{}
	_, err := SetMyDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetMyDescription_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getMyDescription")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyDescriptionParams{}
	_, err := GetMyDescription(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetMyDescription_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyDescriptionParams{}
	_, err := GetMyDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetMyDescription_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyDescriptionParams{}
	_, err := GetMyDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetMyDescription_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyDescriptionParams{}
	_, err := GetMyDescription(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetMyDescription_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyDescriptionParams{}
	_, err := GetMyDescription(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetMyDescription_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetMyDescription_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetMyDescription(context.Background(), bot, &GetMyDescriptionParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetMyDescription_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetMyDescription_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyDescriptionParams{}
	_, err := GetMyDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetMyDescription_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetMyDescription_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyDescriptionParams{}
	_, err := GetMyDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetMyShortDescription_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setMyShortDescription")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyShortDescriptionParams{}
	_, err := SetMyShortDescription(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetMyShortDescription_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyShortDescriptionParams{}
	_, err := SetMyShortDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetMyShortDescription_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyShortDescriptionParams{}
	_, err := SetMyShortDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetMyShortDescription_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyShortDescriptionParams{}
	_, err := SetMyShortDescription(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetMyShortDescription_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyShortDescriptionParams{}
	_, err := SetMyShortDescription(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetMyShortDescription_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetMyShortDescription_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetMyShortDescription(context.Background(), bot, &SetMyShortDescriptionParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetMyShortDescription_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetMyShortDescription_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyShortDescriptionParams{}
	_, err := SetMyShortDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetMyShortDescription_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetMyShortDescription_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyShortDescriptionParams{}
	_, err := SetMyShortDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetMyShortDescription_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getMyShortDescription")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyShortDescriptionParams{}
	_, err := GetMyShortDescription(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetMyShortDescription_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyShortDescriptionParams{}
	_, err := GetMyShortDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetMyShortDescription_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyShortDescriptionParams{}
	_, err := GetMyShortDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetMyShortDescription_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyShortDescriptionParams{}
	_, err := GetMyShortDescription(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetMyShortDescription_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyShortDescriptionParams{}
	_, err := GetMyShortDescription(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetMyShortDescription_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetMyShortDescription_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetMyShortDescription(context.Background(), bot, &GetMyShortDescriptionParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetMyShortDescription_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetMyShortDescription_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyShortDescriptionParams{}
	_, err := GetMyShortDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetMyShortDescription_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetMyShortDescription_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyShortDescriptionParams{}
	_, err := GetMyShortDescription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetMyProfilePhoto_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setMyProfilePhoto")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyProfilePhotoParams{
		Photo: nil,
	}
	_, err := SetMyProfilePhoto(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetMyProfilePhoto_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyProfilePhotoParams{
		Photo: nil,
	}
	_, err := SetMyProfilePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetMyProfilePhoto_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyProfilePhotoParams{
		Photo: nil,
	}
	_, err := SetMyProfilePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetMyProfilePhoto_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyProfilePhotoParams{
		Photo: nil,
	}
	_, err := SetMyProfilePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetMyProfilePhoto_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyProfilePhotoParams{
		Photo: nil,
	}
	_, err := SetMyProfilePhoto(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetMyProfilePhoto_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetMyProfilePhoto_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetMyProfilePhoto(context.Background(), bot, &SetMyProfilePhotoParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetMyProfilePhoto_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetMyProfilePhoto_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyProfilePhotoParams{
		Photo: nil,
	}
	_, err := SetMyProfilePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetMyProfilePhoto_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetMyProfilePhoto_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyProfilePhotoParams{
		Photo: nil,
	}
	_, err := SetMyProfilePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_RemoveMyProfilePhoto_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/removeMyProfilePhoto")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := RemoveMyProfilePhoto(context.Background(), bot, &RemoveMyProfilePhotoParams{})
	require.NoError(t, err)
}

func Test_RemoveMyProfilePhoto_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := RemoveMyProfilePhoto(context.Background(), bot, &RemoveMyProfilePhotoParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_RemoveMyProfilePhoto_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := RemoveMyProfilePhoto(context.Background(), bot, &RemoveMyProfilePhotoParams{})
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_RemoveMyProfilePhoto_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := RemoveMyProfilePhoto(context.Background(), bot, &RemoveMyProfilePhotoParams{})
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_RemoveMyProfilePhoto_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := RemoveMyProfilePhoto(ctx, bot, &RemoveMyProfilePhotoParams{})
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_RemoveMyProfilePhoto_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_RemoveMyProfilePhoto_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := RemoveMyProfilePhoto(context.Background(), bot, &RemoveMyProfilePhotoParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_RemoveMyProfilePhoto_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_RemoveMyProfilePhoto_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := RemoveMyProfilePhoto(context.Background(), bot, &RemoveMyProfilePhotoParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_RemoveMyProfilePhoto_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_RemoveMyProfilePhoto_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := RemoveMyProfilePhoto(context.Background(), bot, &RemoveMyProfilePhotoParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetChatMenuButton_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setChatMenuButton")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatMenuButtonParams{}
	_, err := SetChatMenuButton(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetChatMenuButton_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatMenuButtonParams{}
	_, err := SetChatMenuButton(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetChatMenuButton_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatMenuButtonParams{}
	_, err := SetChatMenuButton(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetChatMenuButton_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatMenuButtonParams{}
	_, err := SetChatMenuButton(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetChatMenuButton_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatMenuButtonParams{}
	_, err := SetChatMenuButton(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetChatMenuButton_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetChatMenuButton_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetChatMenuButton(context.Background(), bot, &SetChatMenuButtonParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetChatMenuButton_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetChatMenuButton_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatMenuButtonParams{}
	_, err := SetChatMenuButton(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetChatMenuButton_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetChatMenuButton_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetChatMenuButtonParams{}
	_, err := SetChatMenuButton(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetChatMenuButton_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getChatMenuButton")
	})).Return(genTestResp(200, `{"ok":true,"result":{"type":"commands"}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMenuButtonParams{}
	_, err := GetChatMenuButton(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetChatMenuButton_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMenuButtonParams{}
	_, err := GetChatMenuButton(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetChatMenuButton_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMenuButtonParams{}
	_, err := GetChatMenuButton(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetChatMenuButton_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMenuButtonParams{}
	_, err := GetChatMenuButton(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetChatMenuButton_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMenuButtonParams{}
	_, err := GetChatMenuButton(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetChatMenuButton_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetChatMenuButton_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetChatMenuButton(context.Background(), bot, &GetChatMenuButtonParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetChatMenuButton_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetChatMenuButton_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMenuButtonParams{}
	_, err := GetChatMenuButton(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetChatMenuButton_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetChatMenuButton_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatMenuButtonParams{}
	_, err := GetChatMenuButton(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetMyDefaultAdministratorRights_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setMyDefaultAdministratorRights")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyDefaultAdministratorRightsParams{}
	_, err := SetMyDefaultAdministratorRights(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetMyDefaultAdministratorRights_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyDefaultAdministratorRightsParams{}
	_, err := SetMyDefaultAdministratorRights(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetMyDefaultAdministratorRights_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyDefaultAdministratorRightsParams{}
	_, err := SetMyDefaultAdministratorRights(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetMyDefaultAdministratorRights_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyDefaultAdministratorRightsParams{}
	_, err := SetMyDefaultAdministratorRights(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetMyDefaultAdministratorRights_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyDefaultAdministratorRightsParams{}
	_, err := SetMyDefaultAdministratorRights(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetMyDefaultAdministratorRights_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetMyDefaultAdministratorRights_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetMyDefaultAdministratorRights(context.Background(), bot, &SetMyDefaultAdministratorRightsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetMyDefaultAdministratorRights_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetMyDefaultAdministratorRights_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyDefaultAdministratorRightsParams{}
	_, err := SetMyDefaultAdministratorRights(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetMyDefaultAdministratorRights_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetMyDefaultAdministratorRights_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetMyDefaultAdministratorRightsParams{}
	_, err := SetMyDefaultAdministratorRights(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetMyDefaultAdministratorRights_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getMyDefaultAdministratorRights")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyDefaultAdministratorRightsParams{}
	_, err := GetMyDefaultAdministratorRights(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetMyDefaultAdministratorRights_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyDefaultAdministratorRightsParams{}
	_, err := GetMyDefaultAdministratorRights(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetMyDefaultAdministratorRights_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyDefaultAdministratorRightsParams{}
	_, err := GetMyDefaultAdministratorRights(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetMyDefaultAdministratorRights_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyDefaultAdministratorRightsParams{}
	_, err := GetMyDefaultAdministratorRights(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetMyDefaultAdministratorRights_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyDefaultAdministratorRightsParams{}
	_, err := GetMyDefaultAdministratorRights(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetMyDefaultAdministratorRights_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetMyDefaultAdministratorRights_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetMyDefaultAdministratorRights(context.Background(), bot, &GetMyDefaultAdministratorRightsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetMyDefaultAdministratorRights_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetMyDefaultAdministratorRights_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyDefaultAdministratorRightsParams{}
	_, err := GetMyDefaultAdministratorRights(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetMyDefaultAdministratorRights_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetMyDefaultAdministratorRights_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetMyDefaultAdministratorRightsParams{}
	_, err := GetMyDefaultAdministratorRights(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetAvailableGifts_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getAvailableGifts")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetAvailableGifts(context.Background(), bot, &GetAvailableGiftsParams{})
	require.NoError(t, err)
}

func Test_GetAvailableGifts_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetAvailableGifts(context.Background(), bot, &GetAvailableGiftsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetAvailableGifts_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetAvailableGifts(context.Background(), bot, &GetAvailableGiftsParams{})
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetAvailableGifts_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetAvailableGifts(context.Background(), bot, &GetAvailableGiftsParams{})
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetAvailableGifts_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetAvailableGifts(ctx, bot, &GetAvailableGiftsParams{})
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetAvailableGifts_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetAvailableGifts_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetAvailableGifts(context.Background(), bot, &GetAvailableGiftsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetAvailableGifts_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetAvailableGifts_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetAvailableGifts(context.Background(), bot, &GetAvailableGiftsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetAvailableGifts_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetAvailableGifts_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetAvailableGifts(context.Background(), bot, &GetAvailableGiftsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendGift_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendGift")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendGiftParams{
		GiftID: "test_value",
	}
	_, err := SendGift(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendGift_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendGiftParams{
		GiftID: "test_value",
	}
	_, err := SendGift(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendGift_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendGiftParams{
		GiftID: "test_value",
	}
	_, err := SendGift(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendGift_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendGiftParams{
		GiftID: "test_value",
	}
	_, err := SendGift(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendGift_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendGiftParams{
		GiftID: "test_value",
	}
	_, err := SendGift(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendGift_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendGift_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendGift(context.Background(), bot, &SendGiftParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendGift_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendGift_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendGiftParams{
		GiftID: "test_value",
	}
	_, err := SendGift(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendGift_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendGift_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendGiftParams{
		GiftID: "test_value",
	}
	_, err := SendGift(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GiftPremiumSubscription_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/giftPremiumSubscription")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GiftPremiumSubscriptionParams{
		UserID:     42,
		MonthCount: 42,
		StarCount:  42,
	}
	_, err := GiftPremiumSubscription(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GiftPremiumSubscription_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GiftPremiumSubscriptionParams{
		UserID:     42,
		MonthCount: 42,
		StarCount:  42,
	}
	_, err := GiftPremiumSubscription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GiftPremiumSubscription_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GiftPremiumSubscriptionParams{
		UserID:     42,
		MonthCount: 42,
		StarCount:  42,
	}
	_, err := GiftPremiumSubscription(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GiftPremiumSubscription_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GiftPremiumSubscriptionParams{
		UserID:     42,
		MonthCount: 42,
		StarCount:  42,
	}
	_, err := GiftPremiumSubscription(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GiftPremiumSubscription_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GiftPremiumSubscriptionParams{
		UserID:     42,
		MonthCount: 42,
		StarCount:  42,
	}
	_, err := GiftPremiumSubscription(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GiftPremiumSubscription_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GiftPremiumSubscription_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GiftPremiumSubscription(context.Background(), bot, &GiftPremiumSubscriptionParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GiftPremiumSubscription_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GiftPremiumSubscription_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GiftPremiumSubscriptionParams{
		UserID:     42,
		MonthCount: 42,
		StarCount:  42,
	}
	_, err := GiftPremiumSubscription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GiftPremiumSubscription_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GiftPremiumSubscription_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GiftPremiumSubscriptionParams{
		UserID:     42,
		MonthCount: 42,
		StarCount:  42,
	}
	_, err := GiftPremiumSubscription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_VerifyUser_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/verifyUser")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &VerifyUserParams{
		UserID: 42,
	}
	_, err := VerifyUser(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_VerifyUser_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &VerifyUserParams{
		UserID: 42,
	}
	_, err := VerifyUser(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_VerifyUser_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &VerifyUserParams{
		UserID: 42,
	}
	_, err := VerifyUser(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_VerifyUser_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &VerifyUserParams{
		UserID: 42,
	}
	_, err := VerifyUser(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_VerifyUser_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &VerifyUserParams{
		UserID: 42,
	}
	_, err := VerifyUser(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_VerifyUser_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_VerifyUser_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := VerifyUser(context.Background(), bot, &VerifyUserParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_VerifyUser_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_VerifyUser_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &VerifyUserParams{
		UserID: 42,
	}
	_, err := VerifyUser(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_VerifyUser_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_VerifyUser_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &VerifyUserParams{
		UserID: 42,
	}
	_, err := VerifyUser(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_VerifyChat_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/verifyChat")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &VerifyChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := VerifyChat(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_VerifyChat_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &VerifyChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := VerifyChat(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_VerifyChat_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &VerifyChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := VerifyChat(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_VerifyChat_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &VerifyChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := VerifyChat(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_VerifyChat_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &VerifyChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := VerifyChat(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_VerifyChat_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_VerifyChat_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := VerifyChat(context.Background(), bot, &VerifyChatParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_VerifyChat_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_VerifyChat_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &VerifyChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := VerifyChat(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_VerifyChat_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_VerifyChat_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &VerifyChatParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := VerifyChat(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_RemoveUserVerification_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/removeUserVerification")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveUserVerificationParams{
		UserID: 42,
	}
	_, err := RemoveUserVerification(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_RemoveUserVerification_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveUserVerificationParams{
		UserID: 42,
	}
	_, err := RemoveUserVerification(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_RemoveUserVerification_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveUserVerificationParams{
		UserID: 42,
	}
	_, err := RemoveUserVerification(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_RemoveUserVerification_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveUserVerificationParams{
		UserID: 42,
	}
	_, err := RemoveUserVerification(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_RemoveUserVerification_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveUserVerificationParams{
		UserID: 42,
	}
	_, err := RemoveUserVerification(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_RemoveUserVerification_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_RemoveUserVerification_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := RemoveUserVerification(context.Background(), bot, &RemoveUserVerificationParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_RemoveUserVerification_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_RemoveUserVerification_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveUserVerificationParams{
		UserID: 42,
	}
	_, err := RemoveUserVerification(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_RemoveUserVerification_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_RemoveUserVerification_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveUserVerificationParams{
		UserID: 42,
	}
	_, err := RemoveUserVerification(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_RemoveChatVerification_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/removeChatVerification")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveChatVerificationParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := RemoveChatVerification(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_RemoveChatVerification_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveChatVerificationParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := RemoveChatVerification(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_RemoveChatVerification_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveChatVerificationParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := RemoveChatVerification(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_RemoveChatVerification_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveChatVerificationParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := RemoveChatVerification(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_RemoveChatVerification_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveChatVerificationParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := RemoveChatVerification(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_RemoveChatVerification_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_RemoveChatVerification_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := RemoveChatVerification(context.Background(), bot, &RemoveChatVerificationParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_RemoveChatVerification_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_RemoveChatVerification_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveChatVerificationParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := RemoveChatVerification(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_RemoveChatVerification_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_RemoveChatVerification_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveChatVerificationParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := RemoveChatVerification(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_ReadBusinessMessage_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/readBusinessMessage")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReadBusinessMessageParams{
		BusinessConnectionID: "test_value",
		ChatID:               42,
		MessageID:            42,
	}
	_, err := ReadBusinessMessage(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_ReadBusinessMessage_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReadBusinessMessageParams{
		BusinessConnectionID: "test_value",
		ChatID:               42,
		MessageID:            42,
	}
	_, err := ReadBusinessMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_ReadBusinessMessage_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReadBusinessMessageParams{
		BusinessConnectionID: "test_value",
		ChatID:               42,
		MessageID:            42,
	}
	_, err := ReadBusinessMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_ReadBusinessMessage_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReadBusinessMessageParams{
		BusinessConnectionID: "test_value",
		ChatID:               42,
		MessageID:            42,
	}
	_, err := ReadBusinessMessage(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_ReadBusinessMessage_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReadBusinessMessageParams{
		BusinessConnectionID: "test_value",
		ChatID:               42,
		MessageID:            42,
	}
	_, err := ReadBusinessMessage(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_ReadBusinessMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ReadBusinessMessage_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := ReadBusinessMessage(context.Background(), bot, &ReadBusinessMessageParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_ReadBusinessMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ReadBusinessMessage_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReadBusinessMessageParams{
		BusinessConnectionID: "test_value",
		ChatID:               42,
		MessageID:            42,
	}
	_, err := ReadBusinessMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_ReadBusinessMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ReadBusinessMessage_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReadBusinessMessageParams{
		BusinessConnectionID: "test_value",
		ChatID:               42,
		MessageID:            42,
	}
	_, err := ReadBusinessMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_DeleteBusinessMessages_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/deleteBusinessMessages")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteBusinessMessagesParams{
		BusinessConnectionID: "test_value",
		MessageIds:           nil,
	}
	_, err := DeleteBusinessMessages(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_DeleteBusinessMessages_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteBusinessMessagesParams{
		BusinessConnectionID: "test_value",
		MessageIds:           nil,
	}
	_, err := DeleteBusinessMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_DeleteBusinessMessages_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteBusinessMessagesParams{
		BusinessConnectionID: "test_value",
		MessageIds:           nil,
	}
	_, err := DeleteBusinessMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_DeleteBusinessMessages_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteBusinessMessagesParams{
		BusinessConnectionID: "test_value",
		MessageIds:           nil,
	}
	_, err := DeleteBusinessMessages(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_DeleteBusinessMessages_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteBusinessMessagesParams{
		BusinessConnectionID: "test_value",
		MessageIds:           nil,
	}
	_, err := DeleteBusinessMessages(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_DeleteBusinessMessages_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteBusinessMessages_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := DeleteBusinessMessages(context.Background(), bot, &DeleteBusinessMessagesParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteBusinessMessages_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteBusinessMessages_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteBusinessMessagesParams{
		BusinessConnectionID: "test_value",
		MessageIds:           nil,
	}
	_, err := DeleteBusinessMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteBusinessMessages_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteBusinessMessages_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteBusinessMessagesParams{
		BusinessConnectionID: "test_value",
		MessageIds:           nil,
	}
	_, err := DeleteBusinessMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetBusinessAccountName_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setBusinessAccountName")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountNameParams{
		BusinessConnectionID: "test_value",
		FirstName:            "test_value",
	}
	_, err := SetBusinessAccountName(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetBusinessAccountName_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountNameParams{
		BusinessConnectionID: "test_value",
		FirstName:            "test_value",
	}
	_, err := SetBusinessAccountName(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetBusinessAccountName_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountNameParams{
		BusinessConnectionID: "test_value",
		FirstName:            "test_value",
	}
	_, err := SetBusinessAccountName(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetBusinessAccountName_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountNameParams{
		BusinessConnectionID: "test_value",
		FirstName:            "test_value",
	}
	_, err := SetBusinessAccountName(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetBusinessAccountName_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountNameParams{
		BusinessConnectionID: "test_value",
		FirstName:            "test_value",
	}
	_, err := SetBusinessAccountName(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetBusinessAccountName_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetBusinessAccountName_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetBusinessAccountName(context.Background(), bot, &SetBusinessAccountNameParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetBusinessAccountName_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetBusinessAccountName_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountNameParams{
		BusinessConnectionID: "test_value",
		FirstName:            "test_value",
	}
	_, err := SetBusinessAccountName(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetBusinessAccountName_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetBusinessAccountName_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountNameParams{
		BusinessConnectionID: "test_value",
		FirstName:            "test_value",
	}
	_, err := SetBusinessAccountName(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetBusinessAccountUsername_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setBusinessAccountUsername")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountUsernameParams{
		BusinessConnectionID: "test_value",
	}
	_, err := SetBusinessAccountUsername(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetBusinessAccountUsername_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountUsernameParams{
		BusinessConnectionID: "test_value",
	}
	_, err := SetBusinessAccountUsername(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetBusinessAccountUsername_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountUsernameParams{
		BusinessConnectionID: "test_value",
	}
	_, err := SetBusinessAccountUsername(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetBusinessAccountUsername_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountUsernameParams{
		BusinessConnectionID: "test_value",
	}
	_, err := SetBusinessAccountUsername(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetBusinessAccountUsername_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountUsernameParams{
		BusinessConnectionID: "test_value",
	}
	_, err := SetBusinessAccountUsername(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetBusinessAccountUsername_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetBusinessAccountUsername_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetBusinessAccountUsername(context.Background(), bot, &SetBusinessAccountUsernameParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetBusinessAccountUsername_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetBusinessAccountUsername_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountUsernameParams{
		BusinessConnectionID: "test_value",
	}
	_, err := SetBusinessAccountUsername(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetBusinessAccountUsername_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetBusinessAccountUsername_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountUsernameParams{
		BusinessConnectionID: "test_value",
	}
	_, err := SetBusinessAccountUsername(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetBusinessAccountBio_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setBusinessAccountBio")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountBioParams{
		BusinessConnectionID: "test_value",
	}
	_, err := SetBusinessAccountBio(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetBusinessAccountBio_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountBioParams{
		BusinessConnectionID: "test_value",
	}
	_, err := SetBusinessAccountBio(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetBusinessAccountBio_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountBioParams{
		BusinessConnectionID: "test_value",
	}
	_, err := SetBusinessAccountBio(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetBusinessAccountBio_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountBioParams{
		BusinessConnectionID: "test_value",
	}
	_, err := SetBusinessAccountBio(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetBusinessAccountBio_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountBioParams{
		BusinessConnectionID: "test_value",
	}
	_, err := SetBusinessAccountBio(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetBusinessAccountBio_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetBusinessAccountBio_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetBusinessAccountBio(context.Background(), bot, &SetBusinessAccountBioParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetBusinessAccountBio_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetBusinessAccountBio_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountBioParams{
		BusinessConnectionID: "test_value",
	}
	_, err := SetBusinessAccountBio(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetBusinessAccountBio_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetBusinessAccountBio_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountBioParams{
		BusinessConnectionID: "test_value",
	}
	_, err := SetBusinessAccountBio(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetBusinessAccountProfilePhoto_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setBusinessAccountProfilePhoto")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountProfilePhotoParams{
		BusinessConnectionID: "test_value",
		Photo:                nil,
	}
	_, err := SetBusinessAccountProfilePhoto(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetBusinessAccountProfilePhoto_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountProfilePhotoParams{
		BusinessConnectionID: "test_value",
		Photo:                nil,
	}
	_, err := SetBusinessAccountProfilePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetBusinessAccountProfilePhoto_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountProfilePhotoParams{
		BusinessConnectionID: "test_value",
		Photo:                nil,
	}
	_, err := SetBusinessAccountProfilePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetBusinessAccountProfilePhoto_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountProfilePhotoParams{
		BusinessConnectionID: "test_value",
		Photo:                nil,
	}
	_, err := SetBusinessAccountProfilePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetBusinessAccountProfilePhoto_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountProfilePhotoParams{
		BusinessConnectionID: "test_value",
		Photo:                nil,
	}
	_, err := SetBusinessAccountProfilePhoto(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetBusinessAccountProfilePhoto_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetBusinessAccountProfilePhoto_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetBusinessAccountProfilePhoto(context.Background(), bot, &SetBusinessAccountProfilePhotoParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetBusinessAccountProfilePhoto_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetBusinessAccountProfilePhoto_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountProfilePhotoParams{
		BusinessConnectionID: "test_value",
		Photo:                nil,
	}
	_, err := SetBusinessAccountProfilePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetBusinessAccountProfilePhoto_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetBusinessAccountProfilePhoto_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountProfilePhotoParams{
		BusinessConnectionID: "test_value",
		Photo:                nil,
	}
	_, err := SetBusinessAccountProfilePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_RemoveBusinessAccountProfilePhoto_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/removeBusinessAccountProfilePhoto")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveBusinessAccountProfilePhotoParams{
		BusinessConnectionID: "test_value",
	}
	_, err := RemoveBusinessAccountProfilePhoto(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_RemoveBusinessAccountProfilePhoto_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveBusinessAccountProfilePhotoParams{
		BusinessConnectionID: "test_value",
	}
	_, err := RemoveBusinessAccountProfilePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_RemoveBusinessAccountProfilePhoto_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveBusinessAccountProfilePhotoParams{
		BusinessConnectionID: "test_value",
	}
	_, err := RemoveBusinessAccountProfilePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_RemoveBusinessAccountProfilePhoto_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveBusinessAccountProfilePhotoParams{
		BusinessConnectionID: "test_value",
	}
	_, err := RemoveBusinessAccountProfilePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_RemoveBusinessAccountProfilePhoto_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveBusinessAccountProfilePhotoParams{
		BusinessConnectionID: "test_value",
	}
	_, err := RemoveBusinessAccountProfilePhoto(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_RemoveBusinessAccountProfilePhoto_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_RemoveBusinessAccountProfilePhoto_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := RemoveBusinessAccountProfilePhoto(context.Background(), bot, &RemoveBusinessAccountProfilePhotoParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_RemoveBusinessAccountProfilePhoto_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_RemoveBusinessAccountProfilePhoto_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveBusinessAccountProfilePhotoParams{
		BusinessConnectionID: "test_value",
	}
	_, err := RemoveBusinessAccountProfilePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_RemoveBusinessAccountProfilePhoto_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_RemoveBusinessAccountProfilePhoto_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RemoveBusinessAccountProfilePhotoParams{
		BusinessConnectionID: "test_value",
	}
	_, err := RemoveBusinessAccountProfilePhoto(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetBusinessAccountGiftSettings_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setBusinessAccountGiftSettings")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountGiftSettingsParams{
		BusinessConnectionID: "test_value",
		ShowGiftButton:       true,
		AcceptedGiftTypes:    AcceptedGiftTypes{},
	}
	_, err := SetBusinessAccountGiftSettings(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetBusinessAccountGiftSettings_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountGiftSettingsParams{
		BusinessConnectionID: "test_value",
		ShowGiftButton:       true,
		AcceptedGiftTypes:    AcceptedGiftTypes{},
	}
	_, err := SetBusinessAccountGiftSettings(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetBusinessAccountGiftSettings_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountGiftSettingsParams{
		BusinessConnectionID: "test_value",
		ShowGiftButton:       true,
		AcceptedGiftTypes:    AcceptedGiftTypes{},
	}
	_, err := SetBusinessAccountGiftSettings(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetBusinessAccountGiftSettings_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountGiftSettingsParams{
		BusinessConnectionID: "test_value",
		ShowGiftButton:       true,
		AcceptedGiftTypes:    AcceptedGiftTypes{},
	}
	_, err := SetBusinessAccountGiftSettings(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetBusinessAccountGiftSettings_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountGiftSettingsParams{
		BusinessConnectionID: "test_value",
		ShowGiftButton:       true,
		AcceptedGiftTypes:    AcceptedGiftTypes{},
	}
	_, err := SetBusinessAccountGiftSettings(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetBusinessAccountGiftSettings_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetBusinessAccountGiftSettings_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetBusinessAccountGiftSettings(context.Background(), bot, &SetBusinessAccountGiftSettingsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetBusinessAccountGiftSettings_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetBusinessAccountGiftSettings_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountGiftSettingsParams{
		BusinessConnectionID: "test_value",
		ShowGiftButton:       true,
		AcceptedGiftTypes:    AcceptedGiftTypes{},
	}
	_, err := SetBusinessAccountGiftSettings(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetBusinessAccountGiftSettings_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetBusinessAccountGiftSettings_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetBusinessAccountGiftSettingsParams{
		BusinessConnectionID: "test_value",
		ShowGiftButton:       true,
		AcceptedGiftTypes:    AcceptedGiftTypes{},
	}
	_, err := SetBusinessAccountGiftSettings(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetBusinessAccountStarBalance_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getBusinessAccountStarBalance")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessAccountStarBalanceParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessAccountStarBalance(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetBusinessAccountStarBalance_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessAccountStarBalanceParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessAccountStarBalance(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetBusinessAccountStarBalance_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessAccountStarBalanceParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessAccountStarBalance(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetBusinessAccountStarBalance_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessAccountStarBalanceParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessAccountStarBalance(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetBusinessAccountStarBalance_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessAccountStarBalanceParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessAccountStarBalance(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetBusinessAccountStarBalance_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetBusinessAccountStarBalance_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetBusinessAccountStarBalance(context.Background(), bot, &GetBusinessAccountStarBalanceParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetBusinessAccountStarBalance_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetBusinessAccountStarBalance_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessAccountStarBalanceParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessAccountStarBalance(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetBusinessAccountStarBalance_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetBusinessAccountStarBalance_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessAccountStarBalanceParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessAccountStarBalance(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_TransferBusinessAccountStars_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/transferBusinessAccountStars")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &TransferBusinessAccountStarsParams{
		BusinessConnectionID: "test_value",
		StarCount:            42,
	}
	_, err := TransferBusinessAccountStars(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_TransferBusinessAccountStars_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &TransferBusinessAccountStarsParams{
		BusinessConnectionID: "test_value",
		StarCount:            42,
	}
	_, err := TransferBusinessAccountStars(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_TransferBusinessAccountStars_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &TransferBusinessAccountStarsParams{
		BusinessConnectionID: "test_value",
		StarCount:            42,
	}
	_, err := TransferBusinessAccountStars(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_TransferBusinessAccountStars_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &TransferBusinessAccountStarsParams{
		BusinessConnectionID: "test_value",
		StarCount:            42,
	}
	_, err := TransferBusinessAccountStars(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_TransferBusinessAccountStars_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &TransferBusinessAccountStarsParams{
		BusinessConnectionID: "test_value",
		StarCount:            42,
	}
	_, err := TransferBusinessAccountStars(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_TransferBusinessAccountStars_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_TransferBusinessAccountStars_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := TransferBusinessAccountStars(context.Background(), bot, &TransferBusinessAccountStarsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_TransferBusinessAccountStars_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_TransferBusinessAccountStars_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &TransferBusinessAccountStarsParams{
		BusinessConnectionID: "test_value",
		StarCount:            42,
	}
	_, err := TransferBusinessAccountStars(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_TransferBusinessAccountStars_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_TransferBusinessAccountStars_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &TransferBusinessAccountStarsParams{
		BusinessConnectionID: "test_value",
		StarCount:            42,
	}
	_, err := TransferBusinessAccountStars(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetBusinessAccountGifts_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getBusinessAccountGifts")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessAccountGiftsParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessAccountGifts(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetBusinessAccountGifts_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessAccountGiftsParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessAccountGifts(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetBusinessAccountGifts_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessAccountGiftsParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessAccountGifts(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetBusinessAccountGifts_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessAccountGiftsParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessAccountGifts(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetBusinessAccountGifts_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessAccountGiftsParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessAccountGifts(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetBusinessAccountGifts_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetBusinessAccountGifts_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetBusinessAccountGifts(context.Background(), bot, &GetBusinessAccountGiftsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetBusinessAccountGifts_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetBusinessAccountGifts_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessAccountGiftsParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessAccountGifts(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetBusinessAccountGifts_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetBusinessAccountGifts_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetBusinessAccountGiftsParams{
		BusinessConnectionID: "test_value",
	}
	_, err := GetBusinessAccountGifts(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetUserGifts_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getUserGifts")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserGiftsParams{
		UserID: 42,
	}
	_, err := GetUserGifts(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetUserGifts_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserGiftsParams{
		UserID: 42,
	}
	_, err := GetUserGifts(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetUserGifts_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserGiftsParams{
		UserID: 42,
	}
	_, err := GetUserGifts(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetUserGifts_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserGiftsParams{
		UserID: 42,
	}
	_, err := GetUserGifts(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetUserGifts_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserGiftsParams{
		UserID: 42,
	}
	_, err := GetUserGifts(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetUserGifts_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetUserGifts_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetUserGifts(context.Background(), bot, &GetUserGiftsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetUserGifts_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetUserGifts_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserGiftsParams{
		UserID: 42,
	}
	_, err := GetUserGifts(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetUserGifts_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetUserGifts_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetUserGiftsParams{
		UserID: 42,
	}
	_, err := GetUserGifts(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetChatGifts_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getChatGifts")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatGiftsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatGifts(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetChatGifts_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatGiftsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatGifts(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetChatGifts_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatGiftsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatGifts(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetChatGifts_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatGiftsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatGifts(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetChatGifts_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatGiftsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatGifts(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetChatGifts_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetChatGifts_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetChatGifts(context.Background(), bot, &GetChatGiftsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetChatGifts_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetChatGifts_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatGiftsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatGifts(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetChatGifts_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetChatGifts_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetChatGiftsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := GetChatGifts(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_ConvertGiftToStars_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/convertGiftToStars")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ConvertGiftToStarsParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
	}
	_, err := ConvertGiftToStars(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_ConvertGiftToStars_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ConvertGiftToStarsParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
	}
	_, err := ConvertGiftToStars(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_ConvertGiftToStars_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ConvertGiftToStarsParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
	}
	_, err := ConvertGiftToStars(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_ConvertGiftToStars_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ConvertGiftToStarsParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
	}
	_, err := ConvertGiftToStars(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_ConvertGiftToStars_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ConvertGiftToStarsParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
	}
	_, err := ConvertGiftToStars(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_ConvertGiftToStars_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ConvertGiftToStars_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := ConvertGiftToStars(context.Background(), bot, &ConvertGiftToStarsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_ConvertGiftToStars_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ConvertGiftToStars_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ConvertGiftToStarsParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
	}
	_, err := ConvertGiftToStars(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_ConvertGiftToStars_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ConvertGiftToStars_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ConvertGiftToStarsParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
	}
	_, err := ConvertGiftToStars(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_UpgradeGift_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/upgradeGift")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UpgradeGiftParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
	}
	_, err := UpgradeGift(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_UpgradeGift_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UpgradeGiftParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
	}
	_, err := UpgradeGift(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_UpgradeGift_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UpgradeGiftParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
	}
	_, err := UpgradeGift(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_UpgradeGift_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UpgradeGiftParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
	}
	_, err := UpgradeGift(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_UpgradeGift_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UpgradeGiftParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
	}
	_, err := UpgradeGift(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_UpgradeGift_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UpgradeGift_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := UpgradeGift(context.Background(), bot, &UpgradeGiftParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_UpgradeGift_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UpgradeGift_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UpgradeGiftParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
	}
	_, err := UpgradeGift(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_UpgradeGift_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UpgradeGift_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UpgradeGiftParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
	}
	_, err := UpgradeGift(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_TransferGift_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/transferGift")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &TransferGiftParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
		NewOwnerChatID:       42,
	}
	_, err := TransferGift(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_TransferGift_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &TransferGiftParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
		NewOwnerChatID:       42,
	}
	_, err := TransferGift(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_TransferGift_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &TransferGiftParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
		NewOwnerChatID:       42,
	}
	_, err := TransferGift(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_TransferGift_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &TransferGiftParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
		NewOwnerChatID:       42,
	}
	_, err := TransferGift(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_TransferGift_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &TransferGiftParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
		NewOwnerChatID:       42,
	}
	_, err := TransferGift(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_TransferGift_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_TransferGift_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := TransferGift(context.Background(), bot, &TransferGiftParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_TransferGift_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_TransferGift_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &TransferGiftParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
		NewOwnerChatID:       42,
	}
	_, err := TransferGift(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_TransferGift_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_TransferGift_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &TransferGiftParams{
		BusinessConnectionID: "test_value",
		OwnedGiftID:          "test_value",
		NewOwnerChatID:       42,
	}
	_, err := TransferGift(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_PostStory_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/postStory")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PostStoryParams{
		BusinessConnectionID: "test_value",
		Content:              nil,
		ActivePeriod:         42,
	}
	_, err := PostStory(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_PostStory_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PostStoryParams{
		BusinessConnectionID: "test_value",
		Content:              nil,
		ActivePeriod:         42,
	}
	_, err := PostStory(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_PostStory_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PostStoryParams{
		BusinessConnectionID: "test_value",
		Content:              nil,
		ActivePeriod:         42,
	}
	_, err := PostStory(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_PostStory_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PostStoryParams{
		BusinessConnectionID: "test_value",
		Content:              nil,
		ActivePeriod:         42,
	}
	_, err := PostStory(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_PostStory_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PostStoryParams{
		BusinessConnectionID: "test_value",
		Content:              nil,
		ActivePeriod:         42,
	}
	_, err := PostStory(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_PostStory_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_PostStory_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := PostStory(context.Background(), bot, &PostStoryParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_PostStory_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_PostStory_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PostStoryParams{
		BusinessConnectionID: "test_value",
		Content:              nil,
		ActivePeriod:         42,
	}
	_, err := PostStory(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_PostStory_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_PostStory_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &PostStoryParams{
		BusinessConnectionID: "test_value",
		Content:              nil,
		ActivePeriod:         42,
	}
	_, err := PostStory(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_RepostStory_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/repostStory")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RepostStoryParams{
		BusinessConnectionID: "test_value",
		FromChatID:           42,
		FromStoryID:          42,
		ActivePeriod:         42,
	}
	_, err := RepostStory(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_RepostStory_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RepostStoryParams{
		BusinessConnectionID: "test_value",
		FromChatID:           42,
		FromStoryID:          42,
		ActivePeriod:         42,
	}
	_, err := RepostStory(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_RepostStory_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RepostStoryParams{
		BusinessConnectionID: "test_value",
		FromChatID:           42,
		FromStoryID:          42,
		ActivePeriod:         42,
	}
	_, err := RepostStory(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_RepostStory_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RepostStoryParams{
		BusinessConnectionID: "test_value",
		FromChatID:           42,
		FromStoryID:          42,
		ActivePeriod:         42,
	}
	_, err := RepostStory(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_RepostStory_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RepostStoryParams{
		BusinessConnectionID: "test_value",
		FromChatID:           42,
		FromStoryID:          42,
		ActivePeriod:         42,
	}
	_, err := RepostStory(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_RepostStory_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_RepostStory_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := RepostStory(context.Background(), bot, &RepostStoryParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_RepostStory_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_RepostStory_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RepostStoryParams{
		BusinessConnectionID: "test_value",
		FromChatID:           42,
		FromStoryID:          42,
		ActivePeriod:         42,
	}
	_, err := RepostStory(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_RepostStory_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_RepostStory_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RepostStoryParams{
		BusinessConnectionID: "test_value",
		FromChatID:           42,
		FromStoryID:          42,
		ActivePeriod:         42,
	}
	_, err := RepostStory(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_EditStory_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/editStory")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditStoryParams{
		BusinessConnectionID: "test_value",
		StoryID:              42,
		Content:              nil,
	}
	_, err := EditStory(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_EditStory_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditStoryParams{
		BusinessConnectionID: "test_value",
		StoryID:              42,
		Content:              nil,
	}
	_, err := EditStory(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_EditStory_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditStoryParams{
		BusinessConnectionID: "test_value",
		StoryID:              42,
		Content:              nil,
	}
	_, err := EditStory(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_EditStory_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditStoryParams{
		BusinessConnectionID: "test_value",
		StoryID:              42,
		Content:              nil,
	}
	_, err := EditStory(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_EditStory_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditStoryParams{
		BusinessConnectionID: "test_value",
		StoryID:              42,
		Content:              nil,
	}
	_, err := EditStory(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_EditStory_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditStory_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := EditStory(context.Background(), bot, &EditStoryParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_EditStory_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditStory_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditStoryParams{
		BusinessConnectionID: "test_value",
		StoryID:              42,
		Content:              nil,
	}
	_, err := EditStory(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_EditStory_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditStory_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditStoryParams{
		BusinessConnectionID: "test_value",
		StoryID:              42,
		Content:              nil,
	}
	_, err := EditStory(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_DeleteStory_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/deleteStory")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStoryParams{
		BusinessConnectionID: "test_value",
		StoryID:              42,
	}
	_, err := DeleteStory(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_DeleteStory_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStoryParams{
		BusinessConnectionID: "test_value",
		StoryID:              42,
	}
	_, err := DeleteStory(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_DeleteStory_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStoryParams{
		BusinessConnectionID: "test_value",
		StoryID:              42,
	}
	_, err := DeleteStory(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_DeleteStory_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStoryParams{
		BusinessConnectionID: "test_value",
		StoryID:              42,
	}
	_, err := DeleteStory(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_DeleteStory_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStoryParams{
		BusinessConnectionID: "test_value",
		StoryID:              42,
	}
	_, err := DeleteStory(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_DeleteStory_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteStory_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := DeleteStory(context.Background(), bot, &DeleteStoryParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteStory_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteStory_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStoryParams{
		BusinessConnectionID: "test_value",
		StoryID:              42,
	}
	_, err := DeleteStory(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteStory_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteStory_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStoryParams{
		BusinessConnectionID: "test_value",
		StoryID:              42,
	}
	_, err := DeleteStory(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_AnswerWebAppQuery_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/answerWebAppQuery")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerWebAppQueryParams{
		WebAppQueryID: "test_value",
		Result:        nil,
	}
	_, err := AnswerWebAppQuery(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_AnswerWebAppQuery_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerWebAppQueryParams{
		WebAppQueryID: "test_value",
		Result:        nil,
	}
	_, err := AnswerWebAppQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_AnswerWebAppQuery_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerWebAppQueryParams{
		WebAppQueryID: "test_value",
		Result:        nil,
	}
	_, err := AnswerWebAppQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_AnswerWebAppQuery_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerWebAppQueryParams{
		WebAppQueryID: "test_value",
		Result:        nil,
	}
	_, err := AnswerWebAppQuery(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_AnswerWebAppQuery_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerWebAppQueryParams{
		WebAppQueryID: "test_value",
		Result:        nil,
	}
	_, err := AnswerWebAppQuery(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_AnswerWebAppQuery_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_AnswerWebAppQuery_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := AnswerWebAppQuery(context.Background(), bot, &AnswerWebAppQueryParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_AnswerWebAppQuery_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_AnswerWebAppQuery_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerWebAppQueryParams{
		WebAppQueryID: "test_value",
		Result:        nil,
	}
	_, err := AnswerWebAppQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_AnswerWebAppQuery_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_AnswerWebAppQuery_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerWebAppQueryParams{
		WebAppQueryID: "test_value",
		Result:        nil,
	}
	_, err := AnswerWebAppQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SavePreparedInlineMessage_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/savePreparedInlineMessage")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SavePreparedInlineMessageParams{
		UserID: 42,
		Result: nil,
	}
	_, err := SavePreparedInlineMessage(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SavePreparedInlineMessage_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SavePreparedInlineMessageParams{
		UserID: 42,
		Result: nil,
	}
	_, err := SavePreparedInlineMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SavePreparedInlineMessage_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SavePreparedInlineMessageParams{
		UserID: 42,
		Result: nil,
	}
	_, err := SavePreparedInlineMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SavePreparedInlineMessage_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SavePreparedInlineMessageParams{
		UserID: 42,
		Result: nil,
	}
	_, err := SavePreparedInlineMessage(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SavePreparedInlineMessage_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SavePreparedInlineMessageParams{
		UserID: 42,
		Result: nil,
	}
	_, err := SavePreparedInlineMessage(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SavePreparedInlineMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SavePreparedInlineMessage_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SavePreparedInlineMessage(context.Background(), bot, &SavePreparedInlineMessageParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SavePreparedInlineMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SavePreparedInlineMessage_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SavePreparedInlineMessageParams{
		UserID: 42,
		Result: nil,
	}
	_, err := SavePreparedInlineMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SavePreparedInlineMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SavePreparedInlineMessage_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SavePreparedInlineMessageParams{
		UserID: 42,
		Result: nil,
	}
	_, err := SavePreparedInlineMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SavePreparedKeyboardButton_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/savePreparedKeyboardButton")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SavePreparedKeyboardButtonParams{
		UserID: 42,
		Button: KeyboardButton{},
	}
	_, err := SavePreparedKeyboardButton(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SavePreparedKeyboardButton_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SavePreparedKeyboardButtonParams{
		UserID: 42,
		Button: KeyboardButton{},
	}
	_, err := SavePreparedKeyboardButton(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SavePreparedKeyboardButton_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SavePreparedKeyboardButtonParams{
		UserID: 42,
		Button: KeyboardButton{},
	}
	_, err := SavePreparedKeyboardButton(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SavePreparedKeyboardButton_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SavePreparedKeyboardButtonParams{
		UserID: 42,
		Button: KeyboardButton{},
	}
	_, err := SavePreparedKeyboardButton(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SavePreparedKeyboardButton_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SavePreparedKeyboardButtonParams{
		UserID: 42,
		Button: KeyboardButton{},
	}
	_, err := SavePreparedKeyboardButton(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SavePreparedKeyboardButton_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SavePreparedKeyboardButton_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SavePreparedKeyboardButton(context.Background(), bot, &SavePreparedKeyboardButtonParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SavePreparedKeyboardButton_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SavePreparedKeyboardButton_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SavePreparedKeyboardButtonParams{
		UserID: 42,
		Button: KeyboardButton{},
	}
	_, err := SavePreparedKeyboardButton(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SavePreparedKeyboardButton_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SavePreparedKeyboardButton_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SavePreparedKeyboardButtonParams{
		UserID: 42,
		Button: KeyboardButton{},
	}
	_, err := SavePreparedKeyboardButton(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_EditMessageText_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/editMessageText")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageTextParams{}
	_, err := EditMessageText(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_EditMessageText_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageTextParams{}
	_, err := EditMessageText(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_EditMessageText_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageTextParams{}
	_, err := EditMessageText(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_EditMessageText_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageTextParams{}
	_, err := EditMessageText(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_EditMessageText_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageTextParams{}
	_, err := EditMessageText(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_EditMessageText_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditMessageText_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := EditMessageText(context.Background(), bot, &EditMessageTextParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_EditMessageText_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditMessageText_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageTextParams{}
	_, err := EditMessageText(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_EditMessageText_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditMessageText_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageTextParams{}
	_, err := EditMessageText(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_EditMessageCaption_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/editMessageCaption")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageCaptionParams{}
	_, err := EditMessageCaption(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_EditMessageCaption_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageCaptionParams{}
	_, err := EditMessageCaption(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_EditMessageCaption_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageCaptionParams{}
	_, err := EditMessageCaption(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_EditMessageCaption_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageCaptionParams{}
	_, err := EditMessageCaption(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_EditMessageCaption_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageCaptionParams{}
	_, err := EditMessageCaption(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_EditMessageCaption_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditMessageCaption_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := EditMessageCaption(context.Background(), bot, &EditMessageCaptionParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_EditMessageCaption_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditMessageCaption_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageCaptionParams{}
	_, err := EditMessageCaption(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_EditMessageCaption_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditMessageCaption_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageCaptionParams{}
	_, err := EditMessageCaption(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_EditMessageMedia_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/editMessageMedia")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageMediaParams{
		Media: nil,
	}
	_, err := EditMessageMedia(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_EditMessageMedia_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageMediaParams{
		Media: nil,
	}
	_, err := EditMessageMedia(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_EditMessageMedia_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageMediaParams{
		Media: nil,
	}
	_, err := EditMessageMedia(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_EditMessageMedia_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageMediaParams{
		Media: nil,
	}
	_, err := EditMessageMedia(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_EditMessageMedia_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageMediaParams{
		Media: nil,
	}
	_, err := EditMessageMedia(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_EditMessageMedia_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditMessageMedia_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := EditMessageMedia(context.Background(), bot, &EditMessageMediaParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_EditMessageMedia_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditMessageMedia_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageMediaParams{
		Media: nil,
	}
	_, err := EditMessageMedia(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_EditMessageMedia_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditMessageMedia_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageMediaParams{
		Media: nil,
	}
	_, err := EditMessageMedia(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_EditMessageLiveLocation_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/editMessageLiveLocation")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageLiveLocationParams{
		Latitude:  1.0,
		Longitude: 1.0,
	}
	_, err := EditMessageLiveLocation(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_EditMessageLiveLocation_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageLiveLocationParams{
		Latitude:  1.0,
		Longitude: 1.0,
	}
	_, err := EditMessageLiveLocation(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_EditMessageLiveLocation_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageLiveLocationParams{
		Latitude:  1.0,
		Longitude: 1.0,
	}
	_, err := EditMessageLiveLocation(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_EditMessageLiveLocation_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageLiveLocationParams{
		Latitude:  1.0,
		Longitude: 1.0,
	}
	_, err := EditMessageLiveLocation(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_EditMessageLiveLocation_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageLiveLocationParams{
		Latitude:  1.0,
		Longitude: 1.0,
	}
	_, err := EditMessageLiveLocation(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_EditMessageLiveLocation_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditMessageLiveLocation_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := EditMessageLiveLocation(context.Background(), bot, &EditMessageLiveLocationParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_EditMessageLiveLocation_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditMessageLiveLocation_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageLiveLocationParams{
		Latitude:  1.0,
		Longitude: 1.0,
	}
	_, err := EditMessageLiveLocation(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_EditMessageLiveLocation_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditMessageLiveLocation_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageLiveLocationParams{
		Latitude:  1.0,
		Longitude: 1.0,
	}
	_, err := EditMessageLiveLocation(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_StopMessageLiveLocation_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/stopMessageLiveLocation")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &StopMessageLiveLocationParams{}
	_, err := StopMessageLiveLocation(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_StopMessageLiveLocation_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &StopMessageLiveLocationParams{}
	_, err := StopMessageLiveLocation(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_StopMessageLiveLocation_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &StopMessageLiveLocationParams{}
	_, err := StopMessageLiveLocation(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_StopMessageLiveLocation_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &StopMessageLiveLocationParams{}
	_, err := StopMessageLiveLocation(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_StopMessageLiveLocation_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &StopMessageLiveLocationParams{}
	_, err := StopMessageLiveLocation(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_StopMessageLiveLocation_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_StopMessageLiveLocation_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := StopMessageLiveLocation(context.Background(), bot, &StopMessageLiveLocationParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_StopMessageLiveLocation_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_StopMessageLiveLocation_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &StopMessageLiveLocationParams{}
	_, err := StopMessageLiveLocation(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_StopMessageLiveLocation_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_StopMessageLiveLocation_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &StopMessageLiveLocationParams{}
	_, err := StopMessageLiveLocation(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_EditMessageChecklist_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/editMessageChecklist")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageChecklistParams{
		BusinessConnectionID: "test_value",
		ChatID:               ChatIDFromInt(123),
		MessageID:            42,
		Checklist:            InputChecklist{},
	}
	_, err := EditMessageChecklist(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_EditMessageChecklist_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageChecklistParams{
		BusinessConnectionID: "test_value",
		ChatID:               ChatIDFromInt(123),
		MessageID:            42,
		Checklist:            InputChecklist{},
	}
	_, err := EditMessageChecklist(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_EditMessageChecklist_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageChecklistParams{
		BusinessConnectionID: "test_value",
		ChatID:               ChatIDFromInt(123),
		MessageID:            42,
		Checklist:            InputChecklist{},
	}
	_, err := EditMessageChecklist(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_EditMessageChecklist_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageChecklistParams{
		BusinessConnectionID: "test_value",
		ChatID:               ChatIDFromInt(123),
		MessageID:            42,
		Checklist:            InputChecklist{},
	}
	_, err := EditMessageChecklist(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_EditMessageChecklist_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageChecklistParams{
		BusinessConnectionID: "test_value",
		ChatID:               ChatIDFromInt(123),
		MessageID:            42,
		Checklist:            InputChecklist{},
	}
	_, err := EditMessageChecklist(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_EditMessageChecklist_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditMessageChecklist_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := EditMessageChecklist(context.Background(), bot, &EditMessageChecklistParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_EditMessageChecklist_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditMessageChecklist_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageChecklistParams{
		BusinessConnectionID: "test_value",
		ChatID:               ChatIDFromInt(123),
		MessageID:            42,
		Checklist:            InputChecklist{},
	}
	_, err := EditMessageChecklist(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_EditMessageChecklist_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditMessageChecklist_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageChecklistParams{
		BusinessConnectionID: "test_value",
		ChatID:               ChatIDFromInt(123),
		MessageID:            42,
		Checklist:            InputChecklist{},
	}
	_, err := EditMessageChecklist(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_EditMessageReplyMarkup_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/editMessageReplyMarkup")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageReplyMarkupParams{}
	_, err := EditMessageReplyMarkup(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_EditMessageReplyMarkup_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageReplyMarkupParams{}
	_, err := EditMessageReplyMarkup(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_EditMessageReplyMarkup_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageReplyMarkupParams{}
	_, err := EditMessageReplyMarkup(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_EditMessageReplyMarkup_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageReplyMarkupParams{}
	_, err := EditMessageReplyMarkup(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_EditMessageReplyMarkup_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageReplyMarkupParams{}
	_, err := EditMessageReplyMarkup(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_EditMessageReplyMarkup_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditMessageReplyMarkup_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := EditMessageReplyMarkup(context.Background(), bot, &EditMessageReplyMarkupParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_EditMessageReplyMarkup_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditMessageReplyMarkup_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageReplyMarkupParams{}
	_, err := EditMessageReplyMarkup(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_EditMessageReplyMarkup_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditMessageReplyMarkup_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditMessageReplyMarkupParams{}
	_, err := EditMessageReplyMarkup(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_StopPoll_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/stopPoll")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &StopPollParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := StopPoll(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_StopPoll_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &StopPollParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := StopPoll(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_StopPoll_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &StopPollParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := StopPoll(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_StopPoll_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &StopPollParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := StopPoll(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_StopPoll_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &StopPollParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := StopPoll(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_StopPoll_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_StopPoll_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := StopPoll(context.Background(), bot, &StopPollParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_StopPoll_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_StopPoll_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &StopPollParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := StopPoll(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_StopPoll_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_StopPoll_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &StopPollParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := StopPoll(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_ApproveSuggestedPost_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/approveSuggestedPost")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ApproveSuggestedPostParams{
		ChatID:    42,
		MessageID: 42,
	}
	_, err := ApproveSuggestedPost(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_ApproveSuggestedPost_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ApproveSuggestedPostParams{
		ChatID:    42,
		MessageID: 42,
	}
	_, err := ApproveSuggestedPost(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_ApproveSuggestedPost_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ApproveSuggestedPostParams{
		ChatID:    42,
		MessageID: 42,
	}
	_, err := ApproveSuggestedPost(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_ApproveSuggestedPost_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ApproveSuggestedPostParams{
		ChatID:    42,
		MessageID: 42,
	}
	_, err := ApproveSuggestedPost(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_ApproveSuggestedPost_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ApproveSuggestedPostParams{
		ChatID:    42,
		MessageID: 42,
	}
	_, err := ApproveSuggestedPost(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_ApproveSuggestedPost_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ApproveSuggestedPost_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := ApproveSuggestedPost(context.Background(), bot, &ApproveSuggestedPostParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_ApproveSuggestedPost_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ApproveSuggestedPost_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ApproveSuggestedPostParams{
		ChatID:    42,
		MessageID: 42,
	}
	_, err := ApproveSuggestedPost(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_ApproveSuggestedPost_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ApproveSuggestedPost_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ApproveSuggestedPostParams{
		ChatID:    42,
		MessageID: 42,
	}
	_, err := ApproveSuggestedPost(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_DeclineSuggestedPost_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/declineSuggestedPost")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeclineSuggestedPostParams{
		ChatID:    42,
		MessageID: 42,
	}
	_, err := DeclineSuggestedPost(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_DeclineSuggestedPost_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeclineSuggestedPostParams{
		ChatID:    42,
		MessageID: 42,
	}
	_, err := DeclineSuggestedPost(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_DeclineSuggestedPost_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeclineSuggestedPostParams{
		ChatID:    42,
		MessageID: 42,
	}
	_, err := DeclineSuggestedPost(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_DeclineSuggestedPost_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeclineSuggestedPostParams{
		ChatID:    42,
		MessageID: 42,
	}
	_, err := DeclineSuggestedPost(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_DeclineSuggestedPost_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeclineSuggestedPostParams{
		ChatID:    42,
		MessageID: 42,
	}
	_, err := DeclineSuggestedPost(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_DeclineSuggestedPost_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeclineSuggestedPost_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := DeclineSuggestedPost(context.Background(), bot, &DeclineSuggestedPostParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_DeclineSuggestedPost_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeclineSuggestedPost_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeclineSuggestedPostParams{
		ChatID:    42,
		MessageID: 42,
	}
	_, err := DeclineSuggestedPost(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_DeclineSuggestedPost_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeclineSuggestedPost_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeclineSuggestedPostParams{
		ChatID:    42,
		MessageID: 42,
	}
	_, err := DeclineSuggestedPost(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_DeleteMessage_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/deleteMessage")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessageParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := DeleteMessage(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_DeleteMessage_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessageParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := DeleteMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_DeleteMessage_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessageParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := DeleteMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_DeleteMessage_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessageParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := DeleteMessage(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_DeleteMessage_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessageParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := DeleteMessage(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_DeleteMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteMessage_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := DeleteMessage(context.Background(), bot, &DeleteMessageParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteMessage_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessageParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := DeleteMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteMessage_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessageParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := DeleteMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_DeleteMessages_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/deleteMessages")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessagesParams{
		ChatID:     ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := DeleteMessages(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_DeleteMessages_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessagesParams{
		ChatID:     ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := DeleteMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_DeleteMessages_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessagesParams{
		ChatID:     ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := DeleteMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_DeleteMessages_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessagesParams{
		ChatID:     ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := DeleteMessages(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_DeleteMessages_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessagesParams{
		ChatID:     ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := DeleteMessages(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_DeleteMessages_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteMessages_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := DeleteMessages(context.Background(), bot, &DeleteMessagesParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteMessages_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteMessages_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessagesParams{
		ChatID:     ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := DeleteMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteMessages_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteMessages_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessagesParams{
		ChatID:     ChatIDFromInt(123),
		MessageIds: nil,
	}
	_, err := DeleteMessages(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_DeleteMessageReaction_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/deleteMessageReaction")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessageReactionParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := DeleteMessageReaction(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_DeleteMessageReaction_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessageReactionParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := DeleteMessageReaction(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_DeleteMessageReaction_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessageReactionParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := DeleteMessageReaction(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_DeleteMessageReaction_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessageReactionParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := DeleteMessageReaction(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_DeleteMessageReaction_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessageReactionParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := DeleteMessageReaction(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_DeleteMessageReaction_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteMessageReaction_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := DeleteMessageReaction(context.Background(), bot, &DeleteMessageReactionParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteMessageReaction_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteMessageReaction_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessageReactionParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := DeleteMessageReaction(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteMessageReaction_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteMessageReaction_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteMessageReactionParams{
		ChatID:    ChatIDFromInt(123),
		MessageID: 42,
	}
	_, err := DeleteMessageReaction(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_DeleteAllMessageReactions_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/deleteAllMessageReactions")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteAllMessageReactionsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteAllMessageReactions(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_DeleteAllMessageReactions_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteAllMessageReactionsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteAllMessageReactions(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_DeleteAllMessageReactions_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteAllMessageReactionsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteAllMessageReactions(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_DeleteAllMessageReactions_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteAllMessageReactionsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteAllMessageReactions(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_DeleteAllMessageReactions_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteAllMessageReactionsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteAllMessageReactions(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_DeleteAllMessageReactions_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteAllMessageReactions_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := DeleteAllMessageReactions(context.Background(), bot, &DeleteAllMessageReactionsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteAllMessageReactions_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteAllMessageReactions_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteAllMessageReactionsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteAllMessageReactions(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteAllMessageReactions_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteAllMessageReactions_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteAllMessageReactionsParams{
		ChatID: ChatIDFromInt(123),
	}
	_, err := DeleteAllMessageReactions(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendSticker_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendSticker")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendStickerParams{
		ChatID:  ChatIDFromInt(123),
		Sticker: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendSticker(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendSticker_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendStickerParams{
		ChatID:  ChatIDFromInt(123),
		Sticker: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendSticker(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendSticker_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendStickerParams{
		ChatID:  ChatIDFromInt(123),
		Sticker: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendSticker(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendSticker_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendStickerParams{
		ChatID:  ChatIDFromInt(123),
		Sticker: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendSticker(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendSticker_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendStickerParams{
		ChatID:  ChatIDFromInt(123),
		Sticker: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendSticker(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendSticker_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendSticker_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendSticker(context.Background(), bot, &SendStickerParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendSticker_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendSticker_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendStickerParams{
		ChatID:  ChatIDFromInt(123),
		Sticker: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendSticker(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendSticker_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendSticker_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendStickerParams{
		ChatID:  ChatIDFromInt(123),
		Sticker: &InputFile{PathOrID: "file_id_test"},
	}
	_, err := SendSticker(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetStickerSet_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getStickerSet")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetStickerSetParams{
		Name: "test_value",
	}
	_, err := GetStickerSet(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetStickerSet_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetStickerSetParams{
		Name: "test_value",
	}
	_, err := GetStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetStickerSet_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetStickerSetParams{
		Name: "test_value",
	}
	_, err := GetStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetStickerSet_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetStickerSetParams{
		Name: "test_value",
	}
	_, err := GetStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetStickerSet_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetStickerSetParams{
		Name: "test_value",
	}
	_, err := GetStickerSet(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetStickerSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetStickerSet_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetStickerSet(context.Background(), bot, &GetStickerSetParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetStickerSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetStickerSet_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetStickerSetParams{
		Name: "test_value",
	}
	_, err := GetStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetStickerSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetStickerSet_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetStickerSetParams{
		Name: "test_value",
	}
	_, err := GetStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetCustomEmojiStickers_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getCustomEmojiStickers")
	})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetCustomEmojiStickersParams{
		CustomEmojiIds: nil,
	}
	_, err := GetCustomEmojiStickers(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetCustomEmojiStickers_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetCustomEmojiStickersParams{
		CustomEmojiIds: nil,
	}
	_, err := GetCustomEmojiStickers(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetCustomEmojiStickers_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetCustomEmojiStickersParams{
		CustomEmojiIds: nil,
	}
	_, err := GetCustomEmojiStickers(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetCustomEmojiStickers_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetCustomEmojiStickersParams{
		CustomEmojiIds: nil,
	}
	_, err := GetCustomEmojiStickers(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetCustomEmojiStickers_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetCustomEmojiStickersParams{
		CustomEmojiIds: nil,
	}
	_, err := GetCustomEmojiStickers(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetCustomEmojiStickers_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetCustomEmojiStickers_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetCustomEmojiStickers(context.Background(), bot, &GetCustomEmojiStickersParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetCustomEmojiStickers_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetCustomEmojiStickers_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetCustomEmojiStickersParams{
		CustomEmojiIds: nil,
	}
	_, err := GetCustomEmojiStickers(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetCustomEmojiStickers_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetCustomEmojiStickers_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetCustomEmojiStickersParams{
		CustomEmojiIds: nil,
	}
	_, err := GetCustomEmojiStickers(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_UploadStickerFile_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/uploadStickerFile")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UploadStickerFileParams{
		UserID:        42,
		Sticker:       &InputFile{PathOrID: "file_id_test"},
		StickerFormat: InputStickerFormatStatic,
	}
	_, err := UploadStickerFile(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_UploadStickerFile_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UploadStickerFileParams{
		UserID:        42,
		Sticker:       &InputFile{PathOrID: "file_id_test"},
		StickerFormat: InputStickerFormatStatic,
	}
	_, err := UploadStickerFile(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_UploadStickerFile_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UploadStickerFileParams{
		UserID:        42,
		Sticker:       &InputFile{PathOrID: "file_id_test"},
		StickerFormat: InputStickerFormatStatic,
	}
	_, err := UploadStickerFile(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_UploadStickerFile_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UploadStickerFileParams{
		UserID:        42,
		Sticker:       &InputFile{PathOrID: "file_id_test"},
		StickerFormat: InputStickerFormatStatic,
	}
	_, err := UploadStickerFile(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_UploadStickerFile_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UploadStickerFileParams{
		UserID:        42,
		Sticker:       &InputFile{PathOrID: "file_id_test"},
		StickerFormat: InputStickerFormatStatic,
	}
	_, err := UploadStickerFile(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_UploadStickerFile_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UploadStickerFile_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := UploadStickerFile(context.Background(), bot, &UploadStickerFileParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_UploadStickerFile_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UploadStickerFile_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UploadStickerFileParams{
		UserID:        42,
		Sticker:       &InputFile{PathOrID: "file_id_test"},
		StickerFormat: InputStickerFormatStatic,
	}
	_, err := UploadStickerFile(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_UploadStickerFile_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UploadStickerFile_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &UploadStickerFileParams{
		UserID:        42,
		Sticker:       &InputFile{PathOrID: "file_id_test"},
		StickerFormat: InputStickerFormatStatic,
	}
	_, err := UploadStickerFile(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_CreateNewStickerSet_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/createNewStickerSet")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateNewStickerSetParams{
		UserID:   42,
		Name:     "test_value",
		Title:    "test_value",
		Stickers: nil,
	}
	_, err := CreateNewStickerSet(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_CreateNewStickerSet_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateNewStickerSetParams{
		UserID:   42,
		Name:     "test_value",
		Title:    "test_value",
		Stickers: nil,
	}
	_, err := CreateNewStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_CreateNewStickerSet_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateNewStickerSetParams{
		UserID:   42,
		Name:     "test_value",
		Title:    "test_value",
		Stickers: nil,
	}
	_, err := CreateNewStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_CreateNewStickerSet_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateNewStickerSetParams{
		UserID:   42,
		Name:     "test_value",
		Title:    "test_value",
		Stickers: nil,
	}
	_, err := CreateNewStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_CreateNewStickerSet_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateNewStickerSetParams{
		UserID:   42,
		Name:     "test_value",
		Title:    "test_value",
		Stickers: nil,
	}
	_, err := CreateNewStickerSet(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_CreateNewStickerSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CreateNewStickerSet_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := CreateNewStickerSet(context.Background(), bot, &CreateNewStickerSetParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_CreateNewStickerSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CreateNewStickerSet_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateNewStickerSetParams{
		UserID:   42,
		Name:     "test_value",
		Title:    "test_value",
		Stickers: nil,
	}
	_, err := CreateNewStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_CreateNewStickerSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CreateNewStickerSet_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateNewStickerSetParams{
		UserID:   42,
		Name:     "test_value",
		Title:    "test_value",
		Stickers: nil,
	}
	_, err := CreateNewStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_AddStickerToSet_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/addStickerToSet")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AddStickerToSetParams{
		UserID:  42,
		Name:    "test_value",
		Sticker: InputSticker{},
	}
	_, err := AddStickerToSet(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_AddStickerToSet_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AddStickerToSetParams{
		UserID:  42,
		Name:    "test_value",
		Sticker: InputSticker{},
	}
	_, err := AddStickerToSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_AddStickerToSet_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AddStickerToSetParams{
		UserID:  42,
		Name:    "test_value",
		Sticker: InputSticker{},
	}
	_, err := AddStickerToSet(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_AddStickerToSet_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AddStickerToSetParams{
		UserID:  42,
		Name:    "test_value",
		Sticker: InputSticker{},
	}
	_, err := AddStickerToSet(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_AddStickerToSet_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AddStickerToSetParams{
		UserID:  42,
		Name:    "test_value",
		Sticker: InputSticker{},
	}
	_, err := AddStickerToSet(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_AddStickerToSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_AddStickerToSet_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := AddStickerToSet(context.Background(), bot, &AddStickerToSetParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_AddStickerToSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_AddStickerToSet_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AddStickerToSetParams{
		UserID:  42,
		Name:    "test_value",
		Sticker: InputSticker{},
	}
	_, err := AddStickerToSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_AddStickerToSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_AddStickerToSet_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AddStickerToSetParams{
		UserID:  42,
		Name:    "test_value",
		Sticker: InputSticker{},
	}
	_, err := AddStickerToSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetStickerPositionInSet_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setStickerPositionInSet")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerPositionInSetParams{
		Sticker:  "test_value",
		Position: 42,
	}
	_, err := SetStickerPositionInSet(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetStickerPositionInSet_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerPositionInSetParams{
		Sticker:  "test_value",
		Position: 42,
	}
	_, err := SetStickerPositionInSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetStickerPositionInSet_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerPositionInSetParams{
		Sticker:  "test_value",
		Position: 42,
	}
	_, err := SetStickerPositionInSet(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetStickerPositionInSet_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerPositionInSetParams{
		Sticker:  "test_value",
		Position: 42,
	}
	_, err := SetStickerPositionInSet(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetStickerPositionInSet_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerPositionInSetParams{
		Sticker:  "test_value",
		Position: 42,
	}
	_, err := SetStickerPositionInSet(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetStickerPositionInSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetStickerPositionInSet_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetStickerPositionInSet(context.Background(), bot, &SetStickerPositionInSetParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetStickerPositionInSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetStickerPositionInSet_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerPositionInSetParams{
		Sticker:  "test_value",
		Position: 42,
	}
	_, err := SetStickerPositionInSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetStickerPositionInSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetStickerPositionInSet_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerPositionInSetParams{
		Sticker:  "test_value",
		Position: 42,
	}
	_, err := SetStickerPositionInSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_DeleteStickerFromSet_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/deleteStickerFromSet")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStickerFromSetParams{
		Sticker: "test_value",
	}
	_, err := DeleteStickerFromSet(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_DeleteStickerFromSet_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStickerFromSetParams{
		Sticker: "test_value",
	}
	_, err := DeleteStickerFromSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_DeleteStickerFromSet_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStickerFromSetParams{
		Sticker: "test_value",
	}
	_, err := DeleteStickerFromSet(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_DeleteStickerFromSet_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStickerFromSetParams{
		Sticker: "test_value",
	}
	_, err := DeleteStickerFromSet(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_DeleteStickerFromSet_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStickerFromSetParams{
		Sticker: "test_value",
	}
	_, err := DeleteStickerFromSet(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_DeleteStickerFromSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteStickerFromSet_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := DeleteStickerFromSet(context.Background(), bot, &DeleteStickerFromSetParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteStickerFromSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteStickerFromSet_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStickerFromSetParams{
		Sticker: "test_value",
	}
	_, err := DeleteStickerFromSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteStickerFromSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteStickerFromSet_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStickerFromSetParams{
		Sticker: "test_value",
	}
	_, err := DeleteStickerFromSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_ReplaceStickerInSet_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/replaceStickerInSet")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReplaceStickerInSetParams{
		UserID:     42,
		Name:       "test_value",
		OldSticker: "test_value",
		Sticker:    InputSticker{},
	}
	_, err := ReplaceStickerInSet(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_ReplaceStickerInSet_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReplaceStickerInSetParams{
		UserID:     42,
		Name:       "test_value",
		OldSticker: "test_value",
		Sticker:    InputSticker{},
	}
	_, err := ReplaceStickerInSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_ReplaceStickerInSet_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReplaceStickerInSetParams{
		UserID:     42,
		Name:       "test_value",
		OldSticker: "test_value",
		Sticker:    InputSticker{},
	}
	_, err := ReplaceStickerInSet(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_ReplaceStickerInSet_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReplaceStickerInSetParams{
		UserID:     42,
		Name:       "test_value",
		OldSticker: "test_value",
		Sticker:    InputSticker{},
	}
	_, err := ReplaceStickerInSet(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_ReplaceStickerInSet_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReplaceStickerInSetParams{
		UserID:     42,
		Name:       "test_value",
		OldSticker: "test_value",
		Sticker:    InputSticker{},
	}
	_, err := ReplaceStickerInSet(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_ReplaceStickerInSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ReplaceStickerInSet_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := ReplaceStickerInSet(context.Background(), bot, &ReplaceStickerInSetParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_ReplaceStickerInSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ReplaceStickerInSet_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReplaceStickerInSetParams{
		UserID:     42,
		Name:       "test_value",
		OldSticker: "test_value",
		Sticker:    InputSticker{},
	}
	_, err := ReplaceStickerInSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_ReplaceStickerInSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ReplaceStickerInSet_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &ReplaceStickerInSetParams{
		UserID:     42,
		Name:       "test_value",
		OldSticker: "test_value",
		Sticker:    InputSticker{},
	}
	_, err := ReplaceStickerInSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetStickerEmojiList_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setStickerEmojiList")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerEmojiListParams{
		Sticker:   "test_value",
		EmojiList: nil,
	}
	_, err := SetStickerEmojiList(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetStickerEmojiList_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerEmojiListParams{
		Sticker:   "test_value",
		EmojiList: nil,
	}
	_, err := SetStickerEmojiList(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetStickerEmojiList_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerEmojiListParams{
		Sticker:   "test_value",
		EmojiList: nil,
	}
	_, err := SetStickerEmojiList(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetStickerEmojiList_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerEmojiListParams{
		Sticker:   "test_value",
		EmojiList: nil,
	}
	_, err := SetStickerEmojiList(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetStickerEmojiList_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerEmojiListParams{
		Sticker:   "test_value",
		EmojiList: nil,
	}
	_, err := SetStickerEmojiList(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetStickerEmojiList_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetStickerEmojiList_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetStickerEmojiList(context.Background(), bot, &SetStickerEmojiListParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetStickerEmojiList_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetStickerEmojiList_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerEmojiListParams{
		Sticker:   "test_value",
		EmojiList: nil,
	}
	_, err := SetStickerEmojiList(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetStickerEmojiList_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetStickerEmojiList_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerEmojiListParams{
		Sticker:   "test_value",
		EmojiList: nil,
	}
	_, err := SetStickerEmojiList(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetStickerKeywords_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setStickerKeywords")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerKeywordsParams{
		Sticker: "test_value",
	}
	_, err := SetStickerKeywords(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetStickerKeywords_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerKeywordsParams{
		Sticker: "test_value",
	}
	_, err := SetStickerKeywords(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetStickerKeywords_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerKeywordsParams{
		Sticker: "test_value",
	}
	_, err := SetStickerKeywords(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetStickerKeywords_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerKeywordsParams{
		Sticker: "test_value",
	}
	_, err := SetStickerKeywords(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetStickerKeywords_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerKeywordsParams{
		Sticker: "test_value",
	}
	_, err := SetStickerKeywords(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetStickerKeywords_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetStickerKeywords_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetStickerKeywords(context.Background(), bot, &SetStickerKeywordsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetStickerKeywords_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetStickerKeywords_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerKeywordsParams{
		Sticker: "test_value",
	}
	_, err := SetStickerKeywords(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetStickerKeywords_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetStickerKeywords_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerKeywordsParams{
		Sticker: "test_value",
	}
	_, err := SetStickerKeywords(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetStickerMaskPosition_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setStickerMaskPosition")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerMaskPositionParams{
		Sticker: "test_value",
	}
	_, err := SetStickerMaskPosition(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetStickerMaskPosition_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerMaskPositionParams{
		Sticker: "test_value",
	}
	_, err := SetStickerMaskPosition(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetStickerMaskPosition_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerMaskPositionParams{
		Sticker: "test_value",
	}
	_, err := SetStickerMaskPosition(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetStickerMaskPosition_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerMaskPositionParams{
		Sticker: "test_value",
	}
	_, err := SetStickerMaskPosition(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetStickerMaskPosition_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerMaskPositionParams{
		Sticker: "test_value",
	}
	_, err := SetStickerMaskPosition(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetStickerMaskPosition_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetStickerMaskPosition_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetStickerMaskPosition(context.Background(), bot, &SetStickerMaskPositionParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetStickerMaskPosition_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetStickerMaskPosition_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerMaskPositionParams{
		Sticker: "test_value",
	}
	_, err := SetStickerMaskPosition(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetStickerMaskPosition_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetStickerMaskPosition_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerMaskPositionParams{
		Sticker: "test_value",
	}
	_, err := SetStickerMaskPosition(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetStickerSetTitle_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setStickerSetTitle")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerSetTitleParams{
		Name:  "test_value",
		Title: "test_value",
	}
	_, err := SetStickerSetTitle(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetStickerSetTitle_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerSetTitleParams{
		Name:  "test_value",
		Title: "test_value",
	}
	_, err := SetStickerSetTitle(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetStickerSetTitle_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerSetTitleParams{
		Name:  "test_value",
		Title: "test_value",
	}
	_, err := SetStickerSetTitle(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetStickerSetTitle_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerSetTitleParams{
		Name:  "test_value",
		Title: "test_value",
	}
	_, err := SetStickerSetTitle(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetStickerSetTitle_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerSetTitleParams{
		Name:  "test_value",
		Title: "test_value",
	}
	_, err := SetStickerSetTitle(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetStickerSetTitle_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetStickerSetTitle_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetStickerSetTitle(context.Background(), bot, &SetStickerSetTitleParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetStickerSetTitle_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetStickerSetTitle_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerSetTitleParams{
		Name:  "test_value",
		Title: "test_value",
	}
	_, err := SetStickerSetTitle(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetStickerSetTitle_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetStickerSetTitle_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerSetTitleParams{
		Name:  "test_value",
		Title: "test_value",
	}
	_, err := SetStickerSetTitle(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetStickerSetThumbnail_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setStickerSetThumbnail")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerSetThumbnailParams{
		Name:   "test_value",
		UserID: 42,
		Format: InputStickerFormatStatic,
	}
	_, err := SetStickerSetThumbnail(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetStickerSetThumbnail_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerSetThumbnailParams{
		Name:   "test_value",
		UserID: 42,
		Format: InputStickerFormatStatic,
	}
	_, err := SetStickerSetThumbnail(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetStickerSetThumbnail_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerSetThumbnailParams{
		Name:   "test_value",
		UserID: 42,
		Format: InputStickerFormatStatic,
	}
	_, err := SetStickerSetThumbnail(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetStickerSetThumbnail_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerSetThumbnailParams{
		Name:   "test_value",
		UserID: 42,
		Format: InputStickerFormatStatic,
	}
	_, err := SetStickerSetThumbnail(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetStickerSetThumbnail_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerSetThumbnailParams{
		Name:   "test_value",
		UserID: 42,
		Format: InputStickerFormatStatic,
	}
	_, err := SetStickerSetThumbnail(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetStickerSetThumbnail_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetStickerSetThumbnail_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetStickerSetThumbnail(context.Background(), bot, &SetStickerSetThumbnailParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetStickerSetThumbnail_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetStickerSetThumbnail_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerSetThumbnailParams{
		Name:   "test_value",
		UserID: 42,
		Format: InputStickerFormatStatic,
	}
	_, err := SetStickerSetThumbnail(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetStickerSetThumbnail_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetStickerSetThumbnail_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetStickerSetThumbnailParams{
		Name:   "test_value",
		UserID: 42,
		Format: InputStickerFormatStatic,
	}
	_, err := SetStickerSetThumbnail(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetCustomEmojiStickerSetThumbnail_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setCustomEmojiStickerSetThumbnail")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetCustomEmojiStickerSetThumbnailParams{
		Name: "test_value",
	}
	_, err := SetCustomEmojiStickerSetThumbnail(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetCustomEmojiStickerSetThumbnail_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetCustomEmojiStickerSetThumbnailParams{
		Name: "test_value",
	}
	_, err := SetCustomEmojiStickerSetThumbnail(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetCustomEmojiStickerSetThumbnail_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetCustomEmojiStickerSetThumbnailParams{
		Name: "test_value",
	}
	_, err := SetCustomEmojiStickerSetThumbnail(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetCustomEmojiStickerSetThumbnail_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetCustomEmojiStickerSetThumbnailParams{
		Name: "test_value",
	}
	_, err := SetCustomEmojiStickerSetThumbnail(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetCustomEmojiStickerSetThumbnail_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetCustomEmojiStickerSetThumbnailParams{
		Name: "test_value",
	}
	_, err := SetCustomEmojiStickerSetThumbnail(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetCustomEmojiStickerSetThumbnail_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetCustomEmojiStickerSetThumbnail_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetCustomEmojiStickerSetThumbnail(context.Background(), bot, &SetCustomEmojiStickerSetThumbnailParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetCustomEmojiStickerSetThumbnail_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetCustomEmojiStickerSetThumbnail_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetCustomEmojiStickerSetThumbnailParams{
		Name: "test_value",
	}
	_, err := SetCustomEmojiStickerSetThumbnail(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetCustomEmojiStickerSetThumbnail_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetCustomEmojiStickerSetThumbnail_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetCustomEmojiStickerSetThumbnailParams{
		Name: "test_value",
	}
	_, err := SetCustomEmojiStickerSetThumbnail(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_DeleteStickerSet_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/deleteStickerSet")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStickerSetParams{
		Name: "test_value",
	}
	_, err := DeleteStickerSet(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_DeleteStickerSet_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStickerSetParams{
		Name: "test_value",
	}
	_, err := DeleteStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_DeleteStickerSet_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStickerSetParams{
		Name: "test_value",
	}
	_, err := DeleteStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_DeleteStickerSet_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStickerSetParams{
		Name: "test_value",
	}
	_, err := DeleteStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_DeleteStickerSet_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStickerSetParams{
		Name: "test_value",
	}
	_, err := DeleteStickerSet(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_DeleteStickerSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteStickerSet_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := DeleteStickerSet(context.Background(), bot, &DeleteStickerSetParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteStickerSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteStickerSet_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStickerSetParams{
		Name: "test_value",
	}
	_, err := DeleteStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_DeleteStickerSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteStickerSet_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &DeleteStickerSetParams{
		Name: "test_value",
	}
	_, err := DeleteStickerSet(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendRichMessage_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendRichMessage")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendRichMessageParams{
		ChatID:      ChatIDFromInt(123),
		RichMessage: InputRichMessage{},
	}
	_, err := SendRichMessage(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendRichMessage_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendRichMessageParams{
		ChatID:      ChatIDFromInt(123),
		RichMessage: InputRichMessage{},
	}
	_, err := SendRichMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendRichMessage_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendRichMessageParams{
		ChatID:      ChatIDFromInt(123),
		RichMessage: InputRichMessage{},
	}
	_, err := SendRichMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendRichMessage_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendRichMessageParams{
		ChatID:      ChatIDFromInt(123),
		RichMessage: InputRichMessage{},
	}
	_, err := SendRichMessage(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendRichMessage_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendRichMessageParams{
		ChatID:      ChatIDFromInt(123),
		RichMessage: InputRichMessage{},
	}
	_, err := SendRichMessage(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendRichMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendRichMessage_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendRichMessage(context.Background(), bot, &SendRichMessageParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendRichMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendRichMessage_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendRichMessageParams{
		ChatID:      ChatIDFromInt(123),
		RichMessage: InputRichMessage{},
	}
	_, err := SendRichMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendRichMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendRichMessage_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendRichMessageParams{
		ChatID:      ChatIDFromInt(123),
		RichMessage: InputRichMessage{},
	}
	_, err := SendRichMessage(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendRichMessageDraft_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendRichMessageDraft")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendRichMessageDraftParams{
		ChatID:      42,
		DraftID:     42,
		RichMessage: InputRichMessage{},
	}
	_, err := SendRichMessageDraft(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendRichMessageDraft_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendRichMessageDraftParams{
		ChatID:      42,
		DraftID:     42,
		RichMessage: InputRichMessage{},
	}
	_, err := SendRichMessageDraft(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendRichMessageDraft_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendRichMessageDraftParams{
		ChatID:      42,
		DraftID:     42,
		RichMessage: InputRichMessage{},
	}
	_, err := SendRichMessageDraft(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendRichMessageDraft_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendRichMessageDraftParams{
		ChatID:      42,
		DraftID:     42,
		RichMessage: InputRichMessage{},
	}
	_, err := SendRichMessageDraft(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendRichMessageDraft_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendRichMessageDraftParams{
		ChatID:      42,
		DraftID:     42,
		RichMessage: InputRichMessage{},
	}
	_, err := SendRichMessageDraft(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendRichMessageDraft_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendRichMessageDraft_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendRichMessageDraft(context.Background(), bot, &SendRichMessageDraftParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendRichMessageDraft_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendRichMessageDraft_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendRichMessageDraftParams{
		ChatID:      42,
		DraftID:     42,
		RichMessage: InputRichMessage{},
	}
	_, err := SendRichMessageDraft(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendRichMessageDraft_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendRichMessageDraft_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendRichMessageDraftParams{
		ChatID:      42,
		DraftID:     42,
		RichMessage: InputRichMessage{},
	}
	_, err := SendRichMessageDraft(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_AnswerInlineQuery_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/answerInlineQuery")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerInlineQueryParams{
		InlineQueryID: "test_value",
		Results:       nil,
	}
	_, err := AnswerInlineQuery(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_AnswerInlineQuery_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerInlineQueryParams{
		InlineQueryID: "test_value",
		Results:       nil,
	}
	_, err := AnswerInlineQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_AnswerInlineQuery_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerInlineQueryParams{
		InlineQueryID: "test_value",
		Results:       nil,
	}
	_, err := AnswerInlineQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_AnswerInlineQuery_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerInlineQueryParams{
		InlineQueryID: "test_value",
		Results:       nil,
	}
	_, err := AnswerInlineQuery(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_AnswerInlineQuery_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerInlineQueryParams{
		InlineQueryID: "test_value",
		Results:       nil,
	}
	_, err := AnswerInlineQuery(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_AnswerInlineQuery_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_AnswerInlineQuery_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := AnswerInlineQuery(context.Background(), bot, &AnswerInlineQueryParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_AnswerInlineQuery_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_AnswerInlineQuery_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerInlineQueryParams{
		InlineQueryID: "test_value",
		Results:       nil,
	}
	_, err := AnswerInlineQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_AnswerInlineQuery_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_AnswerInlineQuery_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerInlineQueryParams{
		InlineQueryID: "test_value",
		Results:       nil,
	}
	_, err := AnswerInlineQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendInvoice_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendInvoice")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendInvoiceParams{
		ChatID:      ChatIDFromInt(123),
		Title:       "test_value",
		Description: "test_value",
		Payload:     "test_value",
		Currency:    "test_value",
		Prices:      nil,
	}
	_, err := SendInvoice(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendInvoice_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendInvoiceParams{
		ChatID:      ChatIDFromInt(123),
		Title:       "test_value",
		Description: "test_value",
		Payload:     "test_value",
		Currency:    "test_value",
		Prices:      nil,
	}
	_, err := SendInvoice(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendInvoice_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendInvoiceParams{
		ChatID:      ChatIDFromInt(123),
		Title:       "test_value",
		Description: "test_value",
		Payload:     "test_value",
		Currency:    "test_value",
		Prices:      nil,
	}
	_, err := SendInvoice(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendInvoice_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendInvoiceParams{
		ChatID:      ChatIDFromInt(123),
		Title:       "test_value",
		Description: "test_value",
		Payload:     "test_value",
		Currency:    "test_value",
		Prices:      nil,
	}
	_, err := SendInvoice(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendInvoice_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendInvoiceParams{
		ChatID:      ChatIDFromInt(123),
		Title:       "test_value",
		Description: "test_value",
		Payload:     "test_value",
		Currency:    "test_value",
		Prices:      nil,
	}
	_, err := SendInvoice(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendInvoice_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendInvoice_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendInvoice(context.Background(), bot, &SendInvoiceParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendInvoice_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendInvoice_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendInvoiceParams{
		ChatID:      ChatIDFromInt(123),
		Title:       "test_value",
		Description: "test_value",
		Payload:     "test_value",
		Currency:    "test_value",
		Prices:      nil,
	}
	_, err := SendInvoice(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendInvoice_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendInvoice_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendInvoiceParams{
		ChatID:      ChatIDFromInt(123),
		Title:       "test_value",
		Description: "test_value",
		Payload:     "test_value",
		Currency:    "test_value",
		Prices:      nil,
	}
	_, err := SendInvoice(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_CreateInvoiceLink_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/createInvoiceLink")
	})).Return(genTestResp(200, `{"ok":true,"result":""}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateInvoiceLinkParams{
		Title:       "test_value",
		Description: "test_value",
		Payload:     "test_value",
		Currency:    "test_value",
		Prices:      nil,
	}
	_, err := CreateInvoiceLink(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_CreateInvoiceLink_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateInvoiceLinkParams{
		Title:       "test_value",
		Description: "test_value",
		Payload:     "test_value",
		Currency:    "test_value",
		Prices:      nil,
	}
	_, err := CreateInvoiceLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_CreateInvoiceLink_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateInvoiceLinkParams{
		Title:       "test_value",
		Description: "test_value",
		Payload:     "test_value",
		Currency:    "test_value",
		Prices:      nil,
	}
	_, err := CreateInvoiceLink(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_CreateInvoiceLink_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateInvoiceLinkParams{
		Title:       "test_value",
		Description: "test_value",
		Payload:     "test_value",
		Currency:    "test_value",
		Prices:      nil,
	}
	_, err := CreateInvoiceLink(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_CreateInvoiceLink_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateInvoiceLinkParams{
		Title:       "test_value",
		Description: "test_value",
		Payload:     "test_value",
		Currency:    "test_value",
		Prices:      nil,
	}
	_, err := CreateInvoiceLink(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_CreateInvoiceLink_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CreateInvoiceLink_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := CreateInvoiceLink(context.Background(), bot, &CreateInvoiceLinkParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_CreateInvoiceLink_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CreateInvoiceLink_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateInvoiceLinkParams{
		Title:       "test_value",
		Description: "test_value",
		Payload:     "test_value",
		Currency:    "test_value",
		Prices:      nil,
	}
	_, err := CreateInvoiceLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_CreateInvoiceLink_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CreateInvoiceLink_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &CreateInvoiceLinkParams{
		Title:       "test_value",
		Description: "test_value",
		Payload:     "test_value",
		Currency:    "test_value",
		Prices:      nil,
	}
	_, err := CreateInvoiceLink(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_AnswerShippingQuery_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/answerShippingQuery")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerShippingQueryParams{
		ShippingQueryID: "test_value",
		Ok:              true,
	}
	_, err := AnswerShippingQuery(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_AnswerShippingQuery_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerShippingQueryParams{
		ShippingQueryID: "test_value",
		Ok:              true,
	}
	_, err := AnswerShippingQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_AnswerShippingQuery_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerShippingQueryParams{
		ShippingQueryID: "test_value",
		Ok:              true,
	}
	_, err := AnswerShippingQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_AnswerShippingQuery_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerShippingQueryParams{
		ShippingQueryID: "test_value",
		Ok:              true,
	}
	_, err := AnswerShippingQuery(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_AnswerShippingQuery_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerShippingQueryParams{
		ShippingQueryID: "test_value",
		Ok:              true,
	}
	_, err := AnswerShippingQuery(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_AnswerShippingQuery_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_AnswerShippingQuery_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := AnswerShippingQuery(context.Background(), bot, &AnswerShippingQueryParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_AnswerShippingQuery_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_AnswerShippingQuery_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerShippingQueryParams{
		ShippingQueryID: "test_value",
		Ok:              true,
	}
	_, err := AnswerShippingQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_AnswerShippingQuery_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_AnswerShippingQuery_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerShippingQueryParams{
		ShippingQueryID: "test_value",
		Ok:              true,
	}
	_, err := AnswerShippingQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_AnswerPreCheckoutQuery_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/answerPreCheckoutQuery")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerPreCheckoutQueryParams{
		PreCheckoutQueryID: "test_value",
		Ok:                 true,
	}
	_, err := AnswerPreCheckoutQuery(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_AnswerPreCheckoutQuery_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerPreCheckoutQueryParams{
		PreCheckoutQueryID: "test_value",
		Ok:                 true,
	}
	_, err := AnswerPreCheckoutQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_AnswerPreCheckoutQuery_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerPreCheckoutQueryParams{
		PreCheckoutQueryID: "test_value",
		Ok:                 true,
	}
	_, err := AnswerPreCheckoutQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_AnswerPreCheckoutQuery_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerPreCheckoutQueryParams{
		PreCheckoutQueryID: "test_value",
		Ok:                 true,
	}
	_, err := AnswerPreCheckoutQuery(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_AnswerPreCheckoutQuery_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerPreCheckoutQueryParams{
		PreCheckoutQueryID: "test_value",
		Ok:                 true,
	}
	_, err := AnswerPreCheckoutQuery(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_AnswerPreCheckoutQuery_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_AnswerPreCheckoutQuery_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := AnswerPreCheckoutQuery(context.Background(), bot, &AnswerPreCheckoutQueryParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_AnswerPreCheckoutQuery_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_AnswerPreCheckoutQuery_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerPreCheckoutQueryParams{
		PreCheckoutQueryID: "test_value",
		Ok:                 true,
	}
	_, err := AnswerPreCheckoutQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_AnswerPreCheckoutQuery_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_AnswerPreCheckoutQuery_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &AnswerPreCheckoutQueryParams{
		PreCheckoutQueryID: "test_value",
		Ok:                 true,
	}
	_, err := AnswerPreCheckoutQuery(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetMyStarBalance_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getMyStarBalance")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetMyStarBalance(context.Background(), bot, &GetMyStarBalanceParams{})
	require.NoError(t, err)
}

func Test_GetMyStarBalance_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetMyStarBalance(context.Background(), bot, &GetMyStarBalanceParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetMyStarBalance_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetMyStarBalance(context.Background(), bot, &GetMyStarBalanceParams{})
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetMyStarBalance_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetMyStarBalance(context.Background(), bot, &GetMyStarBalanceParams{})
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetMyStarBalance_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetMyStarBalance(ctx, bot, &GetMyStarBalanceParams{})
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetMyStarBalance_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetMyStarBalance_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetMyStarBalance(context.Background(), bot, &GetMyStarBalanceParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetMyStarBalance_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetMyStarBalance_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetMyStarBalance(context.Background(), bot, &GetMyStarBalanceParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetMyStarBalance_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetMyStarBalance_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	_, err := GetMyStarBalance(context.Background(), bot, &GetMyStarBalanceParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetStarTransactions_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getStarTransactions")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetStarTransactionsParams{}
	_, err := GetStarTransactions(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetStarTransactions_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetStarTransactionsParams{}
	_, err := GetStarTransactions(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetStarTransactions_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetStarTransactionsParams{}
	_, err := GetStarTransactions(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetStarTransactions_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetStarTransactionsParams{}
	_, err := GetStarTransactions(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetStarTransactions_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetStarTransactionsParams{}
	_, err := GetStarTransactions(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetStarTransactions_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetStarTransactions_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetStarTransactions(context.Background(), bot, &GetStarTransactionsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetStarTransactions_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetStarTransactions_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetStarTransactionsParams{}
	_, err := GetStarTransactions(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetStarTransactions_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetStarTransactions_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetStarTransactionsParams{}
	_, err := GetStarTransactions(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_RefundStarPayment_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/refundStarPayment")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RefundStarPaymentParams{
		UserID:                  42,
		TelegramPaymentChargeID: "test_value",
	}
	_, err := RefundStarPayment(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_RefundStarPayment_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RefundStarPaymentParams{
		UserID:                  42,
		TelegramPaymentChargeID: "test_value",
	}
	_, err := RefundStarPayment(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_RefundStarPayment_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RefundStarPaymentParams{
		UserID:                  42,
		TelegramPaymentChargeID: "test_value",
	}
	_, err := RefundStarPayment(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_RefundStarPayment_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RefundStarPaymentParams{
		UserID:                  42,
		TelegramPaymentChargeID: "test_value",
	}
	_, err := RefundStarPayment(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_RefundStarPayment_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RefundStarPaymentParams{
		UserID:                  42,
		TelegramPaymentChargeID: "test_value",
	}
	_, err := RefundStarPayment(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_RefundStarPayment_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_RefundStarPayment_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := RefundStarPayment(context.Background(), bot, &RefundStarPaymentParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_RefundStarPayment_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_RefundStarPayment_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RefundStarPaymentParams{
		UserID:                  42,
		TelegramPaymentChargeID: "test_value",
	}
	_, err := RefundStarPayment(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_RefundStarPayment_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_RefundStarPayment_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &RefundStarPaymentParams{
		UserID:                  42,
		TelegramPaymentChargeID: "test_value",
	}
	_, err := RefundStarPayment(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_EditUserStarSubscription_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/editUserStarSubscription")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditUserStarSubscriptionParams{
		UserID:                  42,
		TelegramPaymentChargeID: "test_value",
		IsCanceled:              true,
	}
	_, err := EditUserStarSubscription(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_EditUserStarSubscription_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditUserStarSubscriptionParams{
		UserID:                  42,
		TelegramPaymentChargeID: "test_value",
		IsCanceled:              true,
	}
	_, err := EditUserStarSubscription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_EditUserStarSubscription_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditUserStarSubscriptionParams{
		UserID:                  42,
		TelegramPaymentChargeID: "test_value",
		IsCanceled:              true,
	}
	_, err := EditUserStarSubscription(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_EditUserStarSubscription_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditUserStarSubscriptionParams{
		UserID:                  42,
		TelegramPaymentChargeID: "test_value",
		IsCanceled:              true,
	}
	_, err := EditUserStarSubscription(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_EditUserStarSubscription_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditUserStarSubscriptionParams{
		UserID:                  42,
		TelegramPaymentChargeID: "test_value",
		IsCanceled:              true,
	}
	_, err := EditUserStarSubscription(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_EditUserStarSubscription_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditUserStarSubscription_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := EditUserStarSubscription(context.Background(), bot, &EditUserStarSubscriptionParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_EditUserStarSubscription_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditUserStarSubscription_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditUserStarSubscriptionParams{
		UserID:                  42,
		TelegramPaymentChargeID: "test_value",
		IsCanceled:              true,
	}
	_, err := EditUserStarSubscription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_EditUserStarSubscription_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditUserStarSubscription_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &EditUserStarSubscriptionParams{
		UserID:                  42,
		TelegramPaymentChargeID: "test_value",
		IsCanceled:              true,
	}
	_, err := EditUserStarSubscription(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetPassportDataErrors_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setPassportDataErrors")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetPassportDataErrorsParams{
		UserID: 42,
		Errors: nil,
	}
	_, err := SetPassportDataErrors(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetPassportDataErrors_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetPassportDataErrorsParams{
		UserID: 42,
		Errors: nil,
	}
	_, err := SetPassportDataErrors(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetPassportDataErrors_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetPassportDataErrorsParams{
		UserID: 42,
		Errors: nil,
	}
	_, err := SetPassportDataErrors(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetPassportDataErrors_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetPassportDataErrorsParams{
		UserID: 42,
		Errors: nil,
	}
	_, err := SetPassportDataErrors(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetPassportDataErrors_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetPassportDataErrorsParams{
		UserID: 42,
		Errors: nil,
	}
	_, err := SetPassportDataErrors(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetPassportDataErrors_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetPassportDataErrors_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetPassportDataErrors(context.Background(), bot, &SetPassportDataErrorsParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetPassportDataErrors_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetPassportDataErrors_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetPassportDataErrorsParams{
		UserID: 42,
		Errors: nil,
	}
	_, err := SetPassportDataErrors(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetPassportDataErrors_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetPassportDataErrors_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetPassportDataErrorsParams{
		UserID: 42,
		Errors: nil,
	}
	_, err := SetPassportDataErrors(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SendGame_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/sendGame")
	})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendGameParams{
		ChatID:        ChatIDFromInt(123),
		GameShortName: "test_value",
	}
	_, err := SendGame(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SendGame_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendGameParams{
		ChatID:        ChatIDFromInt(123),
		GameShortName: "test_value",
	}
	_, err := SendGame(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SendGame_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendGameParams{
		ChatID:        ChatIDFromInt(123),
		GameShortName: "test_value",
	}
	_, err := SendGame(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SendGame_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendGameParams{
		ChatID:        ChatIDFromInt(123),
		GameShortName: "test_value",
	}
	_, err := SendGame(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SendGame_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendGameParams{
		ChatID:        ChatIDFromInt(123),
		GameShortName: "test_value",
	}
	_, err := SendGame(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SendGame_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendGame_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SendGame(context.Background(), bot, &SendGameParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SendGame_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendGame_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendGameParams{
		ChatID:        ChatIDFromInt(123),
		GameShortName: "test_value",
	}
	_, err := SendGame(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SendGame_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendGame_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SendGameParams{
		ChatID:        ChatIDFromInt(123),
		GameShortName: "test_value",
	}
	_, err := SendGame(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_SetGameScore_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/setGameScore")
	})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetGameScoreParams{
		UserID: 42,
		Score:  42,
	}
	_, err := SetGameScore(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_SetGameScore_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetGameScoreParams{
		UserID: 42,
		Score:  42,
	}
	_, err := SetGameScore(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_SetGameScore_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetGameScoreParams{
		UserID: 42,
		Score:  42,
	}
	_, err := SetGameScore(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_SetGameScore_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetGameScoreParams{
		UserID: 42,
		Score:  42,
	}
	_, err := SetGameScore(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_SetGameScore_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetGameScoreParams{
		UserID: 42,
		Score:  42,
	}
	_, err := SetGameScore(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_SetGameScore_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetGameScore_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := SetGameScore(context.Background(), bot, &SetGameScoreParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_SetGameScore_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetGameScore_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetGameScoreParams{
		UserID: 42,
		Score:  42,
	}
	_, err := SetGameScore(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_SetGameScore_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetGameScore_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &SetGameScoreParams{
		UserID: 42,
		Score:  42,
	}
	_, err := SetGameScore(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}

func Test_GetGameHighScores_Success(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
		return strings.HasSuffix(r.URL.Path, "/getGameHighScores")
	})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetGameHighScoresParams{
		UserID: 42,
	}
	_, err := GetGameHighScores(context.Background(), bot, params)
	require.NoError(t, err)
}

func Test_GetGameHighScores_APIError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetGameHighScoresParams{
		UserID: 42,
	}
	_, err := GetGameHighScores(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 429, ae.Code)
	require.True(t, ae.IsRetryable())
}

func Test_GetGameHighScores_NetworkError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetGameHighScoresParams{
		UserID: 42,
	}
	_, err := GetGameHighScores(context.Background(), bot, params)
	require.Error(t, err)
	var ne *client.NetworkError
	require.ErrorAs(t, err, &ne)
}

func Test_GetGameHighScores_ParseError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetGameHighScoresParams{
		UserID: 42,
	}
	_, err := GetGameHighScores(context.Background(), bot, params)
	require.Error(t, err)
	var pe *client.ParseError
	require.ErrorAs(t, err, &pe)
}

func Test_GetGameHighScores_ContextCanceled(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetGameHighScoresParams{
		UserID: 42,
	}
	_, err := GetGameHighScores(ctx, bot, params)
	require.Error(t, err)
	require.ErrorIs(t, err, context.Canceled)
}

// Test_GetGameHighScores_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetGameHighScores_MissingRequiredFields(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	// Send a Params with all required fields zeroed — simulates a caller
	// that forgot to populate them. The bot library marshals as-is and
	// surfaces Telegram's 400 reply.
	_, err := GetGameHighScores(context.Background(), bot, &GetGameHighScoresParams{})
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 400, ae.Code)
	require.True(t, errors.Is(err, client.ErrBadRequest))
	require.False(t, ae.IsRetryable())
}

// Test_GetGameHighScores_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetGameHighScores_Forbidden(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetGameHighScoresParams{
		UserID: 42,
	}
	_, err := GetGameHighScores(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 403, ae.Code)
	require.True(t, errors.Is(err, client.ErrForbidden))
	require.False(t, ae.IsRetryable())
}

// Test_GetGameHighScores_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetGameHighScores_ServerError(t *testing.T) {
	m := &genTestMockDoer{}
	m.On("Do", mock.Anything).Return(
		genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)

	bot := client.New("test:token", client.WithHTTPClient(m))
	params := &GetGameHighScoresParams{
		UserID: 42,
	}
	_, err := GetGameHighScores(context.Background(), bot, params)
	require.Error(t, err)
	var ae *client.APIError
	require.ErrorAs(t, err, &ae)
	require.Equal(t, 500, ae.Code)
	require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
