import json

import pytest

from xfintech.serde.common.dataformat import DataFormat
from xfintech.serde.common.error import (
    SerialiserFailedError,
    SerialiserInputTypeError,
    SerialiserNotSupportedError,
)
from xfintech.serde.serialiser.python import PythonSerialiser

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


def test_pythonserialiser_has_serialise_method():
    """Test PythonSerialiser has serialise method"""
    assert hasattr(PythonSerialiser, "serialise")
    assert callable(PythonSerialiser.serialise)


def test_pythonserialiser_has_to_json_method():
    """Test PythonSerialiser has to_json method"""
    assert hasattr(PythonSerialiser, "to_json")
    assert callable(PythonSerialiser.to_json)


def test_pythonserialiser_methods_are_static():
    """Test all methods are static methods"""
    assert isinstance(PythonSerialiser.__dict__["serialise"], staticmethod)
    assert isinstance(PythonSerialiser.__dict__["to_json"], staticmethod)


# ==================== serialise() Method Tests ====================


def test_serialise_simple_dict():
    """Test serialise with simple dictionary"""
    data = {"key": "value", "number": 42}
    result = PythonSerialiser.serialise(data, DataFormat.JSON)
    assert isinstance(result, bytes)
    assert b"key" in result and b"value" in result


def test_serialise_with_json_format_enum():
    """Test serialise with JSON DataFormat enum"""
    data = {"a": 1, "b": 2}
    result = PythonSerialiser.serialise(data, DataFormat.JSON)
    assert isinstance(result, bytes)


def test_serialise_with_json_format_string_lowercase():
    """Test serialise with 'json' string"""
    data = {"a": 1}
    result = PythonSerialiser.serialise(data, "json")
    assert isinstance(result, bytes)


def test_serialise_with_json_format_string_uppercase():
    """Test serialise with 'JSON' string"""
    data = {"a": 1}
    result = PythonSerialiser.serialise(data, "JSON")
    assert isinstance(result, bytes)


def test_serialise_with_json_format_string_mixed_case():
    """Test serialise with 'Json' string"""
    data = {"a": 1}
    result = PythonSerialiser.serialise(data, "Json")
    assert isinstance(result, bytes)


def test_serialise_returns_bytes():
    """Test serialise always returns bytes"""
    data = {"test": "data"}
    result = PythonSerialiser.serialise(data, DataFormat.JSON)
    assert isinstance(result, bytes)


def test_serialise_returns_valid_json():
    """Test serialise returns valid JSON bytes"""
    data = {"key1": "value1", "key2": 123}
    result = PythonSerialiser.serialise(data, DataFormat.JSON)

    # Verify it can be parsed back
    parsed = json.loads(result.decode("utf-8"))
    assert parsed == data


def test_serialise_nested_dict():
    """Test serialise with nested dictionary"""
    data = {"outer": {"inner": {"deep": "value"}}}
    result = PythonSerialiser.serialise(data, DataFormat.JSON)
    assert isinstance(result, bytes)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed["outer"]["inner"]["deep"] == "value"


def test_serialise_dict_with_list():
    """Test serialise with dictionary containing list"""
    data = {"items": [1, 2, 3, 4, 5]}
    result = PythonSerialiser.serialise(data, DataFormat.JSON)
    assert isinstance(result, bytes)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed["items"] == [1, 2, 3, 4, 5]


def test_serialise_dict_with_various_types():
    """Test serialise with various data types"""
    data = {
        "string": "text",
        "integer": 42,
        "float": 3.14,
        "boolean": True,
        "null": None,
        "list": [1, 2, 3],
        "nested": {"key": "value"},
    }
    result = PythonSerialiser.serialise(data, DataFormat.JSON)
    assert isinstance(result, bytes)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed == data


def test_serialise_empty_dict():
    """Test serialise with empty dictionary"""
    data = {}
    result = PythonSerialiser.serialise(data, DataFormat.JSON)
    assert isinstance(result, bytes)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed == {}


def test_serialise_large_dict():
    """Test serialise with large dictionary"""
    data = {f"key_{i}": i for i in range(1000)}
    result = PythonSerialiser.serialise(data, DataFormat.JSON)
    assert isinstance(result, bytes)
    assert len(result) > 0


def test_serialise_raises_on_non_dict_list():
    """Test serialise raises error for list input"""
    with pytest.raises(SerialiserInputTypeError) as exc_info:
        PythonSerialiser.serialise([1, 2, 3], DataFormat.JSON)
    assert "expects dict" in str(exc_info.value)


def test_serialise_raises_on_non_dict_string():
    """Test serialise raises error for string input"""
    with pytest.raises(SerialiserInputTypeError):
        PythonSerialiser.serialise("not a dict", DataFormat.JSON)


def test_serialise_raises_on_non_dict_int():
    """Test serialise raises error for integer input"""
    with pytest.raises(SerialiserInputTypeError):
        PythonSerialiser.serialise(123, DataFormat.JSON)


def test_serialise_raises_on_non_dict_bytes():
    """Test serialise raises error for bytes input"""
    with pytest.raises(SerialiserInputTypeError):
        PythonSerialiser.serialise(b"bytes", DataFormat.JSON)


