"""Pure-data coverage for authenticator_common: json helpers, Authentication,
state setter, expiration, and refreshed-token JWT handling.

Bare pytest, fully typed — no classes.
"""

import json
import logging
from datetime import datetime, timedelta, timezone
from unittest.mock import MagicMock

import jwt
import pytest

from yalexs.authenticator_common import (
    Authentication,
    AuthenticationState,
    AuthenticatorCommon,
    from_authentication_json,
    to_authentication_json,
)
from yalexs.const import (
    DEFAULT_BRAND,
    HEADER_ACCESS_TOKEN,
    HEADER_AUGUST_ACCESS_TOKEN,
)


def _fmt(dt: datetime) -> str:
    return dt.strftime("%Y-%m-%dT%H:%M:%S.%fZ")


def _build_common() -> AuthenticatorCommon:
    api = MagicMock()
    api.brand = DEFAULT_BRAND
    common = AuthenticatorCommon(api, "phone", "user", "pass", install_id="iid")
    common._authentication = Authentication(
        AuthenticationState.AUTHENTICATED,
        install_id="iid",
        access_token="old_token",
        access_token_expires=_fmt(datetime.now(timezone.utc) + timedelta(days=30)),
    )
    return common


# ---------------------------------------------------------------------------
# JSON helpers
# ---------------------------------------------------------------------------


def test_to_authentication_json_with_none() -> None:
    assert to_authentication_json(None) == "{}"


def test_from_authentication_json_with_none() -> None:
    assert from_authentication_json(None) is None


def test_roundtrip() -> None:
    expires_at = _fmt(datetime.now(timezone.utc) + timedelta(days=10))
    original = Authentication(
        AuthenticationState.AUTHENTICATED,
        install_id="iid",
        access_token="tok",
        access_token_expires=expires_at,
    )
    revived = from_authentication_json(json.loads(to_authentication_json(original)))
    assert revived.install_id == original.install_id
    assert revived.access_token == original.access_token
    assert revived.access_token_expires == original.access_token_expires
    assert revived.state == original.state


# ---------------------------------------------------------------------------
# Authentication
# ---------------------------------------------------------------------------


def test_install_id_autogenerated_when_missing() -> None:
    auth = Authentication(AuthenticationState.REQUIRES_AUTHENTICATION)
    assert auth.install_id
    assert auth.install_id != ""


def test_state_setter() -> None:
    auth = Authentication(AuthenticationState.REQUIRES_AUTHENTICATION)
    auth.state = AuthenticationState.AUTHENTICATED
    assert auth.state == AuthenticationState.AUTHENTICATED


def test_is_expired_true_for_past() -> None:
    expires_at = _fmt(datetime.now(timezone.utc) - timedelta(days=1))
    auth = Authentication(
        AuthenticationState.AUTHENTICATED, access_token_expires=expires_at
    )
    assert auth.is_expired()


def test_is_expired_false_for_future() -> None:
    expires_at = _fmt(datetime.now(timezone.utc) + timedelta(days=1))
    auth = Authentication(
        AuthenticationState.AUTHENTICATED, access_token_expires=expires_at
    )
    assert not auth.is_expired()


def test_parsed_expiration_time_none_when_no_expires() -> None:
    auth = Authentication(AuthenticationState.REQUIRES_AUTHENTICATION)
    assert auth.parsed_expiration_time() is None


# ---------------------------------------------------------------------------
# AuthenticatorCommon — session response handling + JWT refresh path
# ---------------------------------------------------------------------------


def test_session_response_prefers_access_token_header_over_august() -> None:
    common = _build_common()
    result = common._authentication_from_session_response(
        "iid",
        {HEADER_ACCESS_TOKEN: "new", HEADER_AUGUST_ACCESS_TOKEN: "old"},
        {
            "expiresAt": _fmt(datetime.now(timezone.utc) + timedelta(days=1)),
            "vPassword": True,
            "vInstallId": True,
        },
    )
    assert result.access_token == "new"
    assert result.state == AuthenticationState.AUTHENTICATED


def test_session_response_falls_back_to_august_header() -> None:
    common = _build_common()
    result = common._authentication_from_session_response(
        "iid",
        {HEADER_AUGUST_ACCESS_TOKEN: "old"},
        {
            "expiresAt": _fmt(datetime.now(timezone.utc) + timedelta(days=1)),
            "vPassword": True,
            "vInstallId": True,
        },
    )
    assert result.access_token == "old"


def test_session_response_bad_password() -> None:
    common = _build_common()
    result = common._authentication_from_session_response(
        "iid",
        {HEADER_AUGUST_ACCESS_TOKEN: "x"},
        {
            "expiresAt": _fmt(datetime.now(timezone.utc)),
            "vPassword": False,
            "vInstallId": True,
        },
    )
    assert result.state == AuthenticationState.BAD_PASSWORD


def test_session_response_requires_validation() -> None:
    common = _build_common()
    result = common._authentication_from_session_response(
        "iid",
        {HEADER_AUGUST_ACCESS_TOKEN: "x"},
        {
            "expiresAt": _fmt(datetime.now(timezone.utc)),
            "vPassword": True,
            "vInstallId": False,
        },
    )
    assert result.state == AuthenticationState.REQUIRES_VALIDATION


def test_should_refresh_false_when_not_authenticated() -> None:
    common = _build_common()
    common._authentication.state = AuthenticationState.REQUIRES_AUTHENTICATION
    assert not common.should_refresh()


def test_process_refreshed_access_token_with_exp() -> None:
    common = _build_common()
    exp = int((datetime.now(timezone.utc) + timedelta(days=14)).timestamp())
    token = jwt.encode({"exp": exp}, "secret", algorithm="HS256")
    result = common._process_refreshed_access_token(token)
    assert result.access_token == token
    # Stored as ISO format with trailing Z; parsed_expiration_time should
    # round-trip back to the same UTC timestamp.
    assert int(result.parsed_expiration_time().timestamp()) == exp


def test_process_refreshed_access_token_missing_exp_warns(
    caplog: pytest.LogCaptureFixture,
) -> None:
    common = _build_common()
    token = jwt.encode({"sub": "user"}, "secret", algorithm="HS256")
    caplog.set_level(logging.WARNING, logger="yalexs.authenticator_common")
    result = common._process_refreshed_access_token(token)
    assert any(
        r.name == "yalexs.authenticator_common"
        and r.levelno == logging.WARNING
        and "exp" in r.getMessage()
        for r in caplog.records
    ), f"expected exp warning, got: {caplog.records}"
    # Returned the existing authentication unchanged.
    assert result.access_token == "old_token"
