"""ASCII log compression for large files before upload."""

from __future__ import annotations

import gzip
import logging
import os
import shutil
import time

from vcm_file_uploader._types import CompressionInfo

logger = logging.getLogger(__name__)

DEFAULT_COMPRESS_THRESHOLD = 64 * 1024 * 1024  # 64 MiB
DEFAULT_GZIP_LEVEL = 6
STABILITY_WAIT = 2.0  # seconds between size checks
_SAMPLE_SIZE = 8192  # bytes to sample for ASCII detection
_TEXT_THRESHOLD = 0.95  # ratio of printable+whitespace bytes to classify as text


def is_ascii_file(path: str, sample_size: int = _SAMPLE_SIZE) -> bool:
    """Detect if a file is ASCII/text by sampling its content.

    Reads the first `sample_size` bytes and checks:
    - No null bytes (strong binary indicator)
    - At least 95% of bytes are printable ASCII or whitespace
    """
    try:
        with open(path, "rb") as f:
            chunk = f.read(sample_size)
    except OSError:
        return False

    if not chunk:
        return False

    if b"\x00" in chunk:
        return False

    text_bytes = sum(1 for b in chunk if 32 <= b <= 126 or b in (9, 10, 13))
    return (text_bytes / len(chunk)) >= _TEXT_THRESHOLD


def is_stable(path: str, wait: float = STABILITY_WAIT) -> bool:
    """Check if a file has stopped growing by comparing size over an interval."""
    try:
        size1 = os.path.getsize(path)
        time.sleep(wait)
        size2 = os.path.getsize(path)
        return size1 == size2
    except OSError:
        return False


def compress_file(
    path: str,
    *,
    output_path: str | None = None,
    level: int = DEFAULT_GZIP_LEVEL,
) -> tuple[str, CompressionInfo]:
    """Compress a file with gzip.

    Returns (compressed_path, CompressionInfo).
    """
    original_size = os.path.getsize(path)
    if output_path is None:
        output_path = path + ".gz"

    with open(path, "rb") as f_in, gzip.open(output_path, "wb", compresslevel=level) as f_out:
        shutil.copyfileobj(f_in, f_out)

    compressed_size = os.path.getsize(output_path)
    info = CompressionInfo(
        algorithm="gzip",
        level=level,
        original_bytes=original_size,
        compressed_bytes=compressed_size,
    )
    logger.info(
        "Compressed %s: %d -> %d bytes (%.1f%%)",
        path,
        original_size,
        compressed_size,
        (1 - compressed_size / original_size) * 100 if original_size > 0 else 0,
    )
    return output_path, info


def maybe_compress(
    path: str,
    *,
    category: str | None = None,
    threshold: int = DEFAULT_COMPRESS_THRESHOLD,
    level: int = DEFAULT_GZIP_LEVEL,
    check_stability: bool = True,
) -> tuple[str, CompressionInfo] | None:
    """Compress an ASCII file if it meets the criteria.

    Returns (compressed_path, CompressionInfo) if compressed, None otherwise.

    Criteria:
    - File is ASCII/text (detected by content sampling), or category='log'
    - File size >= threshold
    - File is not actively growing (stable size)
    """
    try:
        size = os.path.getsize(path)
    except OSError:
        return None

    if size < threshold:
        return None

    if category != "log" and not is_ascii_file(path):
        return None

    if check_stability and not is_stable(path):
        logger.info("Skipping compression of %s: file is still growing", path)
        return None

    return compress_file(path, level=level)
