from __future__ import annotations

import os
import stat
from dataclasses import dataclass
from pathlib import Path
from ._exceptions import ShredderError


@dataclass(frozen=True, slots=True)
class ShredReport:
    path: str
    existed: bool
    bytes_written: int
    passes: int
    metadata_wiped: bool
    deleted: bool

def _random_name(length: int = 12) -> str:
    import secrets
    alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    return "".join(secrets.choice(alphabet) for _ in range(length))

def _overwrite_file(path: Path, *, passes: int, chunk_size: int) -> int:
    if passes <= 0:
        raise ValueError("passes must be > 0")
    if chunk_size <= 0:
        raise ValueError("chunk_size must be > 0")
    size = path.stat().st_size
    total_written = 0
    with open(path, "r+b", buffering=0) as f:
        for _ in range(passes):
            f.seek(0)
            remaining = size
            while remaining > 0:
                current = min(chunk_size, remaining)
                f.write(os.urandom(current))
                remaining -= current
                total_written += current
            f.flush()
            os.fsync(f.fileno())
    return total_written

def _wipe_metadata(path: Path, *, variable_rename_rounds: int) -> tuple[Path, bool]:
    current = path
    try:
        os.chmod(current, stat.S_IRUSR | stat.S_IWUSR)
    except OSError:
        pass
    try:
        os.utime(current, (0, 0))
    except OSError:
        pass
    renamed = False
    for _ in range(max(0, variable_rename_rounds)):
        new_path = current.with_name(_random_name())
        try:
            os.replace(current, new_path)
            current = new_path
            renamed = True
        except OSError:
            break
    return current, renamed

def secure_delete(path: str | os.PathLike[str], *, passes: int = 3, chunk_size: int = 1024 * 1024, wipe_metadata: bool = True, variable_rename_rounds: int = 2, missing_ok: bool = False) -> ShredReport:
    target = Path(path)
    if not target.exists():
        if missing_ok:
            return ShredReport(path=str(target), existed=False, bytes_written=0, passes=passes, metadata_wiped=False, deleted=False)
        raise FileNotFoundError(str(target))
    if not target.is_file():
        raise ShredderError("secure_delete currently supports regular files only")
    bytes_written = _overwrite_file(target, passes=passes, chunk_size=chunk_size)
    final_path = target
    metadata_done = False
    if wipe_metadata:
        final_path, metadata_done = _wipe_metadata(target, variable_rename_rounds=variable_rename_rounds)
    os.remove(final_path)
    return ShredReport(path=str(final_path), existed=True, bytes_written=bytes_written, passes=passes, metadata_wiped=metadata_done or wipe_metadata, deleted=True)