# type: ignore
import logging
from typing import Type

import httpx
import pytest  # type: ignore # https://github.com/pytest-dev/pytest/issues/3342

from kasa import (
    Credentials,
    DeviceType,
    Discover,
    SmartBulb,
    SmartDevice,
    SmartDeviceException,
    SmartDimmer,
    SmartLightStrip,
    SmartPlug,
)
from kasa.device_factory import connect, get_protocol
from kasa.deviceconfig import (
    ConnectionType,
    DeviceConfig,
    DeviceFamilyType,
    EncryptType,
)
from kasa.discover import DiscoveryResult


def _get_connection_type_device_class(the_fixture_data):
    if "discovery_result" in the_fixture_data:
        discovery_info = {"result": the_fixture_data["discovery_result"]}
        device_class = Discover._get_device_class(discovery_info)
        dr = DiscoveryResult(**discovery_info["result"])

        connection_type = ConnectionType.from_values(
            dr.device_type, dr.mgt_encrypt_schm.encrypt_type
        )
    else:
        connection_type = ConnectionType.from_values(
            DeviceFamilyType.IotSmartPlugSwitch.value, EncryptType.Xor.value
        )
        device_class = Discover._get_device_class(the_fixture_data)

    return connection_type, device_class


async def test_connect(
    all_fixture_data: dict,
    mocker,
):
    """Test that if the protocol is passed in it gets set correctly."""
    host = "127.0.0.1"
    ctype, device_class = _get_connection_type_device_class(all_fixture_data)

    mocker.patch("kasa.IotProtocol.query", return_value=all_fixture_data)
    mocker.patch("kasa.SmartProtocol.query", return_value=all_fixture_data)
    mocker.patch("kasa.TPLinkSmartHomeProtocol.query", return_value=all_fixture_data)

    config = DeviceConfig(
        host=host, credentials=Credentials("foor", "bar"), connection_type=ctype
    )
    protocol_class = get_protocol(config).__class__

    dev = await connect(
        config=config,
    )
    assert isinstance(dev, device_class)
    assert isinstance(dev.protocol, protocol_class)

    assert dev.config == config


@pytest.mark.parametrize("custom_port", [123, None])
async def test_connect_custom_port(all_fixture_data: dict, mocker, custom_port):
    """Make sure that connect returns an initialized SmartDevice instance."""
    host = "127.0.0.1"

    ctype, _ = _get_connection_type_device_class(all_fixture_data)
    config = DeviceConfig(
        host=host,
        port_override=custom_port,
        connection_type=ctype,
        credentials=Credentials("dummy_user", "dummy_password"),
    )
    default_port = 80 if "discovery_result" in all_fixture_data else 9999

    ctype, _ = _get_connection_type_device_class(all_fixture_data)
    mocker.patch("kasa.TPLinkSmartHomeProtocol.query", return_value=all_fixture_data)
    mocker.patch("kasa.IotProtocol.query", return_value=all_fixture_data)
    mocker.patch("kasa.SmartProtocol.query", return_value=all_fixture_data)
    dev = await connect(config=config)
    assert issubclass(dev.__class__, SmartDevice)
    assert dev.port == custom_port or dev.port == default_port


async def test_connect_logs_connect_time(
    all_fixture_data: dict, caplog: pytest.LogCaptureFixture, mocker
):
    """Test that the connect time is logged when debug logging is enabled."""
    ctype, _ = _get_connection_type_device_class(all_fixture_data)
    mocker.patch("kasa.IotProtocol.query", return_value=all_fixture_data)
    mocker.patch("kasa.SmartProtocol.query", return_value=all_fixture_data)
    mocker.patch("kasa.TPLinkSmartHomeProtocol.query", return_value=all_fixture_data)

    host = "127.0.0.1"
    config = DeviceConfig(
        host=host, credentials=Credentials("foor", "bar"), connection_type=ctype
    )
    logging.getLogger("kasa").setLevel(logging.DEBUG)
    await connect(
        config=config,
    )
    assert "seconds to update" in caplog.text


async def test_connect_query_fails(all_fixture_data: dict, mocker):
    """Make sure that connect fails when query fails."""
    host = "127.0.0.1"
    mocker.patch("kasa.TPLinkSmartHomeProtocol.query", side_effect=SmartDeviceException)
    mocker.patch("kasa.IotProtocol.query", side_effect=SmartDeviceException)
    mocker.patch("kasa.SmartProtocol.query", side_effect=SmartDeviceException)

    ctype, _ = _get_connection_type_device_class(all_fixture_data)
    config = DeviceConfig(
        host=host, credentials=Credentials("foor", "bar"), connection_type=ctype
    )
    with pytest.raises(SmartDeviceException):
        await connect(config=config)


async def test_connect_http_client(all_fixture_data, mocker):
    """Make sure that discover_single returns an initialized SmartDevice instance."""
    host = "127.0.0.1"

    ctype, _ = _get_connection_type_device_class(all_fixture_data)

    mocker.patch("kasa.IotProtocol.query", return_value=all_fixture_data)
    mocker.patch("kasa.SmartProtocol.query", return_value=all_fixture_data)
    mocker.patch("kasa.TPLinkSmartHomeProtocol.query", return_value=all_fixture_data)

    http_client = httpx.AsyncClient()

    config = DeviceConfig(
        host=host, credentials=Credentials("foor", "bar"), connection_type=ctype
    )
    dev = await connect(config=config)
    if ctype.encryption_type != EncryptType.Xor:
        assert dev.protocol._transport._http_client != http_client

    config = DeviceConfig(
        host=host,
        credentials=Credentials("foor", "bar"),
        connection_type=ctype,
        http_client=http_client,
    )
    dev = await connect(config=config)
    if ctype.encryption_type != EncryptType.Xor:
        assert dev.protocol._transport._http_client == http_client