"""Define test fixtures for Enphase Envoy."""

from collections.abc import AsyncGenerator, Generator
from typing import Any
from unittest.mock import AsyncMock, Mock, patch

import jwt
import multidict
from pyenphase import (
    EnvoyACBPower,
    EnvoyBatteryAggregate,
    EnvoyC6CC,
    EnvoyCollar,
    EnvoyData,
    EnvoyEncharge,
    EnvoyEnchargeAggregate,
    EnvoyEnchargePower,
    EnvoyEnpower,
    EnvoyInverter,
    EnvoySystemConsumption,
    EnvoySystemProduction,
    EnvoyTokenAuth,
)
from pyenphase.const import SupportedFeatures
from pyenphase.models.dry_contacts import EnvoyDryContactSettings, EnvoyDryContactStatus
from pyenphase.models.home import EnvoyInterfaceInformation
from pyenphase.models.meters import EnvoyMeterData
from pyenphase.models.tariff import EnvoyStorageSettings, EnvoyTariff
import pytest

from homeassistant.components.enphase_envoy import DOMAIN
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant

from tests.common import MockConfigEntry, load_json_object_fixture


@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
    """Override async_setup_entry."""
    with patch(
        "homeassistant.components.enphase_envoy.async_setup_entry",
        return_value=True,
    ) as mock_setup_entry:
        yield mock_setup_entry


@pytest.fixture(name="config_entry")
def config_entry_fixture(
    hass: HomeAssistant, config: dict[str, str]
) -> MockConfigEntry:
    """Define a config entry fixture."""
    return MockConfigEntry(
        domain=DOMAIN,
        entry_id="45a36e55aaddb2007c5f6602e0c38e72",
        title="Envoy 1234",
        unique_id="1234",
        data=config,
    )


@pytest.fixture(name="config")
def config_fixture() -> dict[str, str]:
    """Define a config entry data fixture."""
    return {
        CONF_HOST: "1.1.1.1",
        CONF_NAME: "Envoy 1234",
        CONF_USERNAME: "test-username",
        CONF_PASSWORD: "test-password",
    }


@pytest.fixture
async def mock_envoy(
    request: pytest.FixtureRequest,
) -> AsyncGenerator[AsyncMock]:
    """Define a mocked Envoy fixture."""
    new_token = jwt.encode(
        payload={"name": "envoy", "exp": 2007837780},
        key="secret",
        algorithm="HS256",
    )
    with (
        patch(
            "homeassistant.components.enphase_envoy.config_flow.Envoy",
            autospec=True,
        ) as mock_client,
        patch(
            "homeassistant.components.enphase_envoy.Envoy",
            new=mock_client,
        ),
        patch(
            "pyenphase.auth.EnvoyTokenAuth._obtain_token",
            return_value=new_token,
        ),
    ):
        mock_envoy = mock_client.return_value
        # Add the fixtures specified
        token = jwt.encode(
            payload={"name": "envoy", "exp": 1907837780},
            key="secret",
            algorithm="HS256",
        )
        mock_envoy.auth = EnvoyTokenAuth("127.0.0.1", token=token, envoy_serial="1234")
        mock_envoy.serial_number = "1234"
        mock = Mock()
        mock.status = 200
        aiohttp_text = AsyncMock()
        aiohttp_text.return_value = "Testing request \nreplies."
        mock.text = aiohttp_text
        mock.headers = multidict.MultiDict([("Hello", "World")])
        mock_envoy.request.return_value = mock

        # determine fixture file name, default envoy if no request passed
        fixture_name = "envoy"
        if hasattr(request, "param"):
            fixture_name = request.param

        # Load envoy model from fixture
        load_envoy_fixture(mock_envoy, fixture_name)
        mock_envoy.update.return_value = mock_envoy.data

        yield mock_envoy


