import pytest

from sitedrop.server.storage import InvalidNameError, SiteStorage


@pytest.fixture
def storage(tmp_path):
    return SiteStorage(tmp_path / "sites")


def test_put_and_get(storage):
    storage.put("hello", "<h1>Hello</h1>")
    assert storage.get("hello") == "<h1>Hello</h1>"


def test_get_nonexistent(storage):
    assert storage.get("nope") is None


def test_delete(storage):
    storage.put("hello", "<h1>Hello</h1>")
    assert storage.delete("hello") is True
    assert storage.get("hello") is None


def test_delete_nonexistent(storage):
    assert storage.delete("nope") is False


def test_list(storage):
    storage.put("alpha", "<p>a</p>")
    storage.put("beta", "<p>b</p>")
    sites = storage.list()
    names = [s.name for s in sites]
    assert "alpha" in names
    assert "beta" in names


def test_put_returns_info(storage):
    info = storage.put("mysite", "<h1>Hi</h1>")
    assert info.name == "mysite"
    assert info.size > 0
    assert info.modified


def test_invalid_name_dotdot(storage):
    with pytest.raises(InvalidNameError):
        storage.put("../evil", "x")


def test_invalid_name_slash(storage):
    with pytest.raises(InvalidNameError):
        storage.put("foo/bar", "x")


def test_invalid_name_dot_start(storage):
    with pytest.raises(InvalidNameError):
        storage.put(".hidden", "x")


def test_valid_names(storage):
    for name in ["a", "my-site", "site_2", "A1-b_c", "0abc"]:
        storage.put(name, "<p>ok</p>")
        assert storage.get(name) == "<p>ok</p>"


def test_overwrite(storage):
    storage.put("page", "<p>v1</p>")
    storage.put("page", "<p>v2</p>")
    assert storage.get("page") == "<p>v2</p>"


def test_empty_content(storage):
    storage.put("empty", "")
    assert storage.get("empty") == ""


def test_list_empty(storage):
    assert storage.list() == []


def test_list_sorted(storage):
    storage.put("cherry", "<p>c</p>")
    storage.put("apple", "<p>a</p>")
    storage.put("banana", "<p>b</p>")
    names = [s.name for s in storage.list()]
    assert names == ["apple", "banana", "cherry"]


def test_invalid_names_special_chars(storage):
    for name in ["@bad", "#bad", "sp ace", "tab\ttab", ""]:
        with pytest.raises(InvalidNameError):
            storage.put(name, "x")


def test_name_too_long(storage):
    with pytest.raises(InvalidNameError, match="too long"):
        storage.put("a" * 241, "<p>x</p>")


def test_name_at_max_length(storage):
    name = "a" * 240
    info = storage.put(name, "<p>x</p>")
    assert info.name == name
    assert storage.get(name) == "<p>x</p>"


def test_put_write_error(storage):
    from unittest.mock import patch

    with patch("tempfile.mkstemp", side_effect=OSError("Permission denied")):
        with pytest.raises(OSError, match="Failed to write"):
            storage.put("test", "<p>x</p>")


def test_exists_present(storage):
    storage.put("hello", "<h1>Hello</h1>")
    assert storage.exists("hello") is True


def test_exists_absent(storage):
    assert storage.exists("nope") is False


def test_exists_invalid_name(storage):
    with pytest.raises(InvalidNameError):
        storage.exists(".bad")


def test_put_cleans_up_temp_on_failure(storage):
    from unittest.mock import patch

    with patch("os.replace", side_effect=OSError("replace failed")):
        with pytest.raises(OSError, match="Failed to write"):
            storage.put("test", "<p>x</p>")

    # No .tmp files should remain
    tmp_files = list(storage.sites_dir.glob("*.tmp"))
    assert tmp_files == []
