#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program.  If not, see [http://www.gnu.org/licenses/].
import inspect
from copy import deepcopy
from typing import Union

import pytest

from telegram import (
    BackgroundFill,
    BackgroundFillFreeformGradient,
    BackgroundFillGradient,
    BackgroundFillSolid,
    BackgroundType,
    BackgroundTypeChatTheme,
    BackgroundTypeFill,
    BackgroundTypePattern,
    BackgroundTypeWallpaper,
    Dice,
    Document,
)
from tests.auxil.slots import mro_slots

ignored = ["self", "api_kwargs"]


class BFDefaults:
    color = 0
    top_color = 1
    bottom_color = 2
    rotation_angle = 45
    colors = [0, 1, 2]


def background_fill_solid():
    return BackgroundFillSolid(BFDefaults.color)


def background_fill_gradient():
    return BackgroundFillGradient(
        BFDefaults.top_color, BFDefaults.bottom_color, BFDefaults.rotation_angle
    )


def background_fill_freeform_gradient():
    return BackgroundFillFreeformGradient(BFDefaults.colors)


class BTDefaults:
    document = Document(1, 2)
    fill = BackgroundFillSolid(color=0)
    dark_theme_dimming = 20
    is_blurred = True
    is_moving = False
    intensity = 90
    is_inverted = False
    theme_name = "ice"


def background_type_fill():
    return BackgroundTypeFill(BTDefaults.fill, BTDefaults.dark_theme_dimming)


def background_type_wallpaper():
    return BackgroundTypeWallpaper(
        BTDefaults.document,
        BTDefaults.dark_theme_dimming,
        BTDefaults.is_blurred,
        BTDefaults.is_moving,
    )


def background_type_pattern():
    return BackgroundTypePattern(
        BTDefaults.document,
        BTDefaults.fill,
        BTDefaults.intensity,
        BTDefaults.is_inverted,
        BTDefaults.is_moving,
    )


def background_type_chat_theme():
    return BackgroundTypeChatTheme(BTDefaults.theme_name)


def make_json_dict(
    instance: Union[BackgroundType, BackgroundFill], include_optional_args: bool = False
) -> dict:
    """Used to make the json dict which we use for testing de_json. Similar to iter_args()"""
    json_dict = {"type": instance.type}
    sig = inspect.signature(instance.__class__.__init__)

    for param in sig.parameters.values():
        if param.name in ignored:  # ignore irrelevant params
            continue

        val = getattr(instance, param.name)
        # Compulsory args-
        if param.default is inspect.Parameter.empty:
            if hasattr(val, "to_dict"):  # convert the user object or any future ones to dict.
                val = val.to_dict()
            json_dict[param.name] = val

        # If we want to test all args (for de_json)-
        elif param.default is not inspect.Parameter.empty and include_optional_args:
            json_dict[param.name] = val
    return json_dict


def iter_args(
    instance: Union[BackgroundType, BackgroundFill],
    de_json_inst: Union[BackgroundType, BackgroundFill],
    include_optional: bool = False,
):
    """
    We accept both the regular instance and de_json created instance and iterate over them for
    easy one line testing later one.
    """
    yield instance.type, de_json_inst.type  # yield this here cause it's not available in sig.

    sig = inspect.signature(instance.__class__.__init__)
    for param in sig.parameters.values():
        if param.name in ignored:
            continue
        inst_at, json_at = getattr(instance, param.name), getattr(de_json_inst, param.name)
        if (
            param.default is not inspect.Parameter.empty and include_optional
        ) or param.default is inspect.Parameter.empty:
            yield inst_at, json_at


@pytest.fixture
def background_type(request):
    return request.param()


