import tempfile

import pytest

from xdatawork.connect.connectkind import ConnectKind
from xdatawork.connect.connectref import ConnectRef

# ==================== Initialization Tests ====================


def test_connectref_init_with_location_only():
    """Test ConnectRef initialization with location only"""
    ref = ConnectRef(location="s3://bucket/key")

    assert ref.location == "s3://bucket/key"
    assert ref.kind is None


def test_connectref_init_with_location_and_kind():
    """Test ConnectRef initialization with location and kind"""
    ref = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)

    assert ref.location == "s3://bucket/key"
    assert ref.kind == ConnectKind.S3


def test_connectref_init_with_string_kind():
    """Test ConnectRef initialization with string kind"""
    ref = ConnectRef(location="/path/to/file", kind="macos")

    assert ref.location == "/path/to/file"
    assert ref.kind == "macos"


def test_connectref_init_with_empty_location():
    """Test ConnectRef with empty location"""
    ref = ConnectRef(location="", kind=ConnectKind.S3)

    assert ref.location == ""
    assert ref.kind == ConnectKind.S3


# ==================== from_dict() Method Tests ====================


def test_from_dict_with_location_only():
    """Test from_dict with location only"""
    data = {"location": "s3://bucket/path"}
    ref = ConnectRef.from_dict(data)

    assert ref.location == "s3://bucket/path"
    assert ref.kind is None


def test_from_dict_with_location_and_kind():
    """Test from_dict with location and kind"""
    data = {"location": "s3://bucket/path", "kind": "s3"}
    ref = ConnectRef.from_dict(data)

    assert ref.location == "s3://bucket/path"
    assert ref.kind == ConnectKind.S3


def test_from_dict_with_kind_enum():
    """Test from_dict with ConnectKind enum"""
    data = {"location": "/path", "kind": ConnectKind.MACOS}
    ref = ConnectRef.from_dict(data)

    assert ref.location == "/path"
    assert ref.kind == ConnectKind.MACOS


def test_from_dict_missing_location():
    """Test from_dict raises KeyError when location is missing"""
    data = {"kind": "s3"}

    with pytest.raises(KeyError, match="'location' is required"):
        ConnectRef.from_dict(data)


def test_from_dict_with_none_kind():
    """Test from_dict with None kind"""
    data = {"location": "s3://bucket/key", "kind": None}
    ref = ConnectRef.from_dict(data)

    assert ref.location == "s3://bucket/key"
    assert ref.kind is None


def test_from_dict_with_invalid_kind():
    """Test from_dict with invalid kind raises error"""
    data = {"location": "s3://bucket/key", "kind": "invalid"}

    with pytest.raises(ValueError, match="Unknown ConnectKind"):
        ConnectRef.from_dict(data)


def test_from_dict_with_extra_fields():
    """Test from_dict ignores extra fields"""
    data = {"location": "s3://bucket", "kind": "s3", "extra": "ignored"}
    ref = ConnectRef.from_dict(data)

    assert ref.location == "s3://bucket"
    assert ref.kind == ConnectKind.S3


# ==================== to_dict() Method Tests ====================


def test_to_dict_with_location_only():
    """Test to_dict with location only"""
    ref = ConnectRef(location="s3://bucket/key", kind=None)
    result = ref.to_dict()

    assert result == {"location": "s3://bucket/key"}


def test_to_dict_with_location_and_kind():
    """Test to_dict with location and kind"""
    ref = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)
    result = ref.to_dict()

    assert result == {"location": "s3://bucket/key", "kind": "s3"}


def test_to_dict_returns_dict():
    """Test to_dict returns a dictionary"""
    ref = ConnectRef(location="/path", kind=ConnectKind.MACOS)
    result = ref.to_dict()

    assert isinstance(result, dict)


def test_to_dict_roundtrip():
    """Test from_dict and to_dict roundtrip"""
    original = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)
    data = original.to_dict()
    restored = ConnectRef.from_dict(data)

    assert restored.location == original.location
    assert restored.kind == original.kind


# ==================== describe() Method Tests ====================


def test_describe_with_location_only():
    """Test describe with location only"""
    ref = ConnectRef(location="s3://bucket/key", kind=None)
    result = ref.describe()

    assert result == {"location": "s3://bucket/key"}


