"""The tests for evohome storage load & save."""

from datetime import datetime, timedelta
from typing import Any, Final, NotRequired, TypedDict

import pytest

from homeassistant.components.evohome.const import DOMAIN, STORAGE_KEY, STORAGE_VER
from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util

from .conftest import setup_evohome
from .const import ACCESS_TOKEN, REFRESH_TOKEN, SESSION_ID, USERNAME


class _SessionDataT(TypedDict):
    session_id: str
    session_id_expires: NotRequired[str]  # 2024-07-27T23:57:30+01:00


class _TokenStoreT(TypedDict):
    username: str
    refresh_token: str
    access_token: str
    access_token_expires: str  # 2024-07-27T23:57:30+01:00
    user_data: NotRequired[_SessionDataT]


class _EmptyStoreT(TypedDict):
    pass


SZ_USERNAME: Final = "username"
SZ_REFRESH_TOKEN: Final = "refresh_token"
SZ_ACCESS_TOKEN: Final = "access_token"
SZ_ACCESS_TOKEN_EXPIRES: Final = "access_token_expires"
SZ_USER_DATA: Final = "user_data"


def dt_pair(dt_dtm: datetime) -> tuple[datetime, str]:
    """Return a datetime without milliseconds and its string representation."""
    dt_str = dt_dtm.isoformat(timespec="seconds")  # e.g. 2024-07-28T00:57:29+01:00
    return dt_util.parse_datetime(dt_str, raise_on_error=True), dt_str


ACCESS_TOKEN_EXP_DTM, ACCESS_TOKEN_EXP_STR = dt_pair(dt_util.now() + timedelta(hours=1))

USERNAME_DIFF: Final = f"not_{USERNAME}"
USERNAME_SAME: Final = USERNAME

_TEST_STORAGE_BASE: Final[_TokenStoreT] = {
    SZ_USERNAME: USERNAME_SAME,
    SZ_REFRESH_TOKEN: REFRESH_TOKEN,
    SZ_ACCESS_TOKEN: ACCESS_TOKEN,
    SZ_ACCESS_TOKEN_EXPIRES: ACCESS_TOKEN_EXP_STR,
}

TEST_STORAGE_DATA: Final[dict[str, _TokenStoreT]] = {
    "sans_session_id": _TEST_STORAGE_BASE,
    "null_session_id": _TEST_STORAGE_BASE | {SZ_USER_DATA: None},  # type: ignore[dict-item]
    "with_session_id": _TEST_STORAGE_BASE | {SZ_USER_DATA: {"session_id": SESSION_ID}},
}

TEST_STORAGE_NULL: Final[dict[str, _EmptyStoreT | None]] = {
    "store_is_absent": None,
    "store_was_reset": {},
}

DOMAIN_STORAGE_BASE: Final = {
    "version": STORAGE_VER,
    "minor_version": 1,
    "key": STORAGE_KEY,
}


@pytest.mark.parametrize("install", ["minimal"])
@pytest.mark.parametrize("idx", TEST_STORAGE_NULL)
async def test_auth_tokens_null(
    hass: HomeAssistant,
    hass_storage: dict[str, Any],
    config: dict[str, str],
    idx: str,
    install: str,
) -> None:
    """Test credentials manager when cache is empty."""

    hass_storage[DOMAIN] = DOMAIN_STORAGE_BASE | {"data": TEST_STORAGE_NULL[idx]}

    async for _ in setup_evohome(hass, config, install=install):
        pass

    # Confirm the expected tokens were cached to storage...
    data: _TokenStoreT = hass_storage[DOMAIN]["data"]

    assert data[SZ_USERNAME] == USERNAME_SAME
    assert data[SZ_REFRESH_TOKEN] == f"new_{REFRESH_TOKEN}"
    assert data[SZ_ACCESS_TOKEN] == f"new_{ACCESS_TOKEN}"
    assert (
        dt_util.parse_datetime(data[SZ_ACCESS_TOKEN_EXPIRES], raise_on_error=True)
        > dt_util.now()
    )


