"""Comprehensive tests for ETag-related methods across all backends."""

import time
import pytest
from moto import mock_aws

from persidict.jokers_and_status_flags import (
    ITEM_NOT_AVAILABLE, KEEP_CURRENT, DELETE_CURRENT, 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_etag_returns_string(tmpdir, DictToTest, kwargs):
    """Verify etag() returns a string for existing keys."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "value1"

    etag = d.etag("key1")

    assert isinstance(etag, str)
    assert len(etag) > 0


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_etag_changes_on_update(tmpdir, DictToTest, kwargs):
    """Verify etag changes when value is updated."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "value1"
    etag_before = d.etag("key1")

    # Use 1.1s sleep for backends with 1-second timestamp resolution (mocked S3)
    time.sleep(1.1)
    d["key1"] = "value2"
    etag_after = d.etag("key1")

    assert etag_before != etag_after


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_etag_stable_without_update(tmpdir, DictToTest, kwargs):
    """Verify etag remains stable when value is not modified."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "value1"

    etag1 = d.etag("key1")
    etag2 = d.etag("key1")

    assert etag1 == etag2


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_etag_missing_key_raises_error(tmpdir, DictToTest, kwargs):
    """Verify etag() raises an error for nonexistent keys."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)

    with pytest.raises(KeyError):
        d.etag("nonexistent")


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_get_item_if_etag_returns_value_when_changed(tmpdir, DictToTest, kwargs):
    """Verify get_item_if_etag returns (value, new_etag) when etag differs."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "value1"
    old_etag = d.etag("key1")

    time.sleep(MIN_SLEEP)
    d["key1"] = "value2"

    result = d.get_item_if("key1", condition=ETAG_HAS_CHANGED, expected_etag=old_etag)

    assert result.condition_was_satisfied
    value = result.new_value
    new_etag = result.resulting_etag
    assert value == "value2"
    assert new_etag != old_etag


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_get_item_if_etag_returns_flag_when_unchanged(tmpdir, DictToTest, kwargs):
    """Verify get_item_if_etag returns COND_NOT_MET_PH when etag matches."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "value1"
    current_etag = d.etag("key1")

    result = d.get_item_if("key1", condition=ETAG_HAS_CHANGED, expected_etag=current_etag)

    assert not result.condition_was_satisfied


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_get_item_if_etag_missing_key_raises_error(tmpdir, DictToTest, kwargs):
    """Verify get_item_if returns result with ITEM_NOT_AVAILABLE for nonexistent keys."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)

    result = d.get_item_if("nonexistent", condition=ETAG_HAS_CHANGED, expected_etag="some_etag")
    assert result.actual_etag is ITEM_NOT_AVAILABLE


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_set_item_get_etag_returns_new_etag(tmpdir, DictToTest, kwargs):
    """Verify set_item_get_etag stores value and returns an etag string."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)

    d["key1"] = "value1"
    etag = d.etag("key1")

    assert etag is not None
    assert isinstance(etag, str)
    assert d["key1"] == "value1"


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_set_item_get_etag_with_keep_current(tmpdir, DictToTest, kwargs):
    """Verify set_item_get_etag with KEEP_CURRENT returns None and keeps value."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "original"
    original_etag = d.etag("key1")

    d["key1"] = KEEP_CURRENT

    assert d["key1"] == "original"
    assert d.etag("key1") == original_etag


@pytest.mark.parametrize("DictToTest, kwargs", mutable_tests)
@mock_aws
def test_set_item_get_etag_with_delete_current(tmpdir, DictToTest, kwargs):
    """Verify set_item_get_etag with DELETE_CURRENT returns None and deletes key."""
    d = make_test_dict(DictToTest, tmpdir, **kwargs)
    d["key1"] = "value1"

    d["key1"] = DELETE_CURRENT

    assert "key1" not in d


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

    etag = d.etag(key)

    assert isinstance(etag, str)
    assert len(etag) > 0