def test_describe_with_location_and_kind():
    """Test describe with location and kind"""
    ref = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)
    result = ref.describe()

    assert result == {"location": "s3://bucket/key", "kind": "s3"}


def test_describe_returns_dict():
    """Test describe returns a dictionary"""
    ref = ConnectRef(location="/path", kind=ConnectKind.MACOS)
    result = ref.describe()

    assert isinstance(result, dict)


# ==================== to() Method Tests ====================


def test_to_with_relative_path():
    """Test to() method with relative path"""
    with tempfile.TemporaryDirectory():
        ref = ConnectRef(location="s3://bucket/base", kind=ConnectKind.S3)
        result = ref.to("subdir/file.txt")

        assert result.location == "s3://bucket/base/subdir/file.txt"
        assert result.kind == ConnectKind.S3


def test_to_removes_trailing_slash():
    """Test to() removes trailing slash from base"""
    ref = ConnectRef(location="s3://bucket/base/", kind=ConnectKind.S3)
    result = ref.to("file.txt")

    assert result.location == "s3://bucket/base/file.txt"


def test_to_removes_leading_slash():
    """Test to() removes leading slash from relative path"""
    ref = ConnectRef(location="s3://bucket/base", kind=ConnectKind.S3)
    result = ref.to("/subdir/file.txt")

    assert result.location == "s3://bucket/base/subdir/file.txt"


def test_to_with_both_slashes():
    """Test to() handles both trailing and leading slashes"""
    ref = ConnectRef(location="s3://bucket/base/", kind=ConnectKind.S3)
    result = ref.to("/file.txt")

    assert result.location == "s3://bucket/base/file.txt"


def test_to_preserves_kind():
    """Test to() preserves kind"""
    ref = ConnectRef(location="/path", kind=ConnectKind.MACOS)
    result = ref.to("subdir")

    assert result.kind == ConnectKind.MACOS


def test_to_returns_new_instance():
    """Test to() returns a new ConnectRef instance"""
    ref = ConnectRef(location="s3://bucket", kind=ConnectKind.S3)
    result = ref.to("file.txt")

    assert result is not ref
    assert isinstance(result, ConnectRef)


def test_to_with_empty_relative():
    """Test to() with empty string"""
    ref = ConnectRef(location="s3://bucket/base", kind=ConnectKind.S3)
    result = ref.to("")

    assert result.location == "s3://bucket/base/"


def test_to_chaining():
    """Test chaining to() calls"""
    ref = ConnectRef(location="s3://bucket", kind=ConnectKind.S3)
    result = ref.to("dir1").to("dir2").to("file.txt")

    assert result.location == "s3://bucket/dir1/dir2/file.txt"


# ==================== Equality Tests ====================


def test_equality_same_values():
    """Test equality with same values"""
    ref1 = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)
    ref2 = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)

    assert ref1 == ref2


def test_equality_different_location():
    """Test inequality with different location"""
    ref1 = ConnectRef(location="s3://bucket/key1", kind=ConnectKind.S3)
    ref2 = ConnectRef(location="s3://bucket/key2", kind=ConnectKind.S3)

    assert ref1 != ref2


def test_equality_different_kind():
    """Test inequality with different kind"""
    ref1 = ConnectRef(location="/path", kind=ConnectKind.S3)
    ref2 = ConnectRef(location="/path", kind=ConnectKind.MACOS)

    assert ref1 != ref2


def test_equality_with_none_kind():
    """Test equality when kind is None"""
    ref1 = ConnectRef(location="s3://bucket/key", kind=None)
    ref2 = ConnectRef(location="s3://bucket/key", kind=None)

    assert ref1 == ref2


def test_equality_one_none_kind():
    """Test inequality when one kind is None"""
    ref1 = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)
    ref2 = ConnectRef(location="s3://bucket/key", kind=None)

    assert ref1 != ref2


def test_equality_with_different_type():
    """Test inequality with different type returns NotImplemented"""
    ref = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)

    assert ref != "s3://bucket/key"
    assert ref != {"location": "s3://bucket/key"}


# ==================== Hash Tests ====================


