import json

import pytest

from xfintech.serde.common.dataformat import DataFormat
from xfintech.serde.common.error import (
    DeserialiserFailedError,
    DeserialiserInputTypeError,
    DeserialiserNotSupportedError,
)
from xfintech.serde.deserialiser.python import PythonDeserialiser
from xfintech.serde.serialiser.python import PythonSerialiser

# ==================== Class Structure Tests ====================


def test_pythondeserialiser_has_deserialise_method():
    """Test PythonDeserialiser has deserialise method"""
    assert hasattr(PythonDeserialiser, "deserialise")
    assert callable(PythonDeserialiser.deserialise)


def test_pythondeserialiser_has_from_json_method():
    """Test PythonDeserialiser has from_json method"""
    assert hasattr(PythonDeserialiser, "from_json")
    assert callable(PythonDeserialiser.from_json)


def test_pythondeserialiser_methods_are_static():
    """Test all methods are static methods"""
    assert isinstance(PythonDeserialiser.__dict__["deserialise"], staticmethod)
    assert isinstance(PythonDeserialiser.__dict__["from_json"], staticmethod)


# ==================== deserialise() Method Tests ====================


def test_deserialise_simple_dict():
    """Test deserialise with simple dictionary"""
    data = {"key": "value", "number": 42}
    bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
    result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
    assert isinstance(result, dict)
    assert result == data


def test_deserialise_with_json_format_enum():
    """Test deserialise with JSON DataFormat enum"""
    data = {"a": 1, "b": 2}
    bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
    result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
    assert result == data


def test_deserialise_with_json_format_string_lowercase():
    """Test deserialise with 'json' string"""
    data = {"a": 1}
    bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
    result = PythonDeserialiser.deserialise(bytes_data, "json")
    assert result == data


def test_deserialise_with_json_format_string_uppercase():
    """Test deserialise with 'JSON' string"""
    data = {"a": 1}
    bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
    result = PythonDeserialiser.deserialise(bytes_data, "JSON")
    assert result == data


def test_deserialise_with_json_format_string_mixed_case():
    """Test deserialise with 'Json' string"""
    data = {"a": 1}
    bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
    result = PythonDeserialiser.deserialise(bytes_data, "Json")
    assert result == data


def test_deserialise_returns_dict():
    """Test deserialise returns dict type"""
    data = {"test": "data"}
    bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
    result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
    assert isinstance(result, dict)


def test_deserialise_nested_dict():
    """Test deserialise with nested dictionary"""
    data = {"outer": {"inner": {"deep": "value"}}}
    bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
    result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
    assert result["outer"]["inner"]["deep"] == "value"


def test_deserialise_dict_with_list():
    """Test deserialise with dictionary containing list"""
    data = {"items": [1, 2, 3, 4, 5]}
    bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
    result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
    assert result["items"] == [1, 2, 3, 4, 5]


def test_deserialise_dict_with_various_types():
    """Test deserialise with various data types"""
    data = {
        "string": "text",
        "integer": 42,
        "float": 3.14,
        "boolean": True,
        "null": None,
        "list": [1, 2, 3],
        "nested": {"key": "value"},
    }
    bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
    result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
    assert result == data


def test_deserialise_empty_dict():
    """Test deserialise with empty dictionary"""
    data = {}
    bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
    result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
    assert result == {}


def test_deserialise_large_dict():
    """Test deserialise with large dictionary"""
    data = {f"key_{i}": i for i in range(1000)}
    bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
    result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
    assert len(result) == 1000


def test_deserialise_raises_on_non_bytes_list():
    """Test deserialise raises error for list input"""
    with pytest.raises(DeserialiserInputTypeError) as exc_info:
        PythonDeserialiser.deserialise([1, 2, 3], DataFormat.JSON)
    assert "expects bytes" in str(exc_info.value)


def test_deserialise_raises_on_non_bytes_string():
    """Test deserialise raises error for string input"""
    with pytest.raises(DeserialiserInputTypeError):
        PythonDeserialiser.deserialise("not bytes", DataFormat.JSON)


