"""Unit tests for the dynojson unmarshall function."""

import json

import pytest

from dynojson import unmarshall

from .fixtures import (
    MARSHALLED_ALL_TYPES,
    MARSHALLED_ARRAY,
    MARSHALLED_ARRAY_ARRAYS,
    MARSHALLED_GET_ITEM,
    MARSHALLED_OBJECT,
    MARSHALLED_SCAN,
    UNMARSHALLED_ALL_TYPES,
    UNMARSHALLED_ARRAY,
    UNMARSHALLED_ARRAY_ARRAYS,
    UNMARSHALLED_GET_ITEM,
    UNMARSHALLED_OBJECT,
    UNMARSHALLED_SCAN,
)


class TestUnmarshall:
    """Tests for converting DynamoDB JSON to regular JSON."""

    def test_unmarshall_single_object(self):
        result = json.loads(unmarshall(MARSHALLED_OBJECT))
        expected = json.loads(UNMARSHALLED_OBJECT)
        assert result == expected

    def test_unmarshall_array_of_ddb_values(self):
        result = json.loads(unmarshall(MARSHALLED_ARRAY))
        expected = json.loads(UNMARSHALLED_ARRAY)
        assert result == expected

    def test_unmarshall_scan_result(self):
        result = json.loads(unmarshall(MARSHALLED_SCAN))
        expected = json.loads(UNMARSHALLED_SCAN)
        assert result == expected

    def test_unmarshall_array_of_arrays(self):
        result = json.loads(unmarshall(MARSHALLED_ARRAY_ARRAYS))
        expected = json.loads(UNMARSHALLED_ARRAY_ARRAYS)
        assert result == expected

    def test_unmarshall_string(self):
        result = json.loads(unmarshall('{"key":{"S":"value"}}'))
        assert result == {"key": "value"}

    def test_unmarshall_number(self):
        result = json.loads(unmarshall('{"key":{"N":"42"}}'))
        assert result == {"key": 42}

    def test_unmarshall_float(self):
        result = json.loads(unmarshall('{"key":{"N":"3.14"}}'))
        assert result == {"key": 3.14}

    def test_unmarshall_boolean(self):
        result = json.loads(unmarshall('{"key":{"BOOL":true}}'))
        assert result == {"key": True}

    def test_unmarshall_null(self):
        result = json.loads(unmarshall('{"key":{"NULL":true}}'))
        assert result == {"key": None}

    def test_unmarshall_list(self):
        result = json.loads(unmarshall('{"key":{"L":[{"S":"a"},{"S":"b"}]}}'))
        assert result == {"key": ["a", "b"]}

    def test_unmarshall_string_set(self):
        result = json.loads(unmarshall('{"key":{"SS":["a","b","c"]}}'))
        assert result == {"key": ["a", "b", "c"]}

    def test_unmarshall_number_set(self):
        result = json.loads(unmarshall('{"key":{"NS":["1","2","3"]}}'))
        assert result == {"key": [1, 2, 3]}

    def test_unmarshall_invalid_json_raises(self):
        with pytest.raises(ValueError):
            unmarshall("not json")

    # ── Binary types ────────────────────────────────────────────────────

    def test_unmarshall_binary(self):
        result = json.loads(unmarshall('{"key":{"B":"dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk"}}'))
        assert result == {"key": "dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk"}

    def test_unmarshall_binary_set(self):
        result = json.loads(unmarshall('{"key":{"BS":["U3Vubnk=","UmFpbnk=","U25vd3k="]}}'))
        assert result == {"key": ["U3Vubnk=", "UmFpbnk=", "U25vd3k="]}

    # ── Empty values ────────────────────────────────────────────────────

    def test_unmarshall_empty_string(self):
        result = json.loads(unmarshall('{"key":{"S":""}}'))
        assert result == {"key": ""}

    def test_unmarshall_empty_list(self):
        result = json.loads(unmarshall('{"key":{"L":[]}}'))
        assert result == {"key": []}

    def test_unmarshall_empty_map(self):
        result = json.loads(unmarshall('{"key":{"M":{}}}'))
        assert result == {"key": {}}

    # ── Number edge cases ───────────────────────────────────────────────

    def test_unmarshall_negative_number(self):
        result = json.loads(unmarshall('{"key":{"N":"-42"}}'))
        assert result == {"key": -42}

    def test_unmarshall_zero(self):
        result = json.loads(unmarshall('{"key":{"N":"0"}}'))
        assert result == {"key": 0}

    def test_unmarshall_scientific_notation(self):
        result = json.loads(unmarshall('{"key":{"N":"1.5E4"}}'))
        assert result == {"key": 15000.0}

    def test_unmarshall_number_set_with_floats(self):
        result = json.loads(unmarshall('{"key":{"NS":["42.2","-19","7.5","3.14"]}}'))
        assert result == {"key": [42.2, -19, 7.5, 3.14]}

    # ── Mixed / complex structures ──────────────────────────────────────

    def test_unmarshall_mixed_type_list(self):
        ddb = json.dumps({"key": {"L": [
            {"S": "hello"}, {"N": "42"}, {"BOOL": True},
            {"NULL": True}, {"M": {"k": {"S": "v"}}},
            {"L": [{"N": "1"}, {"N": "2"}]}
        ]}})
        result = json.loads(unmarshall(ddb))
        assert result == {"key": ["hello", 42, True, None, {"k": "v"}, [1, 2]]}

    def test_unmarshall_all_types_in_one_item(self):
        """Every DynamoDB type descriptor in a single Item."""
        result = json.loads(unmarshall(MARSHALLED_ALL_TYPES))
        expected = json.loads(UNMARSHALLED_ALL_TYPES)
        assert result == expected

    def test_unmarshall_get_item_response(self):
        """Simulates unmarshalling an AWS CLI get-item response."""
        result = json.loads(unmarshall(MARSHALLED_GET_ITEM))
        expected = json.loads(UNMARSHALLED_GET_ITEM)
        assert result == expected

    # ── Roundtrip tests ─────────────────────────────────────────────────

    def test_roundtrip_marshall_then_unmarshall(self):
        """Marshall, then unmarshall should yield the original."""
        from dynojson import marshall
        original = '{"name":"Alice","age":30,"active":true,"address":null}'
        result = unmarshall(marshall(original))
        assert json.loads(result) == json.loads(original)

    def test_roundtrip_nested(self):
        from dynojson import marshall
        original = json.dumps({"users": [
            {"name": "Alice", "scores": [100, 95, 88]},
            {"name": "Bob", "scores": [72, 81]},
        ]})
        result = unmarshall(marshall(original))
        assert json.loads(result) == json.loads(original)