def test_hash_same_values():
    """Test hash is same for equal objects"""
    ref1 = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)
    ref2 = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)

    assert hash(ref1) == hash(ref2)


def test_hash_different_location():
    """Test hash differs for different location"""
    ref1 = ConnectRef(location="s3://bucket/key1", kind=ConnectKind.S3)
    ref2 = ConnectRef(location="s3://bucket/key2", kind=ConnectKind.S3)

    assert hash(ref1) != hash(ref2)


def test_hash_in_set():
    """Test ConnectRef can be used in set"""
    ref1 = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)
    ref2 = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)
    ref3 = ConnectRef(location="s3://bucket/other", kind=ConnectKind.S3)

    ref_set = {ref1, ref2, ref3}

    assert len(ref_set) == 2
    assert ref1 in ref_set


def test_hash_as_dict_key():
    """Test ConnectRef can be used as dict key"""
    ref1 = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)
    ref2 = ConnectRef(location="/path", kind=ConnectKind.MACOS)

    ref_dict = {ref1: "value1", ref2: "value2"}

    assert ref_dict[ref1] == "value1"
    assert ref_dict[ref2] == "value2"


# ==================== String Representation Tests ====================


def test_str_representation():
    """Test __str__ returns location"""
    ref = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)

    assert str(ref) == "s3://bucket/key"


def test_str_without_kind():
    """Test __str__ works without kind"""
    ref = ConnectRef(location="/path/to/file", kind=None)

    assert str(ref) == "/path/to/file"


def test_repr_representation():
    """Test __repr__ representation"""
    ref = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)
    result = repr(ref)

    assert "ConnectRef" in result
    assert "s3://bucket/key" in result
    assert "s3" in result or "S3" in result


def test_repr_without_kind():
    """Test __repr__ without kind"""
    ref = ConnectRef(location="/path", kind=None)
    result = repr(ref)

    assert "ConnectRef" in result
    assert "/path" in result


# ==================== Edge Cases ====================


def test_connectref_with_special_characters():
    """Test ConnectRef with special characters in location"""
    ref = ConnectRef(location="s3://bucket/path with spaces/file.txt", kind=ConnectKind.S3)

    assert ref.location == "s3://bucket/path with spaces/file.txt"


def test_connectref_with_unicode():
    """Test ConnectRef with unicode characters"""
    ref = ConnectRef(location="s3://bucket/文件.txt", kind=ConnectKind.S3)

    assert ref.location == "s3://bucket/文件.txt"


def test_connectref_with_query_params():
    """Test ConnectRef with query parameters"""
    ref = ConnectRef(location="s3://bucket/key?version=1", kind=ConnectKind.S3)

    assert ref.location == "s3://bucket/key?version=1"


def test_connectref_immutability():
    """Test ConnectRef attributes are not accidentally immutable"""
    ref = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)

    ref.location = "new_location"
    assert ref.location == "new_location"


def test_connectref_with_very_long_location():
    """Test ConnectRef with very long location"""
    long_location = "s3://bucket/" + "a" * 1000
    ref = ConnectRef(location=long_location, kind=ConnectKind.S3)

    assert ref.location == long_location
    assert len(ref.location) > 1000


# ==================== Integration Tests ====================


def test_full_workflow():
    """Test full workflow of creating, modifying, and serializing"""
    ref = ConnectRef(location="s3://bucket", kind=ConnectKind.S3)

    ref = ref.to("data").to("file.parquet")
    data = ref.to_dict()
    restored = ConnectRef.from_dict(data)

    assert restored.location == "s3://bucket/data/file.parquet"
    assert restored.kind == ConnectKind.S3


def test_multiple_kinds():
    """Test creating refs with different kinds"""
    s3_ref = ConnectRef(location="s3://bucket/key", kind=ConnectKind.S3)
    macos_ref = ConnectRef(location="/Users/user/file", kind=ConnectKind.MACOS)
    any_ref = ConnectRef(location="path/to/file", kind=ConnectKind.ANY)

    assert s3_ref.kind == ConnectKind.S3
    assert macos_ref.kind == ConnectKind.MACOS
    assert any_ref.kind == ConnectKind.ANY
