"""
Core decompilation logic for IDA Pro batch decompiler.
"""

import os
import subprocess
import tempfile
from pathlib import Path
from typing import Dict, List, Optional, Tuple


class IDABatchDecompiler:
    """
    IDA Pro batch decompiler class.

    This class provides methods for batch decompiling binary files using
    IDA Pro's idat command-line tool.

    Args:
        idat_path: Path to idat executable
        output_dir: Output directory for generated C files
        jobs: Number of parallel processes
        timeout: Timeout in seconds per file
        verbose: Enable verbose output
    """

    def __init__(
        self,
        idat_path: str = "/Applications/IDA Professional 9.1.app/Contents/MacOS/idat",
        output_dir: str = "./output",
        jobs: int = 4,
        timeout: int = 300,
        verbose: bool = False
    ):
        self.idat_path = Path(idat_path)
        self.output_dir = Path(output_dir)
        self.jobs = jobs
        self.timeout = timeout
        self.verbose = verbose

        # Validate idat path
        if not self.idat_path.exists():
            raise FileNotFoundError(f"idat not found: {self.idat_path}")

        # Create output directory
        self.output_dir.mkdir(parents=True, exist_ok=True)

    def create_idapython_script(self) -> str:
        """
        Create IDAPython script for exporting C code.

        Returns:
            The script content as string
        """
        script_content = r"""import idc
import idaapi
import os

# Get output directory from environment
output_dir = os.environ.get('IDA_OUTPUT_DIR', r'{{output_dir}}')
bin_name = os.path.splitext(idc.get_input_file_path())[0]
output_file = os.path.join(output_dir, bin_name + ".c")

os.makedirs(output_dir, exist_ok=True)

# Export C code using generate_file
# GFL_CDECOMP = 0x0004 (C source file)
result = idc.generate_file(idc.GFL_CDECOMP, output_file, 0, idc.BADADDR, 0)
if result == 0:
    print(f"SUCCESS: {{output_file}}")
else:
    print(f"FAILED: generate_file returned {{result}}")

idaapi.qexit(0)
""".replace("{{output_dir}}", self.output_dir.as_posix())

        return script_content

    def decompile_binary(self, binary_path: str) -> Tuple[bool, str]:
        """
        Decompile a single binary file.

        Args:
            binary_path: Path to the binary file

        Returns:
            Tuple of (success, message)
        """
        binary_path = Path(binary_path)
        if not binary_path.exists():
            return False, f"File not found: {binary_path}"

        bin_name = binary_path.stem
        output_file = self.output_dir / f"{bin_name}.c"

        # Create temporary script file
        script_content = self.create_idapython_script()

        with tempfile.NamedTemporaryFile(
            mode='w',
            suffix='.py',
            prefix=f'ida_export_{bin_name}_',
            delete=False
        ) as f:
            script_path = f.name
            f.write(script_content)

        try:
            # Build idat command
            cmd = [
                str(self.idat_path),
                "-A",       # Automatic mode
                f"-S{script_path}",
                str(binary_path)
            ]

            if self.verbose:
                print(f"Processing: {bin_name}")
                print(f"  Command: {' '.join(cmd)}")

            # Run idat
            result = subprocess.run(
                cmd,
                timeout=self.timeout,
                capture_output=True,
                text=True,
                env={**os.environ, 'IDA_OUTPUT_DIR': str(self.output_dir)}
            )

            if output_file.exists():
                return True, f"Generated {output_file}"
            else:
                stderr = result.stderr.strip() if result.stderr else result.stdout.strip()
                return False, f"Output not created: {stderr}"

        except subprocess.TimeoutExpired:
            return False, f"Timeout after {self.timeout}s"
        except Exception as e:
            return False, f"Exception: {e}"
        finally:
            # Clean up temporary script
            try:
                os.unlink(script_path)
            except:
                pass

    def decompile_directory(self, input_dir: str) -> Dict[str, Tuple[bool, str]]:
        """
        Decompile all binaries in a directory.

        Args:
            input_dir: Path to input directory

        Returns:
            Dictionary mapping binary paths to (success, message) tuples
        """
        from .discovery import BinaryScanner

        input_dir = Path(input_dir)
        scanner = BinaryScanner()
        binaries = scanner.scan_directory(input_dir)

        results: Dict[str, Tuple[bool, str]] = {}

        if self.jobs > 1 and len(binaries) > 1:
            # Parallel processing
            import multiprocessing

            args_list = [
                (str(b),) for b in binaries
            ]

            with multiprocessing.Pool(self.jobs) as pool:
                binary_paths = [str(b) for b in binaries]
                decompile_results = pool.map(
                    self._decompile_wrapper,
                    [(b,) for b in binaries]
                )
                results = dict(zip(binary_paths, decompile_results))
        else:
            # Sequential processing
            for binary in binaries:
                success, message = self.decompile_binary(str(binary))
                results[str(binary)] = (success, message)

        return results

    def _decompile_wrapper(self, args: Tuple[str]) -> Tuple[bool, str]:
        """Wrapper for multiprocessing."""
        return self.decompile_binary(args[0])

    def decompile_batch(
        self,
        binary_paths: List[str],
        parallel: bool = True
    ) -> Dict[str, Tuple[bool, str]]:
        """
        Decompile a list of binary files.

        Args:
            binary_paths: List of binary file paths
            parallel: Enable parallel processing

        Returns:
            Dictionary mapping binary paths to (success, message) tuples
        """
        results: Dict[str, Tuple[bool, str]] = {}

        if parallel and self.jobs > 1 and len(binary_paths) > 1:
            import multiprocessing

            with multiprocessing.Pool(self.jobs) as pool:
                decompile_results = pool.map(
                    self._decompile_wrapper,
                    [(b,) for b in binary_paths]
                )
                results = dict(zip(binary_paths, decompile_results))
        else:
            for binary_path in binary_paths:
                success, message = self.decompile_binary(binary_path)
                results[binary_path] = (success, message)

        return results
