"""Fixtures for withings tests."""
import time
from typing import Awaitable, Callable, List

import asynctest
import withings_api as withings
import pytest

import homeassistant.components.api as api
import homeassistant.components.http as http
import homeassistant.components.withings.const as const
from homeassistant.components.withings import CONFIG_SCHEMA, DOMAIN
from homeassistant.config import async_process_ha_core_config
from homeassistant.const import CONF_UNIT_SYSTEM, CONF_UNIT_SYSTEM_METRIC
from homeassistant.setup import async_setup_component

from .common import (
    WITHINGS_MEASURES_RESPONSE,
    WITHINGS_SLEEP_RESPONSE,
    WITHINGS_SLEEP_SUMMARY_RESPONSE,
)


class WithingsFactoryConfig:
    """Configuration for withings test fixture."""

    PROFILE_1 = "Person 1"
    PROFILE_2 = "Person 2"

    def __init__(
        self,
        api_config: dict = None,
        http_config: dict = None,
        measures: List[str] = None,
        unit_system: str = None,
        throttle_interval: int = const.THROTTLE_INTERVAL,
        withings_request_response="DATA",
        withings_measures_response: withings.WithingsMeasures = WITHINGS_MEASURES_RESPONSE,
        withings_sleep_response: withings.WithingsSleep = WITHINGS_SLEEP_RESPONSE,
        withings_sleep_summary_response: withings.WithingsSleepSummary = WITHINGS_SLEEP_SUMMARY_RESPONSE,
    ) -> None:
        """Constructor."""
        self._throttle_interval = throttle_interval
        self._withings_request_response = withings_request_response
        self._withings_measures_response = withings_measures_response
        self._withings_sleep_response = withings_sleep_response
        self._withings_sleep_summary_response = withings_sleep_summary_response
        self._withings_config = {
            const.CLIENT_ID: "my_client_id",
            const.CLIENT_SECRET: "my_client_secret",
            const.PROFILES: [
                WithingsFactoryConfig.PROFILE_1,
                WithingsFactoryConfig.PROFILE_2,
            ],
        }

        self._api_config = api_config or {"base_url": "http://localhost/"}
        self._http_config = http_config or {}
        self._measures = measures

        assert self._withings_config, "withings_config must be set."
        assert isinstance(
            self._withings_config, dict
        ), "withings_config must be a dict."
        assert isinstance(self._api_config, dict), "api_config must be a dict."
        assert isinstance(self._http_config, dict), "http_config must be a dict."

        self._hass_config = {
            "homeassistant": {CONF_UNIT_SYSTEM: unit_system or CONF_UNIT_SYSTEM_METRIC},
            api.DOMAIN: self._api_config,
            http.DOMAIN: self._http_config,
            DOMAIN: self._withings_config,
        }

    @property
    def withings_config(self):
        """Get withings component config."""
        return self._withings_config

    @property
    def api_config(self):
        """Get api component config."""
        return self._api_config

    @property
    def http_config(self):
        """Get http component config."""
        return self._http_config

    @property
    def measures(self):
        """Get the measures."""
        return self._measures

    @property
    def hass_config(self):
        """Home assistant config."""
        return self._hass_config

    @property
    def throttle_interval(self):
        """Throttle interval."""
        return self._throttle_interval

    @property
    def withings_request_response(self):
        """Request response."""
        return self._withings_request_response

    @property
    def withings_measures_response(self) -> withings.WithingsMeasures:
        """Measures response."""
        return self._withings_measures_response

    @property
    def withings_sleep_response(self) -> withings.WithingsSleep:
        """Sleep response."""
        return self._withings_sleep_response

    @property
    def withings_sleep_summary_response(self) -> withings.WithingsSleepSummary:
        """Sleep summary response."""
        return self._withings_sleep_summary_response


class WithingsFactoryData:
    """Data about the configured withing test component."""

    def __init__(
        self,
        hass,
        flow_id,
        withings_auth_get_credentials_mock,
        withings_api_request_mock,
        withings_api_get_measures_mock,
        withings_api_get_sleep_mock,
        withings_api_get_sleep_summary_mock,
        data_manager_get_throttle_interval_mock,
    ):
        """Constructor."""
        self._hass = hass
        self._flow_id = flow_id
        self._withings_auth_get_credentials_mock = withings_auth_get_credentials_mock
        self._withings_api_request_mock = withings_api_request_mock
        self._withings_api_get_measures_mock = withings_api_get_measures_mock
        self._withings_api_get_sleep_mock = withings_api_get_sleep_mock
        self._withings_api_get_sleep_summary_mock = withings_api_get_sleep_summary_mock
        self._data_manager_get_throttle_interval_mock = (
            data_manager_get_throttle_interval_mock
        )

    @property
    def hass(self):
        """Get hass instance."""
        return self._hass

    @property
    def flow_id(self):
        """Get flow id."""
        return self._flow_id

    @property
    def withings_auth_get_credentials_mock(self):
        """Get auth credentials mock."""
        return self._withings_auth_get_credentials_mock

    @property
    def withings_api_request_mock(self):
        """Get request mock."""
        return self._withings_api_request_mock

    @property
    def withings_api_get_measures_mock(self):
        """Get measures mock."""
        return self._withings_api_get_measures_mock

    @property
    def withings_api_get_sleep_mock(self):
        """Get sleep mock."""
        return self._withings_api_get_sleep_mock

    @property
    def withings_api_get_sleep_summary_mock(self):
        """Get sleep summary mock."""
        return self._withings_api_get_sleep_summary_mock

    @property
    def data_manager_get_throttle_interval_mock(self):
        """Get throttle mock."""
        return self._data_manager_get_throttle_interval_mock

    async def configure_user(self):
        """Present a form with user profiles."""
        step = await self.hass.config_entries.flow.async_configure(self.flow_id, None)
        assert step["step_id"] == "user"

    async def configure_profile(self, profile: str):
        """Select the user profile. Present a form with authorization link."""
        print("CONFIG_PROFILE:", profile)
        step = await self.hass.config_entries.flow.async_configure(
            self.flow_id, {const.PROFILE: profile}
        )
        assert step["step_id"] == "auth"

    async def configure_code(self, profile: str, code: str):
        """Handle authorization code. Create config entries."""
        step = await self.hass.config_entries.flow.async_configure(
            self.flow_id, {const.PROFILE: profile, const.CODE: code}
        )
        assert step["type"] == "external_done"

        await self.hass.async_block_till_done()

        step = await self.hass.config_entries.flow.async_configure(
            self.flow_id, {const.PROFILE: profile, const.CODE: code}
        )

        assert step["type"] == "create_entry"

        await self.hass.async_block_till_done()

    async def configure_all(self, profile: str, code: str):
        """Configure all flow steps."""
        await self.configure_user()
        await self.configure_profile(profile)
        await self.configure_code(profile, code)


