"""Test fixtures for fitbit."""

from collections.abc import Awaitable, Callable, Generator
import datetime
from http import HTTPStatus
import time
from typing import Any
from unittest.mock import patch

import pytest
from requests_mock.mocker import Mocker

from homeassistant.components.application_credentials import (
    ClientCredential,
    async_import_client_credential,
)
from homeassistant.components.fitbit.const import (
    CONF_CLIENT_ID,
    CONF_CLIENT_SECRET,
    DOMAIN,
    OAUTH_SCOPES,
)
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component

from tests.common import MockConfigEntry

CLIENT_ID = "1234"
CLIENT_SECRET = "5678"
PROFILE_USER_ID = "fitbit-api-user-id-1"
FAKE_ACCESS_TOKEN = "some-access-token"
FAKE_REFRESH_TOKEN = "some-refresh-token"
FAKE_AUTH_IMPL = "conftest-imported-cred"
FULL_NAME = "First Last"
DISPLAY_NAME = "First L."
PROFILE_DATA = {
    "fullName": FULL_NAME,
    "displayName": DISPLAY_NAME,
    "displayNameSetting": "name",
    "firstName": "First",
    "lastName": "Last",
}

PROFILE_API_URL = "https://api.fitbit.com/1/user/-/profile.json"
DEVICES_API_URL = "https://api.fitbit.com/1/user/-/devices.json"
TIMESERIES_API_URL_FORMAT = (
    "https://api.fitbit.com/1/user/-/{resource}/date/today/7d.json"
)

# These constants differ from values in the config entry or fitbit.conf
SERVER_ACCESS_TOKEN = {
    "refresh_token": "server-refresh-token",
    "access_token": "server-access-token",
    "type": "Bearer",
    "expires_in": 60,
    "scope": " ".join(OAUTH_SCOPES),
}


@pytest.fixture(name="token_expiration_time")
def mcok_token_expiration_time() -> float:
    """Fixture for expiration time of the config entry auth token."""
    return time.time() + 86400


@pytest.fixture(name="scopes")
def mock_scopes() -> list[str]:
    """Fixture for expiration time of the config entry auth token."""
    return OAUTH_SCOPES


@pytest.fixture(name="token_entry")
def mock_token_entry(token_expiration_time: float, scopes: list[str]) -> dict[str, Any]:
    """Fixture for OAuth 'token' data for a ConfigEntry."""
    return {
        "access_token": FAKE_ACCESS_TOKEN,
        "refresh_token": FAKE_REFRESH_TOKEN,
        "scope": " ".join(scopes),
        "token_type": "Bearer",
        "expires_at": token_expiration_time,
    }


@pytest.fixture(name="config_entry")
def mock_config_entry(token_entry: dict[str, Any]) -> MockConfigEntry:
    """Fixture for a config entry."""
    return MockConfigEntry(
        domain=DOMAIN,
        data={
            "auth_implementation": FAKE_AUTH_IMPL,
            "token": token_entry,
        },
        unique_id=PROFILE_USER_ID,
    )


@pytest.fixture
async def setup_credentials(hass: HomeAssistant) -> None:
    """Fixture to setup credentials."""
    assert await async_setup_component(hass, "application_credentials", {})
    await async_import_client_credential(
        hass,
        DOMAIN,
        ClientCredential(CLIENT_ID, CLIENT_SECRET),
        FAKE_AUTH_IMPL,
    )


@pytest.fixture(name="fitbit_config_yaml")
def mock_fitbit_config_yaml(token_expiration_time: float) -> dict[str, Any] | None:
    """Fixture for the yaml fitbit.conf file contents."""
    return {
        CONF_CLIENT_ID: CLIENT_ID,
        CONF_CLIENT_SECRET: CLIENT_SECRET,
        "access_token": FAKE_ACCESS_TOKEN,
        "refresh_token": FAKE_REFRESH_TOKEN,
        "last_saved_at": token_expiration_time,
    }


@pytest.fixture(name="fitbit_config_setup")
def mock_fitbit_config_setup(
    fitbit_config_yaml: dict[str, Any] | None,
) -> Generator[None, None, None]:
    """Fixture to mock out fitbit.conf file data loading and persistence."""
    has_config = fitbit_config_yaml is not None
    with patch(
        "homeassistant.components.fitbit.sensor.os.path.isfile",
        return_value=has_config,
    ), patch(
        "homeassistant.components.fitbit.sensor.load_json_object",
        return_value=fitbit_config_yaml,
    ):
        yield


