"""Test authorization utility functions."""

import pytest

from databuilder_logic_sdk.utils.auth_utils import (
    BceAuth,
    build_authorization,
    canonical_header_str,
    canonical_qs,
    gen_authorization,
    get_canonical_time,
    get_utf8_value,
    normalized,
    normalized_uri,
    serialize_authorization,
)


class TestGetCanonicalTime:
    """Test get_canonical_time function."""

    def test_get_canonical_time_with_zero_timestamp(self) -> None:
        """Test canonical time with zero timestamp uses current time."""
        result = get_canonical_time(0)
        assert isinstance(result, str)
        assert "T" in result
        assert result.endswith("Z")
        # Format: 2025-12-13T03:19:46Z
        assert len(result) == 20

    def test_get_canonical_time_with_specific_timestamp(self) -> None:
        """Test canonical time with specific timestamp."""
        # Unix timestamp: 1609459200 = 2021-01-01 00:00:00 UTC
        result = get_canonical_time(1609459200)
        assert result == "2021-01-01T00:00:00Z"

    def test_get_canonical_time_format(self) -> None:
        """Test canonical time format is correct."""
        result = get_canonical_time(1234567890)  # 2009-02-13 23:31:30 UTC
        assert result == "2009-02-13T23:31:30Z"


class TestGetUtf8Value:
    """Test get_utf8_value function."""

    def test_get_utf8_value_with_string(self) -> None:
        """Test UTF-8 value conversion with string input."""
        result = get_utf8_value("hello")
        assert result == "hello"
        assert isinstance(result, str)

    def test_get_utf8_value_with_bytes(self) -> None:
        """Test UTF-8 value conversion with bytes input."""
        result = get_utf8_value(b"hello")
        assert result == "hello"
        assert isinstance(result, str)

    def test_get_utf8_value_with_chinese(self) -> None:
        """Test UTF-8 value conversion with Chinese characters."""
        result = get_utf8_value("你好")
        assert result == "你好"

    def test_get_utf8_value_with_chinese_bytes(self) -> None:
        """Test UTF-8 value conversion with Chinese bytes."""
        result = get_utf8_value("你好".encode("utf-8"))  # noqa: UP012
        assert result == "你好"


class TestNormalizedUri:
    """Test normalized_uri function."""

    def test_normalized_uri_basic(self) -> None:
        """Test basic URI normalization."""
        result = normalized_uri("/json-api/v1/example/")
        assert result == "/json-api/v1/example/"

    def test_normalized_uri_with_special_chars(self) -> None:
        """Test URI normalization with special characters."""
        result = normalized_uri("/path with spaces")
        assert result == "/path%20with%20spaces"

    def test_normalized_uri_preserves_safe_chars(self) -> None:
        """Test URI normalization preserves safe characters."""
        result = normalized_uri("/path-with_safe.chars~/")
        assert result == "/path-with_safe.chars~/"

    def test_normalized_uri_empty(self) -> None:
        """Test URI normalization with empty string."""
        result = normalized_uri("")
        assert result == ""


class TestNormalized:
    """Test normalized function."""

    def test_normalized_basic(self) -> None:
        """Test basic string normalization."""
        result = normalized("hello")
        assert result == "hello"

    def test_normalized_with_spaces(self) -> None:
        """Test normalization with spaces."""
        result = normalized("hello world")
        assert result == "hello%20world"

    def test_normalized_with_special_chars(self) -> None:
        """Test normalization with special characters."""
        result = normalized("hello-world_test.txt~")
        assert result == "hello-world_test.txt~"

    def test_normalized_chinese(self) -> None:
        """Test normalization with Chinese characters."""
        result = normalized("你好")
        assert "%" in result  # Should be percent-encoded


class TestCanonicalQs:
    """Test canonical_qs function."""

    def test_canonical_qs_basic(self) -> None:
        """Test basic query string construction."""
        params = {"key1": "value1", "key2": "value2"}
        result = canonical_qs(params)
        assert "key1=value1" in result
        assert "key2=value2" in result
        assert "&" in result

    def test_canonical_qs_sorted(self) -> None:
        """Test query string is sorted by key."""
        params = {"z": "1", "a": "2", "m": "3"}
        result = canonical_qs(params)
        # Keys should be sorted: a, m, z
        assert result.index("a=") < result.index("m=")
        assert result.index("m=") < result.index("z=")

    def test_canonical_qs_skips_authorization(self) -> None:
        """Test query string skips authorization parameter."""
        params = {"key1": "value1", "authorization": "should-be-skipped"}
        result = canonical_qs(params)
        assert "key1=value1" in result
        assert "authorization" not in result

    def test_canonical_qs_empty(self) -> None:
        """Test query string with empty params."""
        result = canonical_qs({})
        assert result == ""

    def test_canonical_qs_with_special_chars(self) -> None:
        """Test query string with special characters."""
        params = {"key": "value with spaces"}
        result = canonical_qs(params)
        assert "key=value%20with%20spaces" in result


