import logging
import os
import platform

import libcst as cst

logger = logging.getLogger(__name__)


class ImportInfo:
    def __init__(self, module_name: str, module_path: str, type: str, imported_items: set[str]):
        self.module_name = module_name
        self.module_path = module_path
        self.type = type
        self.imported_items = imported_items

    def __repr__(self) -> str:
        return (
            f"ImportInfo(module_name={self.module_name}, module_path={self.module_path}, "
            f"type={self.type}, imported_items={list(self.imported_items)})"
        )


class ImportPathVisitor(cst.CSTVisitor):
    def __init__(self, project_path: str, absolute_file_path: str, module: cst.Module):
        self.project_path = project_path
        self.absolute_file_path = absolute_file_path
        self.module = module
        self.local_imports: dict[str, ImportInfo] = {}

    def visit_Import(self, node: cst.Import) -> None:
        if not isinstance(node.names, cst.ImportStar):
            for alias in node.names:
                module_name = self._get_full_module_name(alias.name)
                module_path = self._find_import_path(module_name)
                if module_path:
                    self.local_imports[module_name] = ImportInfo(
                        module_name, module_path, "full", set()
                    )

    def visit_ImportFrom(self, node: cst.ImportFrom) -> None:
        if node.module is not None:
            module_name = self._get_full_module_name(node.module)
            module_path = self._find_import_path(module_name)
            imported_items: set[str] = set()
            if module_path:
                if isinstance(node.names, cst.ImportStar):
                    self.local_imports[module_name] = ImportInfo(
                        module_name, module_path, "full", imported_items
                    )
                else:
                    for name in node.names:
                        if isinstance(name.name, cst.Name):
                            imported_items.add(name.name.value)
                    self.local_imports[module_name] = ImportInfo(
                        module_name, module_path, "specific", imported_items
                    )

    def _find_import_path(self, module_name: str) -> str:
        sep = "\\" if platform.system() == "Windows" else "/"

        # Option 1: from project root
        module_path = os.path.join(self.project_path, module_name.replace(".", sep)) + ".py"
        if os.path.exists(module_path):
            return module_path

        # Option 2: from target file directory
        absolute_file_dir = os.path.dirname(self.absolute_file_path)
        module_path = os.path.join(absolute_file_dir, module_name.replace(".", sep)) + ".py"
        if os.path.exists(module_path):
            return module_path

        if "." in module_name:
            # Option 3: walk up 3 parent directories
            modified_path = absolute_file_dir
            for _ in range(1, 4):
                modified_path = os.path.join(modified_path, "..")
                module_path = os.path.join(modified_path, module_name.replace(".", sep)) + ".py"
                if os.path.exists(module_path):
                    return module_path

            # Option 4: search entire project for filename
            export_filename = module_name.split(".")[-1] + ".py"
            for root, _, files in os.walk(self.project_path):
                if export_filename in files:
                    return os.path.join(root, export_filename)

        return ""

    def _get_full_module_name(self, module_node: cst.CSTNode) -> str:
        if isinstance(module_node, cst.Name):
            return module_node.value
        elif isinstance(module_node, cst.Attribute):
            names: list[str] = []
            while isinstance(module_node, cst.Attribute):
                names.insert(0, module_node.attr.value)
                module_node = module_node.value
            if isinstance(module_node, cst.Name):
                names.insert(0, module_node.value)
            return ".".join(names)
        return ""