def load_envoy_fixture(mock_envoy: AsyncMock, fixture_name: str) -> None:
    """Load envoy model from fixture."""

    json_fixture: dict[str, Any] = load_json_object_fixture(
        f"{fixture_name}.json", DOMAIN
    )

    mock_envoy.firmware = json_fixture["firmware"]
    mock_envoy.part_number = json_fixture["part_number"]
    mock_envoy.envoy_model = json_fixture["envoy_model"]
    mock_envoy.supported_features = SupportedFeatures(
        json_fixture["supported_features"]
    )
    mock_envoy.phase_mode = json_fixture["phase_mode"]
    mock_envoy.phase_count = json_fixture["phase_count"]
    mock_envoy.active_phase_count = json_fixture["active_phase_count"]
    mock_envoy.ct_meter_count = json_fixture["ct_meter_count"]
    mock_envoy.consumption_meter_type = json_fixture["consumption_meter_type"]
    mock_envoy.production_meter_type = json_fixture["production_meter_type"]
    mock_envoy.storage_meter_type = json_fixture["storage_meter_type"]

    mock_envoy.data = EnvoyData()
    _load_json_2_production_data(mock_envoy.data, json_fixture)
    _load_json_2_meter_data(mock_envoy.data, json_fixture)
    _load_json_2_inverter_data(mock_envoy.data, json_fixture)
    _load_json_2_encharge_enpower_data(mock_envoy.data, json_fixture)
    _load_json_2_raw_data(mock_envoy.data, json_fixture)

    if item := json_fixture.get("interface_information"):
        mock_envoy.interface_settings.return_value = EnvoyInterfaceInformation(**item)
    else:
        mock_envoy.interface_settings.return_value = None


def _load_json_2_production_data(
    mocked_data: EnvoyData, json_fixture: dict[str, Any]
) -> None:
    """Fill envoy production data from fixture."""
    if item := json_fixture["data"].get("system_consumption"):
        mocked_data.system_consumption = EnvoySystemConsumption(**item)
    if item := json_fixture["data"].get("system_net_consumption"):
        mocked_data.system_net_consumption = EnvoySystemConsumption(**item)
    if item := json_fixture["data"].get("system_production"):
        mocked_data.system_production = EnvoySystemProduction(**item)
    if item := json_fixture["data"].get("system_consumption_phases"):
        mocked_data.system_consumption_phases = {}
        for sub_item, item_data in item.items():
            mocked_data.system_consumption_phases[sub_item] = EnvoySystemConsumption(
                **item_data
            )
    if item := json_fixture["data"].get("system_net_consumption_phases"):
        mocked_data.system_net_consumption_phases = {}
        for sub_item, item_data in item.items():
            mocked_data.system_net_consumption_phases[sub_item] = (
                EnvoySystemConsumption(**item_data)
            )
    if item := json_fixture["data"].get("system_production_phases"):
        mocked_data.system_production_phases = {}
        for sub_item, item_data in item.items():
            mocked_data.system_production_phases[sub_item] = EnvoySystemProduction(
                **item_data
            )
    if item := json_fixture["data"].get("acb_power"):
        mocked_data.acb_power = EnvoyACBPower(**item)


