"""Tests for the Nest integration API glue library.

There are two interesting cases to exercise that have different strategies
for token refresh and for testing:
- API based requests, tested using aioclient_mock
- Pub/sub subcriber initialization, intercepted with patch()

The tests below exercise both cases during integration setup.
"""

import time
from unittest.mock import patch

from homeassistant.components.nest import DOMAIN
from homeassistant.components.nest.const import API_URL, OAUTH2_TOKEN, SDM_SCOPES
from homeassistant.setup import async_setup_component
from homeassistant.util import dt

from .common import (
    CLIENT_ID,
    CLIENT_SECRET,
    CONFIG,
    FAKE_REFRESH_TOKEN,
    FAKE_TOKEN,
    PROJECT_ID,
    create_config_entry,
)

FAKE_UPDATED_TOKEN = "fake-updated-token"


async def async_setup_sdm(hass):
    """Set up the integration."""
    assert await async_setup_component(hass, DOMAIN, CONFIG)
    await hass.async_block_till_done()


async def test_auth(hass, aioclient_mock):
    """Exercise authentication library creates valid credentials."""

    expiration_time = time.time() + 86400
    create_config_entry(hass, expiration_time)

    # Prepare to capture credentials in API request.  Empty payloads just mean
    # no devices or structures are loaded.
    aioclient_mock.get(f"{API_URL}/enterprises/{PROJECT_ID}/structures", json={})
    aioclient_mock.get(f"{API_URL}/enterprises/{PROJECT_ID}/devices", json={})

    # Prepare to capture credentials for Subscriber
    captured_creds = None

    async def async_new_subscriber(creds, subscription_name, loop, async_callback):
        """Capture credentials for tests."""
        nonlocal captured_creds
        captured_creds = creds
        return None  # GoogleNestSubscriber

    with patch(
        "google_nest_sdm.google_nest_subscriber.DefaultSubscriberFactory.async_new_subscriber",
        side_effect=async_new_subscriber,
    ) as new_subscriber_mock:
        await async_setup_sdm(hass)

    # Verify API requests are made with the correct credentials
    calls = aioclient_mock.mock_calls
    assert len(calls) == 2
    (method, url, data, headers) = calls[0]
    assert headers == {"Authorization": f"Bearer {FAKE_TOKEN}"}
    (method, url, data, headers) = calls[1]
    assert headers == {"Authorization": f"Bearer {FAKE_TOKEN}"}

    # Verify the susbcriber was created with the correct credentials
    assert len(new_subscriber_mock.mock_calls) == 1
    assert captured_creds
    creds = captured_creds
    assert creds.token == FAKE_TOKEN
    assert creds.refresh_token == FAKE_REFRESH_TOKEN
    assert int(dt.as_timestamp(creds.expiry)) == int(expiration_time)
    assert creds.valid
    assert not creds.expired
    assert creds.token_uri == OAUTH2_TOKEN
    assert creds.client_id == CLIENT_ID
    assert creds.client_secret == CLIENT_SECRET
    assert creds.scopes == SDM_SCOPES


async def test_auth_expired_token(hass, aioclient_mock):
    """Verify behavior of an expired token."""

    expiration_time = time.time() - 86400
    create_config_entry(hass, expiration_time)

    # Prepare a token refresh response
    aioclient_mock.post(
        OAUTH2_TOKEN,
        json={
            "access_token": FAKE_UPDATED_TOKEN,
            "expires_at": time.time() + 86400,
            "expires_in": 86400,
        },
    )
    # Prepare to capture credentials in API request.  Empty payloads just mean
    # no devices or structures are loaded.
    aioclient_mock.get(f"{API_URL}/enterprises/{PROJECT_ID}/structures", json={})
    aioclient_mock.get(f"{API_URL}/enterprises/{PROJECT_ID}/devices", json={})

    # Prepare to capture credentials for Subscriber
    captured_creds = None

    async def async_new_subscriber(creds, subscription_name, loop, async_callback):
        """Capture credentials for tests."""
        nonlocal captured_creds
        captured_creds = creds
        return None  # GoogleNestSubscriber

    with patch(
        "google_nest_sdm.google_nest_subscriber.DefaultSubscriberFactory.async_new_subscriber",
        side_effect=async_new_subscriber,
    ) as new_subscriber_mock:
        await async_setup_sdm(hass)

    calls = aioclient_mock.mock_calls
    assert len(calls) == 3
    # Verify refresh token call to get an updated token
    (method, url, data, headers) = calls[0]
    assert data == {
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "grant_type": "refresh_token",
        "refresh_token": FAKE_REFRESH_TOKEN,
    }
    # Verify API requests are made with the new token
    (method, url, data, headers) = calls[1]
    assert headers == {"Authorization": f"Bearer {FAKE_UPDATED_TOKEN}"}
    (method, url, data, headers) = calls[2]
    assert headers == {"Authorization": f"Bearer {FAKE_UPDATED_TOKEN}"}

    # The subscriber is created with a token that is expired.  Verify that the
    # credential is expired so the subscriber knows it needs to refresh it.
    assert len(new_subscriber_mock.mock_calls) == 1
    assert captured_creds
    creds = captured_creds
    assert creds.token == FAKE_TOKEN
    assert creds.refresh_token == FAKE_REFRESH_TOKEN
    assert int(dt.as_timestamp(creds.expiry)) == int(expiration_time)
    assert not creds.valid
    assert creds.expired
    assert creds.token_uri == OAUTH2_TOKEN
    assert creds.client_id == CLIENT_ID
    assert creds.client_secret == CLIENT_SECRET
    assert creds.scopes == SDM_SCOPES