def test_deserialise_raises_on_non_bytes_dict():
    """Test deserialise raises error for dict input"""
    with pytest.raises(DeserialiserInputTypeError):
        PythonDeserialiser.deserialise({"a": 1}, DataFormat.JSON)


def test_deserialise_raises_on_non_bytes_int():
    """Test deserialise raises error for integer input"""
    with pytest.raises(DeserialiserInputTypeError):
        PythonDeserialiser.deserialise(123, DataFormat.JSON)


def test_deserialise_raises_on_none():
    """Test deserialise raises error for None input"""
    with pytest.raises(DeserialiserInputTypeError):
        PythonDeserialiser.deserialise(None, DataFormat.JSON)


def test_deserialise_raises_on_unsupported_format_parquet():
    """Test deserialise raises error for PARQUET format"""
    bytes_data = b'{"a": 1}'
    with pytest.raises(DeserialiserNotSupportedError) as exc_info:
        PythonDeserialiser.deserialise(bytes_data, DataFormat.PARQUET)
    assert "Unsupported DataFormat" in str(exc_info.value)


def test_deserialise_raises_on_unsupported_format_csv():
    """Test deserialise raises error for CSV format"""
    bytes_data = b'{"a": 1}'
    with pytest.raises(DeserialiserNotSupportedError):
        PythonDeserialiser.deserialise(bytes_data, DataFormat.CSV)


def test_deserialise_raises_on_invalid_format_string():
    """Test deserialise raises error for invalid format string"""
    bytes_data = b'{"a": 1}'
    with pytest.raises(ValueError) as exc_info:
        PythonDeserialiser.deserialise(bytes_data, "xml")
    assert "Unknown DataFormat" in str(exc_info.value)


def test_deserialise_raises_on_invalid_json():
    """Test deserialise raises error for invalid JSON"""
    with pytest.raises(DeserialiserFailedError):
        PythonDeserialiser.deserialise(b"not valid json", DataFormat.JSON)


# ==================== from_json() Method Tests ====================


def test_from_json_basic():
    """Test from_json with basic dictionary"""
    data = {"key": "value"}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert isinstance(result, dict)
    assert result == data


def test_from_json_returns_dict():
    """Test from_json returns dict type"""
    data = {"test": "data"}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert isinstance(result, dict)


def test_from_json_with_utf8_encoding():
    """Test from_json with UTF-8 encoding (default)"""
    data = {"text": "Hello 世界"}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result["text"] == "Hello 世界"


def test_from_json_with_custom_encoding():
    """Test from_json with custom encoding"""
    data = {"text": "test"}
    bytes_data = PythonSerialiser.to_json(data, encoding="utf-8")
    result = PythonDeserialiser.from_json(bytes_data, encoding="utf-8")
    assert result == data


def test_from_json_with_unicode_characters():
    """Test from_json preserves Unicode"""
    data = {"chinese": "中文", "russian": "Русский", "arabic": "عربي"}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result == data


def test_from_json_nested_structure():
    """Test from_json with nested structure"""
    data = {"level1": {"level2": {"level3": "deep value"}}}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result["level1"]["level2"]["level3"] == "deep value"


def test_from_json_with_list_values():
    """Test from_json with list values"""
    data = {"numbers": [1, 2, 3, 4, 5], "strings": ["a", "b", "c"]}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result["numbers"] == [1, 2, 3, 4, 5]
    assert result["strings"] == ["a", "b", "c"]


def test_from_json_with_null_value():
    """Test from_json with None/null value"""
    data = {"key": None}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result["key"] is None


def test_from_json_with_boolean_values():
    """Test from_json with boolean values"""
    data = {"true_val": True, "false_val": False}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result["true_val"] is True
    assert result["false_val"] is False


def test_from_json_with_numeric_values():
    """Test from_json with various numeric types"""
    data = {"int": 42, "float": 3.14159, "negative": -100, "zero": 0}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result["int"] == 42
    assert abs(result["float"] - 3.14159) < 0.00001
    assert result["negative"] == -100
    assert result["zero"] == 0