@pytest.fixture(name="monitored_resources")
def mock_monitored_resources() -> list[str] | None:
    """Fixture for the fitbit yaml config monitored_resources field."""
    return None


@pytest.fixture(name="configured_unit_system")
def mock_configured_unit_syststem() -> str | None:
    """Fixture for the fitbit yaml config monitored_resources field."""
    return None


@pytest.fixture(name="sensor_platform_config")
def mock_sensor_platform_config(
    monitored_resources: list[str] | None,
    configured_unit_system: str | None,
) -> dict[str, Any]:
    """Fixture for the fitbit sensor platform configuration data in configuration.yaml."""
    config = {}
    if monitored_resources is not None:
        config["monitored_resources"] = monitored_resources
    if configured_unit_system is not None:
        config["unit_system"] = configured_unit_system
    return config


@pytest.fixture(name="sensor_platform_setup")
async def mock_sensor_platform_setup(
    hass: HomeAssistant,
    sensor_platform_config: dict[str, Any],
) -> Callable[[], Awaitable[bool]]:
    """Fixture to set up the integration."""

    async def run() -> bool:
        result = await async_setup_component(
            hass,
            "sensor",
            {
                "sensor": [
                    {
                        "platform": DOMAIN,
                        **sensor_platform_config,
                    }
                ]
            },
        )
        await hass.async_block_till_done()
        return result

    return run


@pytest.fixture
def platforms() -> list[Platform]:
    """Fixture to specify platforms to test."""
    return []


@pytest.fixture(name="integration_setup")
async def mock_integration_setup(
    hass: HomeAssistant,
    config_entry: MockConfigEntry,
    platforms: list[str],
) -> Callable[[], Awaitable[bool]]:
    """Fixture to set up the integration."""
    config_entry.add_to_hass(hass)

    async def run() -> bool:
        with patch(f"homeassistant.components.{DOMAIN}.PLATFORMS", platforms):
            result = await hass.config_entries.async_setup(config_entry.entry_id)
            await hass.async_block_till_done()
        return result

    return run


@pytest.fixture(name="profile_id")
def mock_profile_id() -> str:
    """Fixture for the profile id returned from the API response."""
    return PROFILE_USER_ID


@pytest.fixture(name="profile_locale")
def mock_profile_locale() -> str:
    """Fixture to set the API response for the user profile."""
    return "en_US"


@pytest.fixture(name="profile_data")
def mock_profile_data() -> dict[str, Any]:
    """Fixture to return other profile data fields."""
    return PROFILE_DATA


@pytest.fixture(name="profile_response")
def mock_profile_response(
    profile_id: str, profile_locale: str, profile_data: dict[str, Any]
) -> dict[str, Any]:
    """Fixture to construct the fake profile API response."""
    return {
        "user": {
            "encodedId": profile_id,
            "locale": profile_locale,
            **profile_data,
        },
    }


@pytest.fixture(name="profile", autouse=True)
def mock_profile(requests_mock: Mocker, profile_response: dict[str, Any]) -> None:
    """Fixture to setup fake requests made to Fitbit API during config flow."""
    requests_mock.register_uri(
        "GET",
        PROFILE_API_URL,
        status_code=HTTPStatus.OK,
        json=profile_response,
    )


@pytest.fixture(name="devices_response")
def mock_device_response() -> list[dict[str, Any]]:
    """Return the list of devices."""
    return []


@pytest.fixture(autouse=True)
def mock_devices(requests_mock: Mocker, devices_response: dict[str, Any]) -> None:
    """Fixture to setup fake device responses."""
    requests_mock.register_uri(
        "GET",
        DEVICES_API_URL,
        status_code=HTTPStatus.OK,
        json=devices_response,
    )


def timeseries_response(resource: str, value: str) -> dict[str, Any]:
    """Create a timeseries response value."""
    return {
        resource: [{"dateTime": datetime.datetime.today().isoformat(), "value": value}]
    }


@pytest.fixture(name="register_timeseries")
def mock_register_timeseries(
    requests_mock: Mocker,
) -> Callable[[str, dict[str, Any]], None]:
    """Fixture to setup fake timeseries API responses."""

    def register(resource: str, response: dict[str, Any]) -> None:
        requests_mock.register_uri(
            "GET",
            TIMESERIES_API_URL_FORMAT.format(resource=resource),
            status_code=HTTPStatus.OK,
            json=response,
        )

    return register