# Copyright 2026 Boring for Gemini Authors
# SPDX-License-Identifier: Apache-2.0
"""
Python Language Handler.

Implements analysis using Python's AST module.
"""

import ast

from ..analysis import (
    CodeClass,
    CodeFunction,
    CodeIssue,
    DocItem,
    DocResult,
    ReviewResult,
    TestGenResult,
)
from .base import BaseHandler


class PythonHandler(BaseHandler):
    """Handler for Python files using AST."""

    @property
    def supported_extensions(self) -> list[str]:
        return [".py"]

    @property
    def language_name(self) -> str:
        return "Python"

    def analyze_for_test_gen(self, file_path: str, source_code: str) -> TestGenResult:
        """Extract functions and classes using AST."""
        try:
            tree = ast.parse(source_code)
        except SyntaxError:
            return TestGenResult(file_path=file_path, functions=[], classes=[])

        functions = []
        classes = []

        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                if not node.name.startswith("_"):
                    functions.append(
                        CodeFunction(
                            name=node.name,
                            args=[arg.arg for arg in node.args.args if arg.arg != "self"],
                            docstring=ast.get_docstring(node),
                            lineno=node.lineno,
                            is_async=isinstance(node, ast.AsyncFunctionDef),
                        )
                    )
            elif isinstance(node, ast.ClassDef):
                methods = []
                for item in node.body:
                    if isinstance(item, ast.FunctionDef) and not item.name.startswith("_"):
                        methods.append(item.name)
                classes.append(
                    CodeClass(
                        name=node.name,
                        methods=methods,
                        docstring=ast.get_docstring(node),
                        lineno=node.lineno,
                    )
                )

        return TestGenResult(
            file_path=file_path,
            functions=functions,
            classes=classes,
            module_name=file_path.split("/")[-1].replace(".py", ""),
            source_language="python",
        )

    def perform_code_review(
        self, file_path: str, source_code: str, focus: str = "all"
    ) -> ReviewResult:
        """Perform AST-based code review."""
        issues = []

        # 1. Naming
        if focus in ("all", "naming"):
            issues.extend(self._check_naming(source_code))

        # 2. Error Handling
        if focus in ("all", "error_handling"):
            issues.extend(self._check_error_handling(source_code))

        # 3. Performance
        if focus in ("all", "performance"):
            issues.extend(self._check_performance(source_code))

        # 4. Security
        if focus in ("all", "security"):
            issues.extend(self._check_security(source_code))

        return ReviewResult(file_path=file_path, issues=issues)

    def generate_test_code(self, result: TestGenResult, project_root: str) -> str:
        """Generate pytest code."""
        # Calculate import path
        # Simple heuristic: assume file path is relative to project root or src
        import_path = result.file_path.replace("\\", "/").replace(".py", "").replace("/", ".")
        if import_path.startswith("src."):
            import_path = import_path[4:]

        lines = [
            "# Auto-generated by Vibe Coder Pro (boring_test_gen)",
            "# Run: pytest <this_file>",
            "",
            "import pytest",
            f"from {import_path} import *",
            "",
            "",
        ]

        for func in result.functions:
            lines.append(f"class Test{func.name.title().replace('_', '')}:")
            lines.append(f'    """Tests for {func.name}."""')
            lines.append("")
            lines.append(f"    def test_{func.name}_basic(self):")
            if func.is_async:
                lines.append("        # TODO: Implement async test")
                lines.append("        # res = await func(...)")
            else:
                lines.append(f"        # TODO: Implement test for {func.name}")
            lines.append("        pass")
            lines.append("")

        for cls in result.classes:
            lines.append(f"class Test{cls.name}:")
            lines.append(f'    """Tests for {cls.name} class."""')
            lines.append("")
            lines.append("    @pytest.fixture")
            lines.append("    def instance(self):")
            lines.append(f"        return {cls.name}()")
            lines.append("")
            for method in cls.methods:
                lines.append(f"    def test_{method}(self, instance):")
                lines.append(f"        # instance.{method}(...)")
                lines.append("        pass")
                lines.append("")

        return "\n".join(lines)

    def extract_dependencies(self, file_path: str, source_code: str) -> list[str]:
        """Extract imports using AST."""
        try:
            tree = ast.parse(source_code)
        except SyntaxError:
            return []

        imports = []
        for node in ast.walk(tree):
            if isinstance(node, ast.Import):
                for name in node.names:
                    imports.append(name.name)
            elif isinstance(node, ast.ImportFrom):
                module_base = ("." * node.level) + (node.module or "")
                if not node.module and node.level > 0:
                    # Case: from . import utils
                    # We want to capture '.utils'
                    for name in node.names:
                        imports.append(f"{module_base}{name.name}")
                else:
                    # Case: from os import path -> os
                    # Case: from .utils import func -> .utils
                    if module_base:
                        imports.append(module_base)

        return sorted(set(imports))

    def extract_documentation(self, file_path: str, source_code: str) -> DocResult:
        """Extract documentation using AST."""
        try:
            tree = ast.parse(source_code)
        except SyntaxError:
            return DocResult(file_path=file_path, module_doc="", items=[])

        module_doc = ast.get_docstring(tree) or ""
        items = []

        for node in ast.iter_child_nodes(tree):
            if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                doc = ast.get_docstring(node)
                if doc:
                    items.append(
                        DocItem(
                            name=node.name,
                            type="function",
                            docstring=doc,
                            signature=f"def {node.name}(...)",
                            line_number=node.lineno,
                        )
                    )
            elif isinstance(node, ast.ClassDef):
                doc = ast.get_docstring(node)
                if doc:
                    items.append(
                        DocItem(
                            name=node.name,
                            type="class",
                            docstring=doc,
                            signature=f"class {node.name}",
                            line_number=node.lineno,
                        )
                    )

                # Check methods
                for child in node.body:
                    if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)):
                        method_doc = ast.get_docstring(child)
                        if method_doc:
                            items.append(
                                DocItem(
                                    name=f"{node.name}.{child.name}",
                                    type="method",
                                    docstring=method_doc,
                                    signature=f"def {child.name}(...)",
                                    line_number=child.lineno,
                                )
                            )

        return DocResult(file_path=file_path, module_doc=module_doc, items=items)

    # --- Internal Check Methods ---

    def _check_naming(self, source: str) -> list[CodeIssue]:
        issues = []
        try:
            tree = ast.parse(source)
            for node in ast.walk(tree):
                if isinstance(node, ast.FunctionDef):
                    if any(c.isupper() for c in node.name) and not node.name.isupper():
                        issues.append(
                            CodeIssue(
                                category="Naming",
                                severity="low",
                                message=f"Function `{node.name}` uses camelCase",
                                suggestion="Use snake_case for Python functions",
                                line=node.lineno,
                            )
                        )
        except SyntaxError:
            pass
        return issues

    def _check_error_handling(self, source: str) -> list[CodeIssue]:
        issues = []
        try:
            tree = ast.parse(source)
            for node in ast.walk(tree):
                if isinstance(node, ast.ExceptHandler):
                    if node.type is None:
                        issues.append(
                            CodeIssue(
                                category="Error Handling",
                                severity="high",
                                message="Bare `except:` used",
                                suggestion="Specify exception type (e.g. `except ValueError:`)",
                                line=node.lineno,
                            )
                        )
        except SyntaxError:
            pass
        return issues

    def _check_performance(self, source: str) -> list[CodeIssue]:
        issues = []
        lines = source.split("\n")

        # AST based check could be better for loops, but line-based is faster for simple patterns
        in_loop = False
        loop_indent = 0

        for i, line in enumerate(lines, 1):
            stripped = line.strip()
            indent = len(line) - len(line.lstrip())

            # Simple loop detection (naive)
            if stripped.startswith("for ") or stripped.startswith("while "):
                in_loop = True
                loop_indent = indent
            elif indent <= loop_indent and stripped:
                in_loop = False

            if in_loop:
                if ".append(" in line:
                    issues.append(
                        CodeIssue(
                            category="Performance",
                            severity="low",
                            message="Using `.append()` inside loop",
                            suggestion="Consider list comprehension for better performance",
                            line=i,
                        )
                    )
                if ".query(" in line or ".objects.get(" in line or ".filter(" in line:
                    issues.append(
                        CodeIssue(
                            category="Performance",
                            severity="high",
                            message="Potential N+1 Query",
                            suggestion="Use `select_related` or `prefetch_related` outside loop",
                            line=i,
                        )
                    )
                if "open(" in line:
                    issues.append(
                        CodeIssue(
                            category="Performance",
                            severity="medium",
                            message="File I/O inside loop",
                            suggestion="Move file opening strictly outside the loop",
                            line=i,
                        )
                    )
                if "time.sleep(" in line:
                    issues.append(
                        CodeIssue(
                            category="Performance",
                            severity="medium",
                            message="Blocking sleep inside loop",
                            suggestion="Consider async/await or event-driven approach",
                            line=i,
                        )
                    )

        return issues

    def _check_security(self, source: str) -> list[CodeIssue]:
        issues = []
        dangerous = [
            ("eval(", "high", "Avoid `eval()` (code injection risk)"),
            ("exec(", "high", "Avoid `exec()` (code injection risk)"),
            ("shell=True", "medium", "Avoid `shell=True` in subprocess"),
            ("password =", "medium", "Possible hardcoded password"),
            ("api_key =", "medium", "Possible hardcoded API key"),
        ]
        lines = source.split("\n")
        for i, line in enumerate(lines, 1):
            for pat, sev, msg in dangerous:
                if pat in line:
                    issues.append(CodeIssue(category="Security", severity=sev, message=msg, line=i))
        return issues