class TestCanonicalHeaderStr:
    """Test canonical_header_str function."""

    def test_canonical_header_str_basic(self) -> None:
        """Test basic header string construction."""
        headers = {"Host": "example.com", "Content-Type": "application/json"}
        result = canonical_header_str(headers)
        assert "host:example.com" in result
        assert "content-type:application%2Fjson" in result

    def test_canonical_header_str_requires_host(self) -> None:
        """Test header string requires host header."""
        headers = {"Content-Type": "application/json"}
        with pytest.raises(ValueError, match="host header is required"):
            canonical_header_str(headers)

    def test_canonical_header_str_with_signedheaders(self) -> None:
        """Test header string with specified signed headers."""
        headers = {"Host": "example.com", "Content-Type": "application/json", "X-Custom": "value"}
        result = canonical_header_str(headers, signedheaders=["host", "content-type"])
        assert "host:example.com" in result
        assert "content-type:application%2Fjson" in result
        assert "x-custom" not in result

    def test_canonical_header_str_with_xbce_headers(self) -> None:
        """Test header string includes x-bce- prefixed headers."""
        headers = {"Host": "example.com", "x-bce-date": "2021-01-01", "X-Custom": "value"}
        result = canonical_header_str(headers)
        assert "host:example.com" in result
        assert "x-bce-date:2021-01-01" in result
        assert "x-custom" not in result  # Custom headers excluded unless signed

    def test_canonical_header_str_strips_whitespace(self) -> None:
        """Test header string strips leading/trailing whitespace."""
        headers = {"Host": "  example.com  ", "Content-Type": "  application/json  "}
        result = canonical_header_str(headers)
        assert "host:example.com" in result
        assert "content-type:application%2Fjson" in result

    def test_canonical_header_str_invalid_signedheader(self) -> None:
        """Test header string raises error for invalid signed header."""
        headers = {"Host": "example.com"}
        with pytest.raises(ValueError):
            canonical_header_str(headers, signedheaders=["non-existent"])


class TestBuildAuthorization:
    """Test build_authorization function."""

    def test_build_authorization_basic(self) -> None:
        """Test basic authorization object construction."""
        result = build_authorization("test_ak", ["host"], period=1800)
        assert result["version"] == "bce-auth-v1"
        assert result["access"] == "test_ak"
        assert result["period"] == "1800"
        assert result["signedheaders"] == ["host"]
        assert "timestamp" in result
        assert result["timestamp"].endswith("Z")

    def test_build_authorization_with_timestamp(self) -> None:
        """Test authorization object with specific timestamp."""
        timestamp = "2021-01-01T00:00:00Z"
        result = build_authorization("test_ak", ["host"], period=3600, timestamp=timestamp)
        assert result["timestamp"] == timestamp
        assert result["period"] == "3600"

    def test_build_authorization_empty_signedheaders(self) -> None:
        """Test authorization object with empty signed headers."""
        result = build_authorization("test_ak", [], period=1800)
        assert result["signedheaders"] == []


class TestSerializeAuthorization:
    """Test serialize_authorization function."""

    def test_serialize_authorization_basic(self) -> None:
        """Test basic authorization serialization."""
        auth = {
            "version": "bce-auth-v1",
            "access": "test_ak",
            "timestamp": "2021-01-01T00:00:00Z",
            "period": "1800",
            "signedheaders": ["host", "content-type"],
            "signature": "test_signature",
        }
        result = serialize_authorization(auth)
        assert result == "bce-auth-v1/test_ak/2021-01-01T00:00:00Z/1800/host;content-type/test_signature"

    def test_serialize_authorization_empty_signedheaders(self) -> None:
        """Test authorization serialization with empty signed headers."""
        auth = {
            "version": "bce-auth-v1",
            "access": "test_ak",
            "timestamp": "2021-01-01T00:00:00Z",
            "period": "1800",
            "signedheaders": [],
            "signature": "test_signature",
        }
        result = serialize_authorization(auth)
        assert result == "bce-auth-v1/test_ak/2021-01-01T00:00:00Z/1800//test_signature"