WithingsFactory = Callable[[WithingsFactoryConfig], Awaitable[WithingsFactoryData]]


@pytest.fixture(name="withings_factory")
def withings_factory_fixture(request, hass) -> WithingsFactory:
    """Home assistant platform fixture."""
    patches = []

    async def factory(config: WithingsFactoryConfig) -> WithingsFactoryData:
        CONFIG_SCHEMA(config.hass_config.get(DOMAIN))

        await async_process_ha_core_config(
            hass, config.hass_config.get("homeassistant")
        )
        assert await async_setup_component(hass, http.DOMAIN, config.hass_config)
        assert await async_setup_component(hass, api.DOMAIN, config.hass_config)

        withings_auth_get_credentials_patch = asynctest.patch(
            "withings_api.WithingsAuth.get_credentials",
            return_value=withings.WithingsCredentials(
                access_token="my_access_token",
                token_expiry=time.time() + 600,
                token_type="my_token_type",
                refresh_token="my_refresh_token",
                user_id="my_user_id",
                client_id=config.withings_config.get(const.CLIENT_ID),
                consumer_secret=config.withings_config.get(const.CLIENT_SECRET),
            ),
        )
        withings_auth_get_credentials_mock = withings_auth_get_credentials_patch.start()

        withings_api_request_patch = asynctest.patch(
            "withings_api.WithingsApi.request",
            return_value=config.withings_request_response,
        )
        withings_api_request_mock = withings_api_request_patch.start()

        withings_api_get_measures_patch = asynctest.patch(
            "withings_api.WithingsApi.get_measures",
            return_value=config.withings_measures_response,
        )
        withings_api_get_measures_mock = withings_api_get_measures_patch.start()

        withings_api_get_sleep_patch = asynctest.patch(
            "withings_api.WithingsApi.get_sleep",
            return_value=config.withings_sleep_response,
        )
        withings_api_get_sleep_mock = withings_api_get_sleep_patch.start()

        withings_api_get_sleep_summary_patch = asynctest.patch(
            "withings_api.WithingsApi.get_sleep_summary",
            return_value=config.withings_sleep_summary_response,
        )
        withings_api_get_sleep_summary_mock = (
            withings_api_get_sleep_summary_patch.start()
        )

        data_manager_get_throttle_interval_patch = asynctest.patch(
            "homeassistant.components.withings.common.WithingsDataManager"
            ".get_throttle_interval",
            return_value=config.throttle_interval,
        )
        data_manager_get_throttle_interval_mock = (
            data_manager_get_throttle_interval_patch.start()
        )

        get_measures_patch = asynctest.patch(
            "homeassistant.components.withings.sensor.get_measures",
            return_value=config.measures,
        )
        get_measures_patch.start()

        patches.extend(
            [
                withings_auth_get_credentials_patch,
                withings_api_request_patch,
                withings_api_get_measures_patch,
                withings_api_get_sleep_patch,
                withings_api_get_sleep_summary_patch,
                data_manager_get_throttle_interval_patch,
                get_measures_patch,
            ]
        )

        # Collect the flow id.
        tasks = []

        orig_async_create_task = hass.async_create_task

        def create_task(*args):
            task = orig_async_create_task(*args)
            tasks.append(task)
            return task

        async_create_task_patch = asynctest.patch.object(
            hass, "async_create_task", side_effect=create_task
        )

        with async_create_task_patch:
            assert await async_setup_component(hass, DOMAIN, config.hass_config)
            await hass.async_block_till_done()

            flow_id = tasks[2].result()["flow_id"]

        return WithingsFactoryData(
            hass,
            flow_id,
            withings_auth_get_credentials_mock,
            withings_api_request_mock,
            withings_api_get_measures_mock,
            withings_api_get_sleep_mock,
            withings_api_get_sleep_summary_mock,
            data_manager_get_throttle_interval_mock,
        )

    def cleanup():
        for patch in patches:
            patch.stop()

    request.addfinalizer(cleanup)

    return factory