def test_serialise_raises_on_none():
    """Test serialise raises error for None input"""
    with pytest.raises(SerialiserInputTypeError):
        PythonSerialiser.serialise(None, DataFormat.JSON)


def test_serialise_raises_on_unsupported_format_parquet():
    """Test serialise raises error for PARQUET format"""
    data = {"a": 1}
    with pytest.raises(SerialiserNotSupportedError) as exc_info:
        PythonSerialiser.serialise(data, DataFormat.PARQUET)
    assert "Unsupported DataFormat" in str(exc_info.value)


def test_serialise_raises_on_unsupported_format_csv():
    """Test serialise raises error for CSV format"""
    data = {"a": 1}
    with pytest.raises(SerialiserNotSupportedError):
        PythonSerialiser.serialise(data, DataFormat.CSV)


def test_serialise_raises_on_invalid_format_string():
    """Test serialise raises error for invalid format string"""
    data = {"a": 1}
    with pytest.raises(ValueError) as exc_info:
        PythonSerialiser.serialise(data, "xml")
    assert "Unknown DataFormat" in str(exc_info.value)


# ==================== to_json() Method Tests ====================


def test_to_json_basic():
    """Test to_json with basic dictionary"""
    data = {"key": "value"}
    result = PythonSerialiser.to_json(data)
    assert isinstance(result, bytes)


def test_to_json_returns_bytes():
    """Test to_json returns bytes type"""
    data = {"test": "data"}
    result = PythonSerialiser.to_json(data)
    assert isinstance(result, bytes)


def test_to_json_returns_valid_json():
    """Test to_json returns valid JSON"""
    data = {"name": "test", "value": 123}
    result = PythonSerialiser.to_json(data)

    # Verify it's valid JSON
    parsed = json.loads(result.decode("utf-8"))
    assert parsed == data


def test_to_json_with_utf8_encoding():
    """Test to_json with UTF-8 encoding (default)"""
    data = {"text": "Hello 世界"}
    result = PythonSerialiser.to_json(data)
    assert isinstance(result, bytes)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed["text"] == "Hello 世界"


def test_to_json_with_custom_encoding():
    """Test to_json with custom encoding"""
    data = {"text": "test"}
    result = PythonSerialiser.to_json(data, encoding="utf-8")
    assert isinstance(result, bytes)


def test_to_json_default_formatting():
    """Test to_json uses default formatting (ensure_ascii=False, indent=4, sort_keys=True)"""
    data = {"z": 1, "a": 2, "m": 3}
    result = PythonSerialiser.to_json(data)
    text = result.decode("utf-8")

    # Check that keys are sorted (based on default sort_keys=True)
    assert text.index('"a"') < text.index('"m"') < text.index('"z"')

    # Check that it's indented (indent=4)
    assert "\n" in text


def test_to_json_with_unicode_characters():
    """Test to_json preserves Unicode (ensure_ascii=False by default)"""
    data = {"chinese": "中文", "russian": "Русский", "arabic": "عربي"}
    result = PythonSerialiser.to_json(data)
    text = result.decode("utf-8")

    # Verify Unicode is preserved (not escaped)
    assert "中文" in text
    assert "Русский" in text
    assert "عربي" in text


def test_to_json_nested_structure():
    """Test to_json with nested structure"""
    data = {"level1": {"level2": {"level3": "deep value"}}}
    result = PythonSerialiser.to_json(data)
    assert isinstance(result, bytes)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed["level1"]["level2"]["level3"] == "deep value"


def test_to_json_with_list_values():
    """Test to_json with list values"""
    data = {"numbers": [1, 2, 3, 4, 5], "strings": ["a", "b", "c"]}
    result = PythonSerialiser.to_json(data)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed["numbers"] == [1, 2, 3, 4, 5]
    assert parsed["strings"] == ["a", "b", "c"]


def test_to_json_with_null_value():
    """Test to_json with None/null value"""
    data = {"key": None}
    result = PythonSerialiser.to_json(data)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed["key"] is None


def test_to_json_with_boolean_values():
    """Test to_json with boolean values"""
    data = {"true_val": True, "false_val": False}
    result = PythonSerialiser.to_json(data)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed["true_val"] is True
    assert parsed["false_val"] is False


def test_to_json_with_numeric_values():
    """Test to_json with various numeric types"""
    data = {"int": 42, "float": 3.14159, "negative": -100, "zero": 0}
    result = PythonSerialiser.to_json(data)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed["int"] == 42
    assert abs(parsed["float"] - 3.14159) < 0.00001
    assert parsed["negative"] == -100
    assert parsed["zero"] == 0


def test_to_json_empty_dict():
    """Test to_json with empty dictionary"""
    data = {}
    result = PythonSerialiser.to_json(data)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed == {}


def test_to_json_single_key():
    """Test to_json with single key dictionary"""
    data = {"only": "one"}
    result = PythonSerialiser.to_json(data)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed == data