@pytest.mark.parametrize("install", ["minimal"])
@pytest.mark.parametrize("idx", TEST_STORAGE_DATA)
async def test_auth_tokens_same(
    hass: HomeAssistant,
    hass_storage: dict[str, Any],
    config: dict[str, str],
    idx: str,
    install: str,
) -> None:
    """Test credentials manager when cache contains valid data for this user."""

    hass_storage[DOMAIN] = DOMAIN_STORAGE_BASE | {"data": TEST_STORAGE_DATA[idx]}

    async for _ in setup_evohome(hass, config, install=install):
        pass

    # Confirm the expected tokens were cached to storage...
    data: _TokenStoreT = hass_storage[DOMAIN]["data"]

    assert data[SZ_USERNAME] == USERNAME_SAME
    assert data[SZ_REFRESH_TOKEN] == REFRESH_TOKEN
    assert data[SZ_ACCESS_TOKEN] == ACCESS_TOKEN
    assert dt_util.parse_datetime(data[SZ_ACCESS_TOKEN_EXPIRES]) == ACCESS_TOKEN_EXP_DTM


@pytest.mark.parametrize("install", ["minimal"])
@pytest.mark.parametrize("idx", TEST_STORAGE_DATA)
async def test_auth_tokens_past(
    hass: HomeAssistant,
    hass_storage: dict[str, Any],
    config: dict[str, str],
    idx: str,
    install: str,
) -> None:
    """Test credentials manager when cache contains expired data for this user."""

    _dt_dtm, dt_str = dt_pair(dt_util.now() - timedelta(hours=1))

    # make this access token have expired in the past...
    test_data = TEST_STORAGE_DATA[idx].copy()  # shallow copy is OK here
    test_data[SZ_ACCESS_TOKEN_EXPIRES] = dt_str

    hass_storage[DOMAIN] = DOMAIN_STORAGE_BASE | {"data": test_data}

    async for _ in setup_evohome(hass, config, install=install):
        pass

    # Confirm the expected tokens were cached to storage...
    data: _TokenStoreT = hass_storage[DOMAIN]["data"]

    assert data[SZ_USERNAME] == USERNAME_SAME
    assert data[SZ_REFRESH_TOKEN] == f"new_{REFRESH_TOKEN}"
    assert data[SZ_ACCESS_TOKEN] == f"new_{ACCESS_TOKEN}"
    assert (
        dt_util.parse_datetime(data[SZ_ACCESS_TOKEN_EXPIRES], raise_on_error=True)
        > dt_util.now()
    )


@pytest.mark.parametrize("install", ["minimal"])
@pytest.mark.parametrize("idx", TEST_STORAGE_DATA)
async def test_auth_tokens_diff(
    hass: HomeAssistant,
    hass_storage: dict[str, Any],
    config: dict[str, str],
    idx: str,
    install: str,
) -> None:
    """Test credentials manager when cache contains data for a different user."""

    hass_storage[DOMAIN] = DOMAIN_STORAGE_BASE | {"data": TEST_STORAGE_DATA[idx]}
    config["username"] = USERNAME_DIFF

    async for _ in setup_evohome(hass, config, install=install):
        pass

    # Confirm the expected tokens were cached to storage...
    data: _TokenStoreT = hass_storage[DOMAIN]["data"]

    assert data[SZ_USERNAME] == USERNAME_DIFF
    assert data[SZ_REFRESH_TOKEN] == f"new_{REFRESH_TOKEN}"
    assert data[SZ_ACCESS_TOKEN] == f"new_{ACCESS_TOKEN}"
    assert (
        dt_util.parse_datetime(data[SZ_ACCESS_TOKEN_EXPIRES], raise_on_error=True)
        > dt_util.now()
    )