@pytest.mark.parametrize(
    "background_type",
    [
        background_type_fill,
        background_type_wallpaper,
        background_type_pattern,
        background_type_chat_theme,
    ],
    indirect=True,
)
class TestBackgroundTypeWithoutRequest:
    def test_slot_behaviour(self, background_type):
        inst = background_type
        for attr in inst.__slots__:
            assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
        assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"

    def test_de_json_required_args(self, offline_bot, background_type):
        cls = background_type.__class__
        assert cls.de_json({}, offline_bot) is None

        json_dict = make_json_dict(background_type)
        const_background_type = BackgroundType.de_json(json_dict, offline_bot)
        assert const_background_type.api_kwargs == {}

        assert isinstance(const_background_type, BackgroundType)
        assert isinstance(const_background_type, cls)
        for bg_type_at, const_bg_type_at in iter_args(background_type, const_background_type):
            assert bg_type_at == const_bg_type_at

    def test_de_json_all_args(self, offline_bot, background_type):
        json_dict = make_json_dict(background_type, include_optional_args=True)
        const_background_type = BackgroundType.de_json(json_dict, offline_bot)

        assert const_background_type.api_kwargs == {}

        assert isinstance(const_background_type, BackgroundType)
        assert isinstance(const_background_type, background_type.__class__)
        for bg_type_at, const_bg_type_at in iter_args(
            background_type, const_background_type, True
        ):
            assert bg_type_at == const_bg_type_at

    def test_de_json_invalid_type(self, background_type, offline_bot):
        json_dict = {"type": "invalid", "theme_name": BTDefaults.theme_name}
        background_type = BackgroundType.de_json(json_dict, offline_bot)

        assert type(background_type) is BackgroundType
        assert background_type.type == "invalid"

    def test_de_json_subclass(self, background_type, offline_bot, chat_id):
        """This makes sure that e.g. BackgroundTypeFill(data, offline_bot) never returns a
        BackgroundTypeWallpaper instance."""
        cls = background_type.__class__
        json_dict = make_json_dict(background_type, True)
        assert type(cls.de_json(json_dict, offline_bot)) is cls

    def test_to_dict(self, background_type):
        bg_type_dict = background_type.to_dict()

        assert isinstance(bg_type_dict, dict)
        assert bg_type_dict["type"] == background_type.type

        for slot in background_type.__slots__:  # additional verification for the optional args
            if slot in ("fill", "document"):
                assert (getattr(background_type, slot)).to_dict() == bg_type_dict[slot]
                continue
            assert getattr(background_type, slot) == bg_type_dict[slot]

    def test_equality(self, background_type):
        a = BackgroundType(type="type")
        b = BackgroundType(type="type")
        c = background_type
        d = deepcopy(background_type)
        e = Dice(4, "emoji")
        sig = inspect.signature(background_type.__class__.__init__)
        params = [
            "random" for param in sig.parameters.values() if param.name not in [*ignored, "type"]
        ]
        f = background_type.__class__(*params)

        assert a == b
        assert hash(a) == hash(b)

        assert a != c
        assert hash(a) != hash(c)

        assert a != d
        assert hash(a) != hash(d)

        assert a != e
        assert hash(a) != hash(e)

        assert c == d
        assert hash(c) == hash(d)

        assert c != e
        assert hash(c) != hash(e)

        assert f != c
        assert hash(f) != hash(c)


@pytest.fixture
def background_fill(request):
    return request.param()


@pytest.mark.parametrize(
    "background_fill",
    [
        background_fill_solid,
        background_fill_gradient,
        background_fill_freeform_gradient,
    ],
    indirect=True,
)
class TestBackgroundFillWithoutRequest:
    def test_slot_behaviour(self, background_fill):
        inst = background_fill
        for attr in inst.__slots__:
            assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
        assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"

    def test_de_json_required_args(self, offline_bot, background_fill):
        cls = background_fill.__class__
        assert cls.de_json({}, offline_bot) is None

        json_dict = make_json_dict(background_fill)
        const_background_fill = BackgroundFill.de_json(json_dict, offline_bot)
        assert const_background_fill.api_kwargs == {}

        assert isinstance(const_background_fill, BackgroundFill)
        assert isinstance(const_background_fill, cls)
        for bg_fill_at, const_bg_fill_at in iter_args(background_fill, const_background_fill):
            assert bg_fill_at == const_bg_fill_at

    def test_de_json_all_args(self, offline_bot, background_fill):
        json_dict = make_json_dict(background_fill, include_optional_args=True)
        const_background_fill = BackgroundFill.de_json(json_dict, offline_bot)

        assert const_background_fill.api_kwargs == {}

        assert isinstance(const_background_fill, BackgroundFill)
        assert isinstance(const_background_fill, background_fill.__class__)
        for bg_fill_at, const_bg_fill_at in iter_args(
            background_fill, const_background_fill, True
        ):
            assert bg_fill_at == const_bg_fill_at

    def test_de_json_invalid_type(self, background_fill, offline_bot):
        json_dict = {"type": "invalid", "theme_name": BTDefaults.theme_name}
        background_fill = BackgroundFill.de_json(json_dict, offline_bot)

        assert type(background_fill) is BackgroundFill
        assert background_fill.type == "invalid"

    def test_de_json_subclass(self, background_fill, offline_bot):
        """This makes sure that e.g. BackgroundFillSolid(data, offline_bot) never returns a
        BackgroundFillGradient instance."""
        cls = background_fill.__class__
        json_dict = make_json_dict(background_fill, True)
        assert type(cls.de_json(json_dict, offline_bot)) is cls

    def test_to_dict(self, background_fill):
        bg_fill_dict = background_fill.to_dict()

        assert isinstance(bg_fill_dict, dict)
        assert bg_fill_dict["type"] == background_fill.type

        for slot in background_fill.__slots__:  # additional verification for the optional args
            if slot == "colors":
                assert getattr(background_fill, slot) == tuple(bg_fill_dict[slot])
                continue
            assert getattr(background_fill, slot) == bg_fill_dict[slot]

    def test_equality(self, background_fill):
        a = BackgroundFill(type="type")
        b = BackgroundFill(type="type")
        c = background_fill
        d = deepcopy(background_fill)
        e = Dice(4, "emoji")
        sig = inspect.signature(background_fill.__class__.__init__)
        params = [
            "random" for param in sig.parameters.values() if param.name not in [*ignored, "type"]
        ]
        f = background_fill.__class__(*params)

        assert a == b
        assert hash(a) == hash(b)

        assert a != c
        assert hash(a) != hash(c)

        assert a != d
        assert hash(a) != hash(d)

        assert a != e
        assert hash(a) != hash(e)

        assert c == d
        assert hash(c) == hash(d)

        assert c != e
        assert hash(c) != hash(e)

        assert f != c
        assert hash(f) != hash(c)
