"""Tests for KEEP_CURRENT and DELETE_CURRENT joker handling in conditional operations.

These tests verify that jokers interact correctly with ETag-based conditional
operations, particularly that etag verification still occurs even with jokers.
"""

import time
import pytest
from moto import mock_aws

from persidict.jokers_and_status_flags import (
    ITEM_NOT_AVAILABLE,
    KEEP_CURRENT, DELETE_CURRENT,
    ANY_ETAG, ETAG_IS_THE_SAME, ETAG_HAS_CHANGED
)

from tests.data_for_mutable_tests import mutable_tests, make_test_dict

MIN_SLEEP = 0.02


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_set_item_if_etag_equal_with_keep_current_verifies_etag(tmpdir, DictToTest, kwargs):
    """Critical: KEEP_CURRENT with wrong etag should fail condition.

    This test verifies that even when KEEP_CURRENT is used (which doesn't modify
    the value), the etag is still checked. This is important for correctness.
    """
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "original"
    wrong_etag = "definitely_wrong_etag"

    result = d.set_item_if("key1", value=KEEP_CURRENT, condition=ETAG_IS_THE_SAME, expected_etag=wrong_etag)

    assert not result.condition_was_satisfied
    assert d["key1"] == "original"  # Value unchanged


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_set_item_if_etag_equal_with_keep_current_matching_etag(tmpdir, DictToTest, kwargs):
    """Verify KEEP_CURRENT with matching etag succeeds and keeps value."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "original"
    etag = d.etag("key1")

    result = d.set_item_if("key1", value=KEEP_CURRENT, condition=ETAG_IS_THE_SAME, expected_etag=etag)

    assert result.condition_was_satisfied
    assert d["key1"] == "original"
    assert d.etag("key1") == etag  # Etag unchanged


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_set_item_if_etag_equal_with_delete_current_succeeds(tmpdir, DictToTest, kwargs):
    """Verify DELETE_CURRENT with matching etag deletes the key."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "value"
    etag = d.etag("key1")

    result = d.set_item_if("key1", value=DELETE_CURRENT, condition=ETAG_IS_THE_SAME, expected_etag=etag)

    assert result.condition_was_satisfied
    assert "key1" not in d


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_set_item_if_etag_equal_with_delete_current_fails_on_wrong_etag(tmpdir, DictToTest, kwargs):
    """Verify DELETE_CURRENT with wrong etag fails condition and preserves key."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "original"
    old_etag = d.etag("key1")

    time.sleep(1.1)  # Ensure timestamp changes
    d["key1"] = "modified"

    result = d.set_item_if("key1", value=DELETE_CURRENT, condition=ETAG_IS_THE_SAME, expected_etag=old_etag)

    assert not result.condition_was_satisfied
    assert "key1" in d
    assert d["key1"] == "modified"


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_set_item_if_etag_different_with_keep_current_verifies_etag(tmpdir, DictToTest, kwargs):
    """Verify KEEP_CURRENT with unchanged etag fails condition."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "original"
    current_etag = d.etag("key1")

    result = d.set_item_if("key1", value=KEEP_CURRENT, condition=ETAG_HAS_CHANGED, expected_etag=current_etag)

    assert not result.condition_was_satisfied
    assert d["key1"] == "original"


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_set_item_if_etag_different_with_keep_current_changed_etag(tmpdir, DictToTest, kwargs):
    """Verify KEEP_CURRENT with changed etag succeeds (no modification)."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "original"
    old_etag = d.etag("key1")

    time.sleep(1.1)
    d["key1"] = "modified"

    result = d.set_item_if("key1", value=KEEP_CURRENT, condition=ETAG_HAS_CHANGED, expected_etag=old_etag)

    assert result.condition_was_satisfied
    assert d["key1"] == "modified"  # Value stays as modified


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_set_item_if_etag_different_with_delete_current_succeeds(tmpdir, DictToTest, kwargs):
    """Verify DELETE_CURRENT with changed etag deletes the key."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "original"
    old_etag = d.etag("key1")

    time.sleep(1.1)
    d["key1"] = "modified"

    result = d.set_item_if("key1", value=DELETE_CURRENT, condition=ETAG_HAS_CHANGED, expected_etag=old_etag)

    assert result.condition_was_satisfied
    assert "key1" not in d


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_set_item_if_etag_different_with_delete_current_unchanged_etag(tmpdir, DictToTest, kwargs):
    """Verify DELETE_CURRENT with unchanged etag fails condition."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "value"
    current_etag = d.etag("key1")

    result = d.set_item_if("key1", value=DELETE_CURRENT, condition=ETAG_HAS_CHANGED, expected_etag=current_etag)

    assert not result.condition_was_satisfied
    assert "key1" in d
    assert d["key1"] == "value"


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_joker_keep_current_on_missing_key_conditional(tmpdir, DictToTest, kwargs):
    """Verify KEEP_CURRENT on missing key with conditional set does not raise.

    New API treats missing keys as actual_etag=ITEM_NOT_AVAILABLE.
    """
    d = make_test_dict(DictToTest, tmpdir, **kwargs)

    result = d.set_item_if("nonexistent", value=KEEP_CURRENT, condition=ETAG_IS_THE_SAME, expected_etag="some_etag")

    assert not result.condition_was_satisfied
    assert result.actual_etag is ITEM_NOT_AVAILABLE


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_joker_delete_current_on_missing_key_conditional(tmpdir, DictToTest, kwargs):
    """Verify DELETE_CURRENT on missing key with conditional set does not raise.

    New API treats missing keys as actual_etag=ITEM_NOT_AVAILABLE.
    """
    d = make_test_dict(DictToTest, tmpdir, **kwargs)

    result = d.set_item_if("nonexistent", value=DELETE_CURRENT, condition=ETAG_IS_THE_SAME, expected_etag="some_etag")

    assert not result.condition_was_satisfied
    assert result.actual_etag is ITEM_NOT_AVAILABLE


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_keep_current_preserves_exact_value(tmpdir, DictToTest, kwargs):
    """Verify KEEP_CURRENT doesn't alter value in any way."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    original = {"complex": [1, 2, 3], "nested": {"a": "b"}}
    d["key1"] = original
    etag = d.etag("key1")

    d.set_item_if("key1", value=KEEP_CURRENT, condition=ETAG_IS_THE_SAME, expected_etag=etag)

    assert d["key1"] == original


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_delete_current_removes_key_completely(tmpdir, DictToTest, kwargs):
    """Verify DELETE_CURRENT removes key from iteration and containment checks."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "value"
    d["key2"] = "value2"
    etag = d.etag("key1")

    d.set_item_if("key1", value=DELETE_CURRENT, condition=ETAG_IS_THE_SAME, expected_etag=etag)

    assert "key1" not in d
    assert "key1" not in list(d.keys())
    assert len(d) == 1


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_jokers_with_tuple_keys(tmpdir, DictToTest, kwargs):
    """Verify jokers work correctly with hierarchical tuple keys."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    key = ("prefix", "subkey", "leaf")
    d[key] = "value"
    etag = d.etag(key)

    # Test KEEP_CURRENT
    result = d.set_item_if(key, value=KEEP_CURRENT, condition=ETAG_IS_THE_SAME, expected_etag=etag)
    assert result.condition_was_satisfied
    assert d[key] == "value"

    # Test DELETE_CURRENT
    result = d.set_item_if(key, value=DELETE_CURRENT, condition=ETAG_IS_THE_SAME, expected_etag=etag)
    assert result.condition_was_satisfied
    assert key not in d


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_keep_current_does_not_update_etag(tmpdir, DictToTest, kwargs):
    """Verify KEEP_CURRENT doesn't change the etag (value not touched)."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "value"
    etag_before = d.etag("key1")

    d.set_item_if("key1", value=KEEP_CURRENT, condition=ETAG_IS_THE_SAME, expected_etag=etag_before)
    etag_after = d.etag("key1")

    assert etag_before == etag_after


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_keep_current_with_unknown_etag_fails(tmpdir, DictToTest, kwargs):
    """Verify KEEP_CURRENT with ITEM_NOT_AVAILABLE fails condition."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "value"

    result = d.set_item_if("key1", value=KEEP_CURRENT, condition=ETAG_IS_THE_SAME, expected_etag=ITEM_NOT_AVAILABLE)

    assert not result.condition_was_satisfied
    assert d["key1"] == "value"


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_delete_current_with_unknown_etag_fails(tmpdir, DictToTest, kwargs):
    """Verify DELETE_CURRENT with ITEM_NOT_AVAILABLE fails condition."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "value"

    result = d.set_item_if("key1", value=DELETE_CURRENT, condition=ETAG_IS_THE_SAME, expected_etag=ITEM_NOT_AVAILABLE)

    assert not result.condition_was_satisfied
    assert "key1" in d


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_delete_current_success_result_fields(tmpdir, DictToTest, kwargs):
    """Verify result fields on successful DELETE_CURRENT.

    On success: resulting_etag is ITEM_NOT_AVAILABLE (key gone),
    actual_etag is the pre-delete etag, new_value is ITEM_NOT_AVAILABLE.
    """
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "value"
    etag = d.etag("key1")

    result = d.set_item_if("key1", value=DELETE_CURRENT, condition=ETAG_IS_THE_SAME, expected_etag=etag)

    assert result.condition_was_satisfied
    assert result.actual_etag == etag
    assert result.resulting_etag is ITEM_NOT_AVAILABLE
    assert result.new_value is ITEM_NOT_AVAILABLE
    assert "key1" not in d


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_delete_current_with_any_etag_succeeds(tmpdir, DictToTest, kwargs):
    """Verify DELETE_CURRENT with ANY_ETAG deletes an existing key."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "value"
    d.etag("key1")

    result = d.set_item_if("key1", value=DELETE_CURRENT, condition=ANY_ETAG, expected_etag="irrelevant")

    assert result.condition_was_satisfied
    assert result.resulting_etag is ITEM_NOT_AVAILABLE
    assert result.new_value is ITEM_NOT_AVAILABLE
    assert "key1" not in d


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_delete_current_with_any_etag_missing_key(tmpdir, DictToTest, kwargs):
    """Verify DELETE_CURRENT with ANY_ETAG on a missing key reports condition satisfied.

    ANY_ETAG is unconditionally true, so condition_was_satisfied must be True
    even when the key is absent (the delete is a no-op).
    """
    d = make_test_dict(DictToTest, tmpdir, **kwargs)

    result = d.set_item_if("nonexistent", value=DELETE_CURRENT, condition=ANY_ETAG, expected_etag="irrelevant")

    assert result.condition_was_satisfied
    assert result.actual_etag is ITEM_NOT_AVAILABLE
    assert result.resulting_etag is ITEM_NOT_AVAILABLE
    assert result.new_value is ITEM_NOT_AVAILABLE


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_delete_current_missing_key_etag_same_with_item_not_available(tmpdir, DictToTest, kwargs):
    """Verify DELETE_CURRENT on a missing key when ETAG_IS_THE_SAME + ITEM_NOT_AVAILABLE.

    The caller believes the key is absent (expected_etag=ITEM_NOT_AVAILABLE) and
    it truly is, so the condition is satisfied. The delete is a no-op.
    """
    d = make_test_dict(DictToTest, tmpdir, **kwargs)

    result = d.set_item_if(
        "nonexistent", value=DELETE_CURRENT,
        condition=ETAG_IS_THE_SAME, expected_etag=ITEM_NOT_AVAILABLE)

    assert result.condition_was_satisfied
    assert result.actual_etag is ITEM_NOT_AVAILABLE
    assert result.resulting_etag is ITEM_NOT_AVAILABLE


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_delete_current_missing_key_etag_changed_with_real_etag(tmpdir, DictToTest, kwargs):
    """Verify DELETE_CURRENT on a missing key when ETAG_HAS_CHANGED + real expected_etag.

    The caller expects a real ETag but actual is ITEM_NOT_AVAILABLE, so the
    condition is satisfied (they differ). The delete is a no-op.
    """
    d = make_test_dict(DictToTest, tmpdir, **kwargs)

    result = d.set_item_if(
        "nonexistent", value=DELETE_CURRENT,
        condition=ETAG_HAS_CHANGED, expected_etag="some_old_etag")

    assert result.condition_was_satisfied
    assert result.actual_etag is ITEM_NOT_AVAILABLE
    assert result.resulting_etag is ITEM_NOT_AVAILABLE
