from __future__ import annotations

import io
import zipfile
from pathlib import Path

import pytest

from yee88.telegram import files as tg_files
from yee88.telegram.files import ZipTooLargeError, zip_directory


def test_zip_directory_skips_symlinks(tmp_path: Path) -> None:
    root = tmp_path / "root"
    root.mkdir()
    target = root / "dir"
    target.mkdir()
    (target / "safe.txt").write_text("ok", encoding="utf-8")
    outside = tmp_path / "secret.txt"
    outside.write_text("secret", encoding="utf-8")
    link_path = target / "leak.txt"
    try:
        link_path.symlink_to(outside)
    except (OSError, NotImplementedError):
        pytest.skip("symlinks not supported")

    payload = zip_directory(root, Path("dir"), deny_globs=())

    with zipfile.ZipFile(io.BytesIO(payload)) as archive:
        names = set(archive.namelist())

    assert "dir/safe.txt" in names
    assert "dir/leak.txt" not in names


def test_zip_directory_limits_size(tmp_path: Path) -> None:
    root = tmp_path / "root"
    root.mkdir()
    target = root / "dir"
    target.mkdir()
    (target / "data.bin").write_bytes(b"x" * 1024)

    with pytest.raises(ZipTooLargeError):
        zip_directory(root, Path("dir"), deny_globs=(), max_bytes=10)


def test_split_command_args_falls_back_on_bad_quotes() -> None:
    assert tg_files.split_command_args('bad "quote') == ("bad", '"quote')


def test_parse_file_command_unknown_command() -> None:
    command, rest, error = tg_files.parse_file_command("nope arg")
    assert command is None
    assert rest == "arg"
    assert error == tg_files.file_usage()


def test_parse_file_prompt_errors() -> None:
    path, force, error = tg_files.parse_file_prompt("--wat", allow_empty=False)
    assert path is None
    assert force is False
    assert error == "unknown flag: --wat"

    path, force, error = tg_files.parse_file_prompt("", allow_empty=False)
    assert path is None
    assert force is False
    assert error == "missing path"


def test_parse_file_prompt_force_flag() -> None:
    path, force, error = tg_files.parse_file_prompt(
        "--force note.txt", allow_empty=False
    )
    assert path == "note.txt"
    assert force is True
    assert error is None


def test_normalize_relative_path_rejects_invalid() -> None:
    for value in ("", "   ", "~/.ssh", "/etc/passwd", "../secret", ".git/config"):
        assert tg_files.normalize_relative_path(value) is None


def test_normalize_relative_path_rejects_dot_only() -> None:
    assert tg_files.normalize_relative_path("./") is None


def test_normalize_relative_path_strips_dots() -> None:
    assert tg_files.normalize_relative_path("docs/./guide.txt") == Path(
        "docs/guide.txt"
    )


def test_resolve_path_within_root_rejects_escape(tmp_path: Path) -> None:
    assert tg_files.resolve_path_within_root(tmp_path, Path("../escape")) is None


def test_deny_reason_matches_patterns() -> None:
    assert tg_files.deny_reason(Path(".git/config"), ["**/*.pem"]) == ".git/**"
    assert tg_files.deny_reason(Path("secrets/key.pem"), ["**/*.pem"]) == "**/*.pem"


def test_format_bytes_various_units() -> None:
    assert tg_files.format_bytes(0) == "0 b"
    assert tg_files.format_bytes(1536) == "1.5 kb"
    assert tg_files.format_bytes(20480) == "20 kb"


def test_default_upload_name_fallbacks() -> None:
    assert tg_files.default_upload_name("", "files/report.txt") == "report.txt"
    assert tg_files.default_upload_name(None, None) == "upload.bin"


def test_global_uploads_dir_uses_system_temp() -> None:
    """Test that global_uploads_dir returns a path under system temp directory."""
    import tempfile

    from yee88.settings import TelegramFilesSettings

    global_dir = TelegramFilesSettings.global_uploads_dir()
    assert global_dir.parent == Path(tempfile.gettempdir())
    assert global_dir.name == "yee88-uploads"


def test_use_global_tmp_default_enabled() -> None:
    """Test that use_global_tmp is enabled by default."""
    from yee88.settings import TelegramFilesSettings

    settings = TelegramFilesSettings()
    assert settings.use_global_tmp is True
