"""
Proxy Generator for .pyi Stub Files

This module generates the runtime proxy objects from .pyi stub files.
The proxies exist only to prevent import errors and provide IDE type hints.

Note that:
- Proxies are never executed locally (source code is sent to Designer)
- Proxies just need to exist and not throw errors
"""

import ast
import importlib.metadata
import inspect
from pathlib import Path
from typing import Any


class StubProxy:
    """StubProxy object for type checking - does nothing at runtime.

    This class exists solely to prevent import errors when using the d3 module
    outside of Designer. The actual execution happens on Designer's side where
    the real d3 module exists.
    """

    def __getattr__(self, name: Any) -> 'StubProxy':
        return StubProxy()

    def __call__(self, *args: Any, **kwargs: Any) -> 'StubProxy':
        return StubProxy()

    def __setattr__(self, name: str, value: Any) -> None:
        # Allow attribute assignment (does nothing)
        pass

    def __repr__(self) -> str:
        return "StubProxy()"

    def __str__(self) -> str:
        return "StubProxy()"


class StubAnalyser(ast.NodeVisitor):
    """Extract names from .pyi stub file."""

    def __init__(self):
        self.classes: set[str] = set()
        self.functions: set[str] = set()
        self.variables: set[str] = set()
        self.aliases: set[str] = set()

    def visit_ClassDef(self, node: ast.ClassDef) -> None:
        """Record class names."""
        self.classes.add(node.name)

    def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
        """Record function names."""
        self.functions.add(node.name)

    def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
        """Record module-level variables (e.g., resourceManager: ResourceManager)."""
        if isinstance(node.target, ast.Name):
            self.variables.add(node.target.id)

    def visit_Assign(self, node: ast.Assign) -> None:
        """Record type aliases (e.g., Vec3 = Vec)."""
        for target in node.targets:
            if isinstance(target, ast.Name):
                self.aliases.add(target.id)


def get_package_version() -> str:
    """Get version from installed package metadata."""
    try:
        return importlib.metadata.version("designer-plugin-pystub")
    except importlib.metadata.PackageNotFoundError:
        return "unknown"


def generate_proxy_module(stub_file_path: str) -> str:
    """Generate the simplest possible proxy module.

    Args:
        stub_file_path: Path to the .pyi stub file

    Returns:
        Python source code for the proxy module
    """
    # Parse the stub file
    with open(stub_file_path, 'r') as f:
        stub_content = f.read()

    tree = ast.parse(stub_content)

    # Extract all names
    analyser = StubAnalyser()
    analyser.visit(tree)

    # Build the output module
    lines: list[str] = []

    # Module docstring
    version = get_package_version()
    lines.append('"""')
    lines.append('Auto-generated stub proxy module.')
    lines.append(f'Version: {version}')
    lines.append('')
    lines.append('This module provides stub objects that exist only for type checking.')
    lines.append('All actual execution happens on Designer side with the real d3 module.')
    lines.append(f'Generated from: {Path(stub_file_path).name}')
    lines.append('"""')
    lines.append('')

    # Add necessary imports
    lines.append('from typing import Any')
    lines.append('')

    # Add the StubProxy class using inspect.getsource
    stub_class_source = inspect.getsource(StubProxy)
    lines.append(stub_class_source.rstrip())
    lines.append('')
    lines.append('')

    # Generate all module-level variables
    if analyser.variables:
        lines.append('# Module-level variables')
        for var_name in sorted(analyser.variables):
            lines.append(f'{var_name} = {StubProxy.__name__}()')
        lines.append('')

    # Generate all type aliases
    if analyser.aliases:
        lines.append('# Type aliases')
        for alias_name in sorted(analyser.aliases):
            lines.append(f'{alias_name} = {StubProxy.__name__}()')
        lines.append('')

    # Generate all functions
    if analyser.functions:
        lines.append('# Functions')
        for func_name in sorted(analyser.functions):
            lines.append(f'def {func_name}(*args, **kwargs):')
            lines.append(f'    """Stub function for {func_name}"""')
            lines.append(f'    return {StubProxy.__name__}()')
            lines.append('')
        lines.append('')

    # Generate all classes
    if analyser.classes:
        lines.append('# Classes')
        for class_name in sorted(analyser.classes):
            lines.append(f'class {class_name}:')
            lines.append(f'    """Stub class for {class_name}"""')
            lines.append(f'    def __new__(cls, *args, **kwargs):')
            lines.append(f'        return {StubProxy.__name__}()')
            lines.append('')

    return '\n'.join(lines)


def install_proxy_module(stub_path: Path, output_path: Path) -> None:
    """Generate and install proxy module alongside the .pyi stub file.

    Args:
        stub_file_path: Path to the .pyi stub file
        output_path: Optional custom output path. If None, placed alongside stub file

    Returns:
        Path to the generated .py file
    """
    # Generate the proxy code
    proxy_code: str = generate_proxy_module(str(stub_path))

    # Write to file
    output_path.write_text(proxy_code)
    print(f"Generated proxy module: {output_path}")
    print(f"- {len(proxy_code.splitlines())} lines")


def generate_proxy_from_d3_pyi():
    """Generate d3.py proxy from the designer_plugin d3.pyi stub."""
    from importlib.resources import files

    try:
        # Get the installed package path
        stub_file = files('designer_plugin.pystub').joinpath('d3.pyi')

        # Convert to Path for easier manipulation
        stub_path_str = str(stub_file)
        stub_path = Path(stub_path_str)
        output_path: Path = stub_path.with_name(f'{stub_path.stem}_proxy.py')

        # Generate and install the proxy
        install_proxy_module(stub_path, output_path)
        print("=== Successfully generated d3.py ===")
        print(f"Location: {output_path}")
        print("You can now use: from designer_plugin.pystub import d3")

        return output_path

    except ModuleNotFoundError:
        print("Error: designer_plugin not found. Make sure it's installed.")
        raise
    except Exception as e:
        print(f"Error generating proxy: {e}")
        raise


def main() -> int:
    try:
        generate_proxy_from_d3_pyi()
        return 0
    except Exception:
        return 1


if __name__ == "__main__":
    main()