class TestGenAuthorization:
    """Test gen_authorization function."""

    def test_gen_authorization_basic(self) -> None:
        """Test basic authorization generation."""
        request = {
            "uri": "/api/v1/test",
            "params": {"key": "value"},
            "method": "GET",
            "headers": {"Host": "example.com"},
        }
        result = gen_authorization(request, "test_ak", "test_sk", timestamp="2021-01-01T00:00:00Z")
        assert isinstance(result, str)
        assert result.startswith("bce-auth-v1/")
        assert "test_ak" in result
        assert "2021-01-01T00:00:00Z" in result

    def test_gen_authorization_without_headers(self) -> None:
        """Test authorization generation without headers."""
        request = {
            "uri": "/api/v1/test",
            "params": {},
            "method": "GET",
            "headers": {"Host": "example.com"},  # Host header is required
        }
        result = gen_authorization(request, "test_ak", "test_sk", timestamp="2021-01-01T00:00:00Z")
        assert isinstance(result, str)
        assert result.startswith("bce-auth-v1/")

    def test_gen_authorization_post_with_body(self) -> None:
        """Test authorization generation for POST request."""
        request = {
            "uri": "/api/v1/test",
            "params": {},
            "method": "POST",
            "headers": {
                "Host": "example.com",
                "Content-Type": "application/json",
                "Content-Length": "100",
            },
        }
        result = gen_authorization(request, "test_ak", "test_sk", timestamp="2021-01-01T00:00:00Z")
        assert isinstance(result, str)
        assert result.startswith("bce-auth-v1/")

    def test_gen_authorization_default_timestamp(self) -> None:
        """Test authorization generation uses current time when timestamp not provided."""
        request = {
            "uri": "/api/v1/test",
            "params": {},
            "method": "GET",
            "headers": {"Host": "example.com"},
        }
        result = gen_authorization(request, "test_ak", "test_sk")
        assert isinstance(result, str)
        assert result.startswith("bce-auth-v1/")


class TestBceAuth:
    """Test BceAuth class."""

    def test_bce_auth_initialization(self) -> None:
        """Test BceAuth initialization."""
        auth = BceAuth("test_ak", "test_sk")
        assert auth.ak == "test_ak"
        assert auth.sk == "test_sk"

    def test_bce_auth_call_adds_authorization_header(self) -> None:
        """Test BceAuth adds authorization header to request."""
        auth = BceAuth("test_ak", "test_sk")

        # Mock a request object
        class MockRequest:
            def __init__(self) -> None:
                self.url = "http://example.com/api/v1/test?key=value"
                self.method = "GET"
                self.headers: dict[str, str] = {}

        request = MockRequest()
        result = auth(request)

        assert "Authorization" in result.headers
        assert result.headers["Authorization"].startswith("bce-auth-v1/")
        assert "test_ak" in result.headers["Authorization"]

    def test_bce_auth_call_with_existing_host_header(self) -> None:
        """Test BceAuth preserves existing host header."""
        auth = BceAuth("test_ak", "test_sk")

        class MockRequest:
            def __init__(self) -> None:
                self.url = "http://example.com/api/v1/test"
                self.method = "POST"
                self.headers = {"Host": "custom-host.com"}

        request = MockRequest()
        result = auth(request)

        assert result.headers["Host"] == "custom-host.com"
        assert "Authorization" in result.headers

    def test_bce_auth_call_with_query_params(self) -> None:
        """Test BceAuth handles query parameters correctly."""
        auth = BceAuth("test_ak", "test_sk")

        class MockRequest:
            def __init__(self) -> None:
                self.url = "http://example.com/api/v1/test?key1=value1&key2=value2"
                self.method = "GET"
                self.headers: dict[str, str] = {}

        request = MockRequest()
        result = auth(request)

        assert "Authorization" in result.headers
        assert isinstance(result.headers["Authorization"], str)

    def test_bce_auth_call_without_query_params(self) -> None:
        """Test BceAuth handles URL without query parameters."""
        auth = BceAuth("test_ak", "test_sk")

        class MockRequest:
            def __init__(self) -> None:
                self.url = "http://example.com/api/v1/test"
                self.method = "GET"
                self.headers: dict[str, str] = {}

        request = MockRequest()
        result = auth(request)

        assert "Authorization" in result.headers

    def test_bce_auth_call_sets_host_if_missing(self) -> None:
        """Test BceAuth sets host header if not present."""
        auth = BceAuth("test_ak", "test_sk")

        class MockRequest:
            def __init__(self) -> None:
                self.url = "http://example.com/api/v1/test"
                self.method = "GET"
                self.headers: dict[str, str] = {}

        request = MockRequest()
        result = auth(request)

        assert "Host" in result.headers
        assert result.headers["Host"] == "example.com"