def test_from_json_empty_dict():
    """Test from_json with empty dictionary"""
    data = {}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result == {}


def test_from_json_single_key():
    """Test from_json with single key dictionary"""
    data = {"only": "one"}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result == data


def test_from_json_special_characters_in_values():
    """Test from_json with special characters"""
    data = {
        "quotes": 'He said "hello"',
        "newlines": "line1\nline2",
        "tabs": "col1\tcol2",
        "backslash": "path\\to\\file",
    }
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result == data


def test_from_json_special_characters_in_keys():
    """Test from_json with special characters in keys"""
    data = {"key with spaces": "value1", "key-with-dashes": "value2", "key.with.dots": "value3"}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result == data


def test_from_json_large_dict():
    """Test from_json with large dictionary"""
    data = {f"key_{i}": f"value_{i}" for i in range(1000)}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert len(result) == 1000


def test_from_json_deeply_nested():
    """Test from_json with deeply nested structure"""
    data = {"a": {"b": {"c": {"d": {"e": {"f": "deep"}}}}}}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result["a"]["b"]["c"]["d"]["e"]["f"] == "deep"


def test_from_json_mixed_nesting():
    """Test from_json with mixed nested structures"""
    data = {
        "list_of_dicts": [{"id": 1, "name": "first"}, {"id": 2, "name": "second"}],
        "dict_of_lists": {"group1": [1, 2, 3], "group2": [4, 5, 6]},
    }
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result == data


def test_from_json_raises_on_invalid_json():
    """Test from_json raises error for invalid JSON"""
    with pytest.raises(DeserialiserFailedError):
        PythonDeserialiser.from_json(b"not valid json")


def test_from_json_raises_on_malformed_json():
    """Test from_json raises error for malformed JSON"""
    with pytest.raises(DeserialiserFailedError):
        PythonDeserialiser.from_json(b'{"key": }')


def test_from_json_can_return_list():
    """Test from_json can deserialise to list (not just dict)"""
    bytes_data = b"[1, 2, 3]"
    result = PythonDeserialiser.from_json(bytes_data)
    assert result == [1, 2, 3]


def test_from_json_can_return_string():
    """Test from_json can deserialise to string"""
    bytes_data = b'"hello"'
    result = PythonDeserialiser.from_json(bytes_data)
    assert result == "hello"


def test_from_json_can_return_number():
    """Test from_json can deserialise to number"""
    bytes_data = b"42"
    result = PythonDeserialiser.from_json(bytes_data)
    assert result == 42


def test_from_json_can_return_boolean():
    """Test from_json can deserialise to boolean"""
    bytes_data = b"true"
    result = PythonDeserialiser.from_json(bytes_data)
    assert result is True


def test_from_json_can_return_null():
    """Test from_json can deserialise to None"""
    bytes_data = b"null"
    result = PythonDeserialiser.from_json(bytes_data)
    assert result is None


# ==================== Roundtrip Tests ====================


def test_roundtrip_serialise_deserialise():
    """Test data can be serialised and deserialised back"""
    original = {"name": "test", "value": 123, "nested": {"key": "value"}, "list": [1, 2, 3]}

    # Serialise
    bytes_data = PythonSerialiser.serialise(original, DataFormat.JSON)

    # Deserialise
    result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)

    assert result == original


def test_roundtrip_to_json_from_json():
    """Test to_json and from_json are inverses"""
    original = {"key1": "value1", "key2": 123, "key3": [1, 2, 3]}

    bytes_data = PythonSerialiser.to_json(original)
    result = PythonDeserialiser.from_json(bytes_data)

    assert result == original


def test_roundtrip_preserves_types():
    """Test roundtrip preserves data types"""
    original = {
        "string": "text",
        "int": 42,
        "float": 3.14,
        "bool": True,
        "null": None,
        "list": [1, 2, 3],
        "dict": {"nested": "value"},
    }

    bytes_data = PythonSerialiser.to_json(original)
    result = PythonDeserialiser.from_json(bytes_data)

    assert isinstance(result["string"], str)
    assert isinstance(result["int"], int)
    assert isinstance(result["float"], float)
    assert isinstance(result["bool"], bool)
    assert result["null"] is None
    assert isinstance(result["list"], list)
    assert isinstance(result["dict"], dict)


