from __future__ import annotations

from datetime import UTC
from datetime import datetime
from typing import cast

import pytest

from amsdal_ml.mcp_server.mcp_oauth.services.types import DEFAULT_NEXT_PATH
from amsdal_ml.mcp_server.mcp_oauth.services.types import JwtClaims
from amsdal_ml.mcp_server.mcp_oauth.services.types import OAuthConstants
from amsdal_ml.mcp_server.mcp_oauth.services.utils import access_token_expires_in_seconds
from amsdal_ml.mcp_server.mcp_oauth.services.utils import build_access_payload
from amsdal_ml.mcp_server.mcp_oauth.services.utils import build_refresh_payload
from amsdal_ml.mcp_server.mcp_oauth.services.utils import consume_authorization_code_jti
from amsdal_ml.mcp_server.mcp_oauth.services.utils import get_signing_key
from amsdal_ml.mcp_server.mcp_oauth.services.utils import normalize_next
from amsdal_ml.mcp_server.mcp_oauth.services.utils import pkce_s256
from amsdal_ml.mcp_server.mcp_oauth.services.utils import render_login_html
from amsdal_ml.mcp_server.mcp_oauth.services.utils import store_authorization_code_jti
from amsdal_ml.ml_config import ml_config

TWO_HOURS_IN_SECONDS = 7200


def test_normalize_next_fallback_and_allowed_urls():
    base_url = 'https://example.com/'

    assert normalize_next(None, base_url=base_url) == DEFAULT_NEXT_PATH
    assert normalize_next('/local', base_url=base_url) == '/local'
    assert normalize_next('//evil.com/path', base_url=base_url) == DEFAULT_NEXT_PATH
    assert normalize_next('https://example.com/path', base_url=base_url) == 'https://example.com/path'
    assert normalize_next('https://evil.example/path', base_url=base_url) == DEFAULT_NEXT_PATH
    assert normalize_next('https://example.com/path', base_url=cast(str, None)) == DEFAULT_NEXT_PATH


def test_pkce_s256_is_deterministic():
    assert pkce_s256('abc') == 'ungWv48Bz-pBQUDeXa4iI7ADYaOWF3qctBD_YfIAFa0'


def test_render_login_html_escapes_error_and_next():
    html = render_login_html(next_url='/?x=1&y=2', error='<b>bad</b>')

    assert '&lt;b&gt;bad&lt;/b&gt;' in html
    assert '/?x=1&amp;y=2' in html


def test_authorization_code_jti_cache_store_and_consume():
    now_ts = int(datetime.now(tz=UTC).timestamp())
    store_authorization_code_jti('fresh-jti', now_ts + 60)

    assert consume_authorization_code_jti('fresh-jti') is True
    assert consume_authorization_code_jti('fresh-jti') is False


def test_authorization_code_jti_cleanup_removes_expired():
    now_ts = int(datetime.now(tz=UTC).timestamp())
    store_authorization_code_jti('expired-jti', now_ts - 1)

    assert consume_authorization_code_jti('expired-jti') is False


def test_get_signing_key_success_and_failure(monkeypatch):
    from amsdal.contrib.auth.errors import AuthenticationError
    from amsdal.contrib.auth.settings import auth_settings

    monkeypatch.setattr(auth_settings, 'AUTH_JWT_KEY', 'test-key-value', raising=False)
    assert get_signing_key() == 'test-key-value'

    monkeypatch.setattr(auth_settings, 'AUTH_JWT_KEY', '', raising=False)
    with pytest.raises(AuthenticationError, match='JWT key is not set'):
        get_signing_key()


def test_build_payload_helpers_and_expiry(monkeypatch):
    monkeypatch.setattr(ml_config, 'oauth_issuer', 'https://issuer.example')
    monkeypatch.setattr(ml_config, 'oauth_access_token_exp_hours', 2)
    monkeypatch.setattr(ml_config, 'oauth_refresh_token_exp_days', 3)
    now = datetime.now(tz=UTC)

    access_payload = build_access_payload(
        sub='user-1',
        resource='https://mcp.example.com',
        scope='files:read',
        now=now,
    )
    refresh_payload = build_refresh_payload(
        sub='user-1',
        client_id='client-1',
        resource='https://mcp.example.com',
        scope='files:read',
        now=now,
    )

    assert access_payload[JwtClaims.ISSUER.value] == 'https://issuer.example'
    assert access_payload[JwtClaims.TOKEN_TYPE.value] == OAuthConstants.TOKEN_TYPE_ACCESS.value
    assert refresh_payload[JwtClaims.CLIENT_ID.value] == 'client-1'
    assert refresh_payload[JwtClaims.TOKEN_TYPE.value] == OAuthConstants.TOKEN_TYPE_REFRESH.value
    assert access_token_expires_in_seconds() == TWO_HOURS_IN_SECONDS