def test_to_json_special_characters_in_values():
    """Test to_json with special characters"""
    data = {
        "quotes": 'He said "hello"',
        "newlines": "line1\nline2",
        "tabs": "col1\tcol2",
        "backslash": "path\\to\\file",
    }
    result = PythonSerialiser.to_json(data)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed == data


def test_to_json_special_characters_in_keys():
    """Test to_json with special characters in keys"""
    data = {"key with spaces": "value1", "key-with-dashes": "value2", "key.with.dots": "value3"}
    result = PythonSerialiser.to_json(data)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed == data


def test_to_json_large_dict():
    """Test to_json with large dictionary"""
    data = {f"key_{i}": f"value_{i}" for i in range(1000)}
    result = PythonSerialiser.to_json(data)
    assert isinstance(result, bytes)
    parsed = json.loads(result.decode("utf-8"))
    assert len(parsed) == 1000


def test_to_json_deeply_nested():
    """Test to_json with deeply nested structure"""
    data = {"a": {"b": {"c": {"d": {"e": {"f": "deep"}}}}}}
    result = PythonSerialiser.to_json(data)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed["a"]["b"]["c"]["d"]["e"]["f"] == "deep"


def test_to_json_mixed_nesting():
    """Test to_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]},
    }
    result = PythonSerialiser.to_json(data)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed == data


# ==================== Error Handling Tests ====================


def test_to_json_raises_on_non_serializable():
    """Test to_json raises error for non-JSON-serializable objects"""
    import datetime

    data = {"date": datetime.datetime.now()}

    with pytest.raises(SerialiserFailedError) as exc_info:
        PythonSerialiser.to_json(data)
    assert "Failed to serialize data" in str(exc_info.value)


def test_to_json_raises_on_circular_reference():
    """Test to_json raises error for circular reference"""
    data = {"key": "value"}
    data["self"] = data  # Create circular reference

    with pytest.raises(SerialiserFailedError):
        PythonSerialiser.to_json(data)


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


def test_serialise_and_to_json_produce_same_result():
    """Test serialise and to_json produce same result"""
    data = {"a": 1, "b": 2}
    result1 = PythonSerialiser.serialise(data, DataFormat.JSON)
    result2 = PythonSerialiser.to_json(data)

    # Parse both to compare (formatting might differ slightly)
    parsed1 = json.loads(result1.decode("utf-8"))
    parsed2 = json.loads(result2.decode("utf-8"))
    assert parsed1 == parsed2


def test_serialise_same_dict_multiple_times():
    """Test serialising same dict multiple times produces consistent results"""
    data = {"key": "value", "number": 42}
    result1 = PythonSerialiser.serialise(data, DataFormat.JSON)
    result2 = PythonSerialiser.serialise(data, DataFormat.JSON)

    # Parse to compare (accounting for potential ordering differences)
    parsed1 = json.loads(result1.decode("utf-8"))
    parsed2 = json.loads(result2.decode("utf-8"))
    assert parsed1 == parsed2


def test_roundtrip_serialise_deserialise():
    """Test data can be serialised and deserialised back"""
    from xfintech.serde.deserialiser.python import PythonDeserialiser

    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_dict_with_empty_strings():
    """Test dict with empty string values"""
    data = {"empty": "", "not_empty": "value"}
    result = PythonSerialiser.serialise(data, DataFormat.JSON)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed["empty"] == ""
    assert parsed["not_empty"] == "value"


def test_dict_with_empty_lists():
    """Test dict with empty list values"""
    data = {"empty_list": [], "list": [1, 2]}
    result = PythonSerialiser.serialise(data, DataFormat.JSON)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed["empty_list"] == []
    assert parsed["list"] == [1, 2]


def test_dict_with_empty_nested_dicts():
    """Test dict with empty nested dictionaries"""
    data = {"outer": {}, "another": {"inner": {}}}
    result = PythonSerialiser.serialise(data, DataFormat.JSON)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed["outer"] == {}
    assert parsed["another"]["inner"] == {}


def test_dict_with_numeric_string_keys():
    """Test dict with numeric string keys"""
    data = {"1": "one", "2": "two", "10": "ten"}
    result = PythonSerialiser.serialise(data, DataFormat.JSON)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed["1"] == "one"
    assert parsed["2"] == "two"


def test_dict_with_very_long_strings():
    """Test dict with very long string values"""
    long_string = "x" * 100000
    data = {"long": long_string}
    result = PythonSerialiser.serialise(data, DataFormat.JSON)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed["long"] == long_string


def test_dict_with_multiple_unicode_scripts():
    """Test dict with multiple Unicode scripts"""
    data = {
        "latin": "Hello",
        "cyrillic": "Привет",
        "arabic": "مرحبا",
        "chinese": "你好",
        "japanese": "こんにちは",
        "korean": "안녕하세요",
        "emoji": "👋🌍",
    }
    result = PythonSerialiser.serialise(data, DataFormat.JSON)
    parsed = json.loads(result.decode("utf-8"))
    assert parsed == data


def test_output_encoding_consistency():
    """Test output is consistently UTF-8 encoded"""
    data = {"text": "test"}
    result = PythonSerialiser.to_json(data)

    # Should decode without error
    text = result.decode("utf-8")
    assert isinstance(text, str)