def test_roundtrip_multiple_times():
    """Test multiple roundtrips produce consistent results"""
    original = {"data": "test", "value": 42}

    # First roundtrip
    bytes1 = PythonSerialiser.to_json(original)
    result1 = PythonDeserialiser.from_json(bytes1)

    # Second roundtrip
    bytes2 = PythonSerialiser.to_json(result1)
    result2 = PythonDeserialiser.from_json(bytes2)

    assert result1 == original
    assert result2 == original


def test_roundtrip_with_unicode():
    """Test roundtrip with Unicode data"""
    original = {
        "latin": "Hello",
        "cyrillic": "Привет",
        "arabic": "مرحبا",
        "chinese": "你好",
        "japanese": "こんにちは",
        "korean": "안녕하세요",
        "emoji": "👋🌍",
    }

    bytes_data = PythonSerialiser.to_json(original)
    result = PythonDeserialiser.from_json(bytes_data)

    assert result == original


def test_roundtrip_empty_dict():
    """Test roundtrip with empty dictionary"""
    original = {}
    bytes_data = PythonSerialiser.to_json(original)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result == {}


def test_roundtrip_with_empty_strings():
    """Test roundtrip with empty string values"""
    original = {"empty": "", "not_empty": "value"}
    bytes_data = PythonSerialiser.to_json(original)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result == original


def test_roundtrip_with_empty_lists():
    """Test roundtrip with empty list values"""
    original = {"empty_list": [], "list": [1, 2]}
    bytes_data = PythonSerialiser.to_json(original)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result == original


def test_roundtrip_with_empty_nested_dicts():
    """Test roundtrip with empty nested dictionaries"""
    original = {"outer": {}, "another": {"inner": {}}}
    bytes_data = PythonSerialiser.to_json(original)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result == original


# ==================== Integration and Edge Case Tests ====================


def test_deserialise_and_from_json_produce_same_result():
    """Test deserialise and from_json produce same result"""
    bytes_data = b'{"a": 1, "b": 2}'
    result1 = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
    result2 = PythonDeserialiser.from_json(bytes_data)
    assert result1 == result2


def test_from_json_with_very_long_strings():
    """Test from_json with very long string values"""
    long_string = "x" * 100000
    data = {"long": long_string}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result["long"] == long_string


def test_from_json_with_numeric_string_keys():
    """Test from_json with numeric string keys"""
    data = {"1": "one", "2": "two", "10": "ten"}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert result["1"] == "one"
    assert result["2"] == "two"
    assert result["10"] == "ten"


def test_from_json_preserves_order():
    """Test from_json preserves key order (in Python 3.7+)"""
    data = {"z": 1, "a": 2, "m": 3}
    bytes_data = json.dumps(data).encode("utf-8")
    result = PythonDeserialiser.from_json(bytes_data)
    # Note: Order preservation depends on json.dumps maintaining order
    assert list(result.keys()) == ["z", "a", "m"]


def test_output_decoding_consistency():
    """Test output is consistently decoded"""
    data = {"text": "test"}
    bytes_data = PythonSerialiser.to_json(data)
    result = PythonDeserialiser.from_json(bytes_data)
    assert isinstance(result, dict)
    assert isinstance(result["text"], str)


def test_from_json_with_escaped_characters():
    """Test from_json handles escaped characters"""
    bytes_data = b'{"text": "line1\\nline2"}'
    result = PythonDeserialiser.from_json(bytes_data)
    assert result["text"] == "line1\nline2"


def test_from_json_with_unicode_escape_sequences():
    """Test from_json handles Unicode escape sequences"""
    bytes_data = b'{"text": "\\u4e2d\\u6587"}'
    result = PythonDeserialiser.from_json(bytes_data)
    assert result["text"] == "中文"
