"""Python source parser using stdlib ast."""

from __future__ import annotations

import ast
from pathlib import Path

from codectx.parser.types import DependencyRecord, EntityRecord, ParseResult, RelationRecord


class _Visitor(ast.NodeVisitor):
    def __init__(self) -> None:
        self.entities: list[EntityRecord] = []
        self.dependencies: list[DependencyRecord] = []
        self.relations: list[RelationRecord] = []
        self.class_stack: list[str] = []
        self.function_stack: list[str] = []
        self.import_aliases: dict[str, str] = {}

    def visit_Import(self, node: ast.Import) -> None:
        for alias in node.names:
            alias_name = alias.asname or alias.name.split(".", 1)[0]
            self.import_aliases[alias_name] = alias.name
            self.dependencies.append(
                DependencyRecord(
                    path=alias.name,
                    type="import",
                    line_number=node.lineno,
                    is_local=alias.name.startswith("."),
                )
            )

    def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
        module = node.module or ""
        prefix = "." * node.level
        module_path = f"{prefix}{module}"
        for alias in node.names:
            if alias.name == "*":
                continue
            alias_name = alias.asname or alias.name
            self.import_aliases[alias_name] = f"{module_path}.{alias.name}".strip(".")
        self.dependencies.append(
            DependencyRecord(
                path=module_path,
                type="from",
                line_number=node.lineno,
                is_local=node.level > 0,
            )
        )

    def visit_ClassDef(self, node: ast.ClassDef) -> None:
        qualified_name = self._qualify(node.name)
        parent = self.class_stack[-1] if self.class_stack else ""
        self.entities.append(
            EntityRecord(
                name=node.name,
                qualified_name=qualified_name,
                type="class",
                kind="class",
                start_line=node.lineno,
                end_line=getattr(node, "end_lineno", node.lineno),
                column_start=node.col_offset + 1,
                column_end=getattr(node, "end_col_offset", node.col_offset + 1),
                docs=ast.get_docstring(node) or "",
                parent=parent,
                visibility="private" if node.name.startswith("_") else "public",
                scope="global" if not self.class_stack else "class",
                language="python",
            )
        )
        if parent:
            self.relations.append(
                RelationRecord(
                    source_qualified_name=parent,
                    target_qualified_name=qualified_name,
                    relation_type="contains",
                    line_number=node.lineno,
                )
            )
        self.class_stack.append(qualified_name)
        self.generic_visit(node)
        self.class_stack.pop()

    def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
        self._visit_function(node, is_async=False)

    def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
        self._visit_function(node, is_async=True)

    def visit_Call(self, node: ast.Call) -> None:
        if self.function_stack:
            target = self._resolve_call_target(node.func)
            if target:
                self.relations.append(
                    RelationRecord(
                        source_qualified_name=self.function_stack[-1],
                        target_qualified_name=target,
                        relation_type="calls",
                        line_number=node.lineno,
                        context=ast.unparse(node.func) if hasattr(ast, "unparse") else "",
                    )
                )
        self.generic_visit(node)

    def _visit_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef, is_async: bool) -> None:
        parent = self.class_stack[-1] if self.class_stack else ""
        qualified_name = self._qualify(node.name)
        entity_type = "method" if parent else "function"
        kind = f"async_{entity_type}" if is_async else entity_type
        try:
            signature = f"{node.name}{ast.unparse(node.args)}"
        except Exception:
            signature = node.name
        self.entities.append(
            EntityRecord(
                name=node.name,
                qualified_name=qualified_name,
                type=entity_type,
                kind=kind,
                signature=signature,
                start_line=node.lineno,
                end_line=getattr(node, "end_lineno", node.lineno),
                column_start=node.col_offset + 1,
                column_end=getattr(node, "end_col_offset", node.col_offset + 1),
                docs=ast.get_docstring(node) or "",
                parent=parent,
                visibility="private" if node.name.startswith("_") else "public",
                scope="class" if parent else "global",
                language="python",
            )
        )
        if parent:
            self.relations.append(
                RelationRecord(
                    source_qualified_name=parent,
                    target_qualified_name=qualified_name,
                    relation_type="contains",
                    line_number=node.lineno,
                )
            )
        self.function_stack.append(qualified_name)
        self.generic_visit(node)
        self.function_stack.pop()

    def _qualify(self, name: str) -> str:
        if self.function_stack:
            return f"{self.function_stack[-1]}.{name}"
        if self.class_stack:
            return f"{self.class_stack[-1]}.{name}"
        return name

    def _resolve_call_target(self, node: ast.expr) -> str:
        if isinstance(node, ast.Name):
            if node.id in self.import_aliases:
                return self.import_aliases[node.id]
            return node.id
        if isinstance(node, ast.Attribute):
            if isinstance(node.value, ast.Name) and node.value.id == "self" and self.class_stack:
                return f"{self.class_stack[-1]}.{node.attr}"
            if isinstance(node.value, ast.Name) and node.value.id in self.import_aliases:
                return f"{self.import_aliases[node.value.id]}.{node.attr}".strip(".")
            return node.attr
        return ""


def parse_python(file_path: str, content: str) -> ParseResult:
    tree = ast.parse(content, filename=str(Path(file_path)))
    visitor = _Visitor()
    visitor.visit(tree)
    return ParseResult(
        file_path=file_path,
        language="python",
        entities=visitor.entities,
        dependencies=visitor.dependencies,
        relations=visitor.relations,
    )