def _load_json_2_meter_data(
    mocked_data: EnvoyData, json_fixture: dict[str, Any]
) -> None:
    """Fill envoy meter data from fixture."""
    if meters := json_fixture["data"].get("ctmeters"):
        mocked_data.ctmeters = {}
        [
            mocked_data.ctmeters.update({meter: EnvoyMeterData(**meter_data)})
            for meter, meter_data in meters.items()
        ]
    if meters := json_fixture["data"].get("ctmeters_phases"):
        mocked_data.ctmeters_phases = {}
        for meter, meter_data in meters.items():
            meter_phase_data: dict[str, EnvoyMeterData] = {}
            [
                meter_phase_data.update({phase: EnvoyMeterData(**phase_data)})
                for phase, phase_data in meter_data.items()
            ]
            mocked_data.ctmeters_phases.update({meter: meter_phase_data})

    if item := json_fixture["data"].get("ctmeter_production"):
        mocked_data.ctmeter_production = EnvoyMeterData(**item)
    if item := json_fixture["data"].get("ctmeter_consumption"):
        mocked_data.ctmeter_consumption = EnvoyMeterData(**item)
    if item := json_fixture["data"].get("ctmeter_storage"):
        mocked_data.ctmeter_storage = EnvoyMeterData(**item)
    if item := json_fixture["data"].get("ctmeter_production_phases"):
        mocked_data.ctmeter_production_phases = {}
        for sub_item, item_data in item.items():
            mocked_data.ctmeter_production_phases[sub_item] = EnvoyMeterData(
                **item_data
            )
    if item := json_fixture["data"].get("ctmeter_consumption_phases"):
        mocked_data.ctmeter_consumption_phases = {}
        for sub_item, item_data in item.items():
            mocked_data.ctmeter_consumption_phases[sub_item] = EnvoyMeterData(
                **item_data
            )
    if item := json_fixture["data"].get("ctmeter_storage_phases"):
        mocked_data.ctmeter_storage_phases = {}
        for sub_item, item_data in item.items():
            mocked_data.ctmeter_storage_phases[sub_item] = EnvoyMeterData(**item_data)


def _load_json_2_inverter_data(
    mocked_data: EnvoyData, json_fixture: dict[str, Any]
) -> None:
    """Fill envoy inverter data from fixture."""
    if item := json_fixture["data"].get("inverters"):
        mocked_data.inverters = {}
        for sub_item, item_data in item.items():
            mocked_data.inverters[sub_item] = EnvoyInverter(**item_data)


def _load_json_2_encharge_enpower_data(
    mocked_data: EnvoyData, json_fixture: dict[str, Any]
) -> None:
    """Fill envoy encharge/enpower data from fixture."""
    if item := json_fixture["data"].get("encharge_inventory"):
        mocked_data.encharge_inventory = {}
        for sub_item, item_data in item.items():
            mocked_data.encharge_inventory[sub_item] = EnvoyEncharge(**item_data)
    if item := json_fixture["data"].get("enpower"):
        mocked_data.enpower = EnvoyEnpower(**item)
    if item := json_fixture["data"].get("encharge_aggregate"):
        mocked_data.encharge_aggregate = EnvoyEnchargeAggregate(**item)
    if item := json_fixture["data"].get("encharge_power"):
        mocked_data.encharge_power = {}
        for sub_item, item_data in item.items():
            mocked_data.encharge_power[sub_item] = EnvoyEnchargePower(**item_data)
    if item := json_fixture["data"].get("tariff"):
        mocked_data.tariff = EnvoyTariff(**item)
        mocked_data.tariff.storage_settings = EnvoyStorageSettings(
            **item["storage_settings"]
        )
    if item := json_fixture["data"].get("dry_contact_status"):
        mocked_data.dry_contact_status = {}
        for sub_item, item_data in item.items():
            mocked_data.dry_contact_status[sub_item] = EnvoyDryContactStatus(
                **item_data
            )
    if item := json_fixture["data"].get("dry_contact_settings"):
        mocked_data.dry_contact_settings = {}
        for sub_item, item_data in item.items():
            mocked_data.dry_contact_settings[sub_item] = EnvoyDryContactSettings(
                **item_data
            )
    if item := json_fixture["data"].get("battery_aggregate"):
        mocked_data.battery_aggregate = EnvoyBatteryAggregate(**item)
    if item := json_fixture["data"].get("collar"):
        mocked_data.collar = EnvoyCollar(**item)
    if item := json_fixture["data"].get("c6cc"):
        mocked_data.c6cc = EnvoyC6CC(**item)


def _load_json_2_raw_data(mocked_data: EnvoyData, json_fixture: dict[str, Any]) -> None:
    """Fill envoy raw data from fixture."""
    if item := json_fixture["data"].get("raw"):
        mocked_data.raw = item
