# src/ctf/forensics.py
# Library for forensic analysis (pure functions only)

# vim foldmethod=marker
from dataclasses import dataclass, field
from pathlib import Path
import stat
import os
import sys
from typing import List, Dict

try:
    import pwd
    import grp
except ImportError:
    pwd = None
    grp = None

COMMON_SIGNATURES = {
    b"\x89PNG\r\n\x1a\n": ("PNG Image", [".png"]),
    b"\xff\xd8\xff": ("JPEG Image", [".jpg", ".jpeg"]),
    b"%PDF": ("PDF Document", [".pdf"]),
    b"PK\x03\x04": ("ZIP Archive", [".zip"]),
    b"\x7fELF": ("ELF Executable", [".elf"]),
    b"MZ": ("PE Executable", [".exe", ".dll"]),
    b"GIF87a": ("GIF Image", [".gif"]),
    b"GIF89a": ("GIF Image", [".gif"]),
    b"7z\xbc\xaf\x27\x1c": ("7-Zip Archive", [".7z"]),
    b"\x1f\x8b": ("GZIP Archive", [".gz"]),
    b"Rar!\x1a\x07\x00": ("RAR Archive", [".rar"]),
    b"Rar!\x1a\x07\x01\x00": ("RAR Archive", [".rar"]),
    b"BZh": ("BZIP2 Archive", [".bz2"]),
    b"BM": ("BMP Image", [".bmp"]),
    b"ID3": ("MP3 Audio", [".mp3"]),
}

# {{{ FileMetadata
@dataclass
class FileMetadata:
    filename: str
    size: int
    magic: str
    extension: str
    detected_type: str
    
    # Task 1: POSIX Permissions
    permissions_octal: str
    permissions_symbolic: str
    
    # Task 2: Ownership Identity
    owner_uid: int
    owner_username: str
    owner_gid: int
    owner_groupname: str
    
    # Task 3: Allocation Metrics
    allocated_size: int
    
    # Task 4: Hard Links
    hard_links: int
    
    # Task 5: Inode & Device Identifiers
    inode: int
    device: int
    
    # Task 6: Extended Attributes
    extended_attributes: Dict[str, str] = field(default_factory=dict)
# }}}

# {{{ get_metadata
def get_metadata(path: Path) -> FileMetadata:
    """Extracts metadata attributes from a file without any console rendering."""
    p = Path(path)
    if not p.exists():
        raise FileNotFoundError(f"File not found: {p}")
        
    stat_info = p.stat()
    
    # Apparent size & extension
    size = stat_info.st_size
    extension = p.suffix
    
    # Magic bytes
    try:
        with open(p, 'rb') as f:
            magic = f.read(8).hex().upper()
    except Exception:
        magic = ""
        
    detected_type = "Unknown"
    try:
        magic_bytes = bytes.fromhex(magic)
        for signature, (type_name, exts) in COMMON_SIGNATURES.items():
            if magic_bytes.startswith(signature):
                detected_type = type_name
                break
    except Exception:
        pass
        
    # POSIX Permissions
    mode = stat_info.st_mode
    permissions_octal = oct(stat.S_IMODE(mode))
    permissions_symbolic = stat.filemode(mode)
    
    # Ownership Identity
    owner_uid = stat_info.st_uid
    owner_gid = stat_info.st_gid
    owner_username = str(owner_uid)
    owner_groupname = str(owner_gid)
    
    if pwd is not None:
        try:
            owner_username = pwd.getpwuid(owner_uid).pw_name
        except KeyError:
            pass
    if grp is not None:
        try:
            owner_groupname = grp.getgrgid(owner_gid).gr_name
        except KeyError:
            pass
            
    # Allocation Metrics
    if hasattr(stat_info, "st_blocks"):
        allocated_size = stat_info.st_blocks * 512
    else:
        allocated_size = size
        
    # Hard Links
    hard_links = stat_info.st_nlink
    
    # Inode & Device Identifiers
    inode = stat_info.st_ino
    device = stat_info.st_dev
    
    # Extended Attributes
    extended_attributes = {}
    if hasattr(os, "listxattr") and hasattr(os, "getxattr"):
        try:
            attrs = os.listxattr(p)
            for attr in attrs:
                try:
                    val = os.getxattr(p, attr)
                    extended_attributes[attr] = val.decode("utf-8", errors="ignore")
                except OSError:
                    pass
        except OSError:
            pass
            
    return FileMetadata(
        filename=p.name,
        size=size,
        magic=magic,
        extension=extension,
        detected_type=detected_type,
        permissions_octal=permissions_octal,
        permissions_symbolic=permissions_symbolic,
        owner_uid=owner_uid,
        owner_username=owner_username,
        owner_gid=owner_gid,
        owner_groupname=owner_groupname,
        allocated_size=allocated_size,
        hard_links=hard_links,
        inode=inode,
        device=device,
        extended_attributes=extended_attributes
    )
# }}}

