import base64
import hashlib
import time

import httpx
import jwt
import pytest
import respx

from eduzz_miau_client import MiauClient

API_URL = "https://miau.test"
APP_SECRET = "miau_d_abcdefghijklmnopqrstuvwxyz0123456789"


def _make_jwt(exp: int) -> str:
    return jwt.encode({"exp": exp, "application": {"id": "a1", "name": "test"}, "secret": {"id": "s1", "environment": "development"}}, "secret", algorithm="HS256")


class TestConstructor:
    def test_raises_on_empty_api_url(self):
        with pytest.raises(ValueError, match="api_url"):
            MiauClient(api_url="", app_secret=APP_SECRET)

    def test_raises_on_empty_app_secret(self):
        with pytest.raises(ValueError, match="app_secret"):
            MiauClient(api_url=API_URL, app_secret="")

    def test_basic_auth_token_matches_node_logic(self):
        client = MiauClient(api_url=API_URL, app_secret=APP_SECRET)

        api_key = APP_SECRET[7:32]
        hashed = hashlib.sha256(APP_SECRET.encode()).hexdigest()
        expected = base64.b64encode(f"{api_key}:{hashed}".encode()).decode()

        assert client._basic_auth_token == expected
        client.close()

    def test_context_manager(self):
        with MiauClient(api_url=API_URL, app_secret=APP_SECRET) as client:
            assert client._api_url == API_URL


class TestGetToken:
    @respx.mock
    def test_fetches_token_from_api(self):
        token = _make_jwt(int(time.time()) + 300)

        respx.get(f"{API_URL}/v1/oauth/token").mock(
            return_value=httpx.Response(200, json={"access_token": token, "token_type": "Bearer", "expires_in": 300})
        )

        with MiauClient(api_url=API_URL, app_secret=APP_SECRET) as client:
            result = client.get_token()
            assert result == token

    @respx.mock
    def test_caches_token_before_expiry(self):
        token = _make_jwt(int(time.time()) + 300)
        call_count = 0

        def _handler(request: httpx.Request) -> httpx.Response:
            nonlocal call_count
            call_count += 1
            return httpx.Response(200, json={"access_token": token, "token_type": "Bearer", "expires_in": 300})

        respx.get(f"{API_URL}/v1/oauth/token").mock(side_effect=_handler)

        with MiauClient(api_url=API_URL, app_secret=APP_SECRET) as client:
            client.get_token()
            client.get_token()
            client.get_token()

            assert call_count == 1

    @respx.mock
    def test_refetches_token_near_expiry(self):
        expired_token = _make_jwt(int(time.time()) + 30)
        fresh_token = _make_jwt(int(time.time()) + 300)
        call_count = 0

        def _handler(request: httpx.Request) -> httpx.Response:
            nonlocal call_count
            call_count += 1
            if call_count == 1:
                return httpx.Response(200, json={"access_token": expired_token, "token_type": "Bearer", "expires_in": 30})
            return httpx.Response(200, json={"access_token": fresh_token, "token_type": "Bearer", "expires_in": 300})

        respx.get(f"{API_URL}/v1/oauth/token").mock(side_effect=_handler)

        with MiauClient(api_url=API_URL, app_secret=APP_SECRET) as client:
            first = client.get_token()
            second = client.get_token()

            assert call_count == 2
            assert first == expired_token
            assert second == fresh_token

    @respx.mock
    def test_raises_on_error_response(self):
        respx.get(f"{API_URL}/v1/oauth/token").mock(
            return_value=httpx.Response(401, json={"error": "invalid_client", "error_description": "Bad credentials"})
        )

        with MiauClient(api_url=API_URL, app_secret=APP_SECRET) as client:
            with pytest.raises(RuntimeError, match="Bad credentials"):
                client.get_token()

    @respx.mock
    def test_raises_on_error_with_message_field(self):
        respx.get(f"{API_URL}/v1/oauth/token").mock(
            return_value=httpx.Response(403, json={"message": "Forbidden access", "error": "forbidden"})
        )

        with MiauClient(api_url=API_URL, app_secret=APP_SECRET) as client:
            with pytest.raises(RuntimeError, match="Forbidden access"):
                client.get_token()

    @respx.mock
    def test_sends_correct_auth_header(self):
        token = _make_jwt(int(time.time()) + 300)
        captured_headers = {}

        def _handler(request: httpx.Request) -> httpx.Response:
            captured_headers.update(dict(request.headers))
            return httpx.Response(200, json={"access_token": token, "token_type": "Bearer", "expires_in": 300})

        respx.get(f"{API_URL}/v1/oauth/token").mock(side_effect=_handler)

        with MiauClient(api_url=API_URL, app_secret=APP_SECRET) as client:
            client.get_token()

            assert captured_headers["authorization"].startswith("Basic ")
            assert captured_headers["content-type"] == "application/json"
