import hashlib
import os
import shutil
import tarfile
import zipfile
from typing import List


class OSAFiles:
    """Enhanced file operations for SysPack"""

    def __init__(self, osaf_core):
        self.osaf_core = osaf_core
        self.log = osaf_core.log
        self.proc = osaf_core.processes

    def p(self, path):
        """Normalize path"""
        return os.path.normpath(os.path.expanduser(path))

    def touch(self, file_path):
        if self.osaf_core.utils.is_windows():
            from pathlib import Path
            Path(file_path).touch()
        else:
            self.proc.exec(['touch', self.p(file_path)])

    def mkdir(self, dir_path, mode=None, parents=True):
        if self.exists(dir_path):
            self.log.dd(f'Directory {dir_path} exists, skipping')
            return

        if self.osaf_core.utils.is_windows():
            os.makedirs(dir_path, exist_ok=True)
        else:
            cmd = ['mkdir', self.p(dir_path)]
            if parents:
                cmd.append('-p')
            if mode:
                cmd.extend(['-m', mode])
            cmd.append('-v')
            self.proc.exec(cmd, log=True)

    def write(self, path, data):
        return self.write_str(path, data)

    def read(self, path):
        return self.read_str(path)

    def write_str(self, path, data, mode='rewrite'):
        path = self.p(path)
        self.mkdir(os.path.dirname(path), parents=True)

        open_mode = 'w' if mode == 'rewrite' else 'a'
        with open(path, open_mode, encoding='UTF-8') as f:
            f.write(data)

    def write_str_list(self, path, data):
        """Write list of strings to file"""
        with open(self.p(path), 'w', encoding='UTF-8') as f:
            for line in data:
                f.write(line + '\n')

    def read_str(self, path):
        with open(self.p(path), 'r', encoding='UTF-8') as f:
            return f.read()

    def read_str_list(self, path):
        with open(self.p(path), 'r', encoding='UTF-8') as f:
            return [line.strip() for line in f if line.strip()]

    def exists(self, path):
        return os.path.exists(self.p(path))

    def is_dir(self, path):
        return os.path.isdir(self.p(path))

    def is_file(self, path):
        return os.path.isfile(self.p(path))

    def is_link(self, path):
        return os.path.islink(self.p(path))

    def is_socket(self, path):
        """Check if path is a socket"""
        try:
            return os.path.exists(self.p(path)) and not (self.is_file(path) or self.is_dir(path))
        except:
            return False

    def remove(self, path):
        path = self.p(path)
        if os.path.isfile(path):
            os.remove(path)
        elif os.path.isdir(path):
            shutil.rmtree(path)

    def mv(self, src, dest):
        src, dest = self.p(src), self.p(dest)
        if self.osaf_core.utils.is_windows():
            shutil.move(src, dest)
        else:
            self.proc.exec(['mv', src, dest, '-v'], log=True)

    def cp(self, src, dest):
        src, dest = self.p(src), self.p(dest)
        if self.osaf_core.utils.is_windows():
            if self.is_dir(src):
                shutil.copytree(src, dest, dirs_exist_ok=True)
            else:
                shutil.copy2(src, dest)
        else:
            cmd = ['cp', src, dest, '-v']
            if self.is_dir(src):
                cmd.append('-r')
            self.proc.exec(cmd, log=True)

    def chmod(self, file, mode="0700", recurcive=False):
        if self.osaf_core.utils.is_windows():
            return None  # chmod not applicable on Windows
        cmd = ['chmod', mode, self.p(file)]
        if recurcive:
            cmd.append('-R')
        return self.proc.exec(cmd)

    def chown(self, file, new_own, new_grp=None, recursive=False):
        if self.osaf_core.utils.is_windows():
            return None  # chown not applicable on Windows
        owner = new_own
        if new_grp:
            owner = f'{new_own}:{new_grp}'
        cmd = ['chown', owner, self.p(file)]
        if recursive:
            cmd.append('-R')
        cmd.append('-v')
        return self.proc.exec(cmd)

    def cd(self, *args):
        if len(args) == 0:
            os.chdir(self.osaf_core.master_workdir)
        elif len(args) == 1:
            os.chdir(args[0])

    def list(self, dir_path=None):
        if dir_path is None:
            dir_path = os.getcwd()
        return os.listdir(self.p(dir_path))

    # NEW METHODS FOR SYSPACK
    def find_files(self, pattern: str, search_dir: str = None) -> List[str]:
        """Find files matching pattern"""
        import glob
        if search_dir is None:
            search_dir = self.osaf_core.master_workdir
        return glob.glob(os.path.join(search_dir, pattern), recursive=True)

    def calculate_hash(self, file_path: str, algorithm: str = "md5") -> str:
        """Calculate file hash"""
        hash_func = hashlib.new(algorithm)
        with open(self.p(file_path), 'rb') as f:
            for chunk in iter(lambda: f.read(4096), b""):
                hash_func.update(chunk)
        return hash_func.hexdigest()

    def extract_archive(self, archive_path: str, extract_to: str = None) -> bool:
        """Extract zip or tar archive"""
        archive_path = self.p(archive_path)
        if extract_to is None:
            extract_to = os.path.splitext(archive_path)[0]

        try:
            if archive_path.endswith('.zip'):
                with zipfile.ZipFile(archive_path, 'r') as zip_ref:
                    zip_ref.extractall(extract_to)
            elif archive_path.endswith(('.tar.gz', '.tgz')):
                with tarfile.open(archive_path, 'r:gz') as tar_ref:
                    tar_ref.extractall(extract_to)
            elif archive_path.endswith('.tar'):
                with tarfile.open(archive_path, 'r') as tar_ref:
                    tar_ref.extractall(extract_to)
            else:
                self.log.e(f"Unsupported archive format: {archive_path}")
                return False
            return True
        except Exception as e:
            self.log.e(f"Failed to extract {archive_path}: {e}")
            return False
