"""
Code Summary Generator - LLM-powered hierarchical summary generation

Generates summaries at multiple levels:
- Package: Directory-level summaries aggregated from modules
- Module: File-level summaries
- Class: Class definition summaries with methods
- Function: Function definition summaries

Uses the configured LLM backend directly for efficient summary generation.
"""

import ast
import hashlib
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Optional

from groknroll.clients import BaseLM, get_client
from groknroll.core.types import ClientBackend
from groknroll.storage.database import Database


@dataclass
class FileSummary:
    """Summary of a single file"""

    file_path: str
    summary: str
    keywords: list[str]
    classes: list["ClassSummary"]
    functions: list["FunctionSummary"]
    imports: list[str]
    content_hash: str


@dataclass
class ClassSummary:
    """Summary of a class definition"""

    name: str
    summary: str
    keywords: list[str]
    signature: str
    docstring: Optional[str]
    methods: list[str]
    line_start: int
    line_end: int


@dataclass
class FunctionSummary:
    """Summary of a function definition"""

    name: str
    summary: str
    keywords: list[str]
    signature: str
    docstring: Optional[str]
    line_start: int
    line_end: int


class CodeSummaryGenerator:
    """
    Generate hierarchical summaries using LLM.

    Supports incremental updates - only re-summarizes changed files
    based on content hash comparison.
    """

    # Prompt templates
    FILE_SUMMARY_PROMPT = """Analyze this Python file and provide a concise summary.

File: {file_path}

```python
{content}
```

Provide:
1. A 2-3 sentence summary of what this file does
2. 5-10 keywords that describe its functionality (lowercase, underscore_separated)

Format your response as:
SUMMARY: <your summary>
KEYWORDS: <keyword1>, <keyword2>, ...
"""

    CLASS_SUMMARY_PROMPT = """Analyze this Python class and provide a concise summary.

Class: {class_name}
File: {file_path}

```python
{content}
```

Provide:
1. A 1-2 sentence summary of what this class does
2. 3-5 keywords that describe its functionality (lowercase, underscore_separated)

Format your response as:
SUMMARY: <your summary>
KEYWORDS: <keyword1>, <keyword2>, ...
"""

    FUNCTION_SUMMARY_PROMPT = """Analyze this Python function and provide a concise summary.

Function: {function_name}
File: {file_path}

```python
{content}
```

Provide:
1. A 1 sentence summary of what this function does
2. 2-4 keywords that describe its functionality (lowercase, underscore_separated)

Format your response as:
SUMMARY: <your summary>
KEYWORDS: <keyword1>, <keyword2>, ...
"""

    PACKAGE_SUMMARY_PROMPT = """Summarize this Python package based on its modules.

Package: {package_path}

Modules and their summaries:
{module_summaries}

Provide:
1. A 2-3 sentence summary of what this package provides
2. 5-10 keywords that describe its functionality (lowercase, underscore_separated)

Format your response as:
SUMMARY: <your summary>
KEYWORDS: <keyword1>, <keyword2>, ...
"""

    def __init__(
        self,
        database: Database,
        project_id: int,
        backend: ClientBackend = "anthropic",
        model: str = "claude-sonnet-4-20250514",
        verbose: bool = False,
    ):
        """
        Initialize summary generator.

        Args:
            database: Database instance for storing summaries
            project_id: Project ID in database
            backend: LLM backend to use
            model: Model name for summaries
            verbose: Whether to print progress
        """
        self.database = database
        self.project_id = project_id
        self.backend = backend
        self.model = model
        self.verbose = verbose
        self._client: Optional[BaseLM] = None

    @property
    def client(self) -> BaseLM:
        """Lazy-initialize LLM client."""
        if self._client is None:
            self._client = get_client(self.backend, {"model_name": self.model})
        return self._client

    def summarize_codebase(
        self,
        root_path: Path,
        files: dict[str, Any],
        force_rescan: bool = False,
    ) -> int:
        """
        Generate summaries for entire codebase incrementally.

        Args:
            root_path: Root path of the codebase
            files: Dict mapping relative_path to FileInfo from indexer
            force_rescan: If True, regenerate all summaries

        Returns:
            Number of files summarized
        """
        if self.verbose:
            print(f"📝 Generating summaries for {len(files)} files...")

        # Compute content hashes for all files
        content_hashes = {}
        for rel_path, file_info in files.items():
            if file_info.content:
                content_hashes[rel_path] = self._compute_hash(file_info.content)

        # Find files needing summary updates
        if force_rescan:
            files_to_summarize = list(files.keys())
        else:
            files_to_summarize = self._find_changed_files(content_hashes)

        if self.verbose:
            print(f"   {len(files_to_summarize)} files need summarization")

        summarized_count = 0
        for rel_path in files_to_summarize:
            file_info = files.get(rel_path)
            if file_info and file_info.content and file_info.language == "python":
                try:
                    self.summarize_file(rel_path, file_info.content)
                    summarized_count += 1
                    if self.verbose and summarized_count % 10 == 0:
                        print(f"   Summarized {summarized_count}/{len(files_to_summarize)} files")
                except Exception as e:
                    if self.verbose:
                        print(f"   ⚠️  Error summarizing {rel_path}: {e}")

        # Generate package summaries
        if summarized_count > 0:
            self._generate_package_summaries(root_path, files)

        if self.verbose:
            print(f"✅ Summarized {summarized_count} files")

        return summarized_count

    def summarize_file(self, file_path: str, content: str) -> FileSummary:
        """
        Generate summary for a single file.

        Args:
            file_path: Relative path to file
            content: File content

        Returns:
            FileSummary with all extracted information
        """
        content_hash = self._compute_hash(content)

        # Parse AST to extract structure
        try:
            tree = ast.parse(content)
        except SyntaxError:
            # Can't parse - create minimal summary
            return self._create_minimal_summary(file_path, content, content_hash)

        # Extract classes and functions
        classes = []
        functions = []
        imports = []

        for node in ast.walk(tree):
            if isinstance(node, ast.ClassDef):
                class_summary = self._summarize_class(file_path, content, node)
                classes.append(class_summary)
            elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                # Only top-level functions
                if not any(
                    isinstance(parent, ast.ClassDef) for parent in ast.walk(tree)
                ):
                    func_summary = self._summarize_function(file_path, content, node)
                    functions.append(func_summary)
            elif isinstance(node, ast.Import):
                for alias in node.names:
                    imports.append(alias.name)
            elif isinstance(node, ast.ImportFrom):
                if node.module:
                    imports.append(node.module)

        # Generate file-level summary
        file_summary_text, file_keywords = self._generate_file_summary(
            file_path, content
        )

        # Store in database
        module_summary = self.database.store_summary(
            project_id=self.project_id,
            scope_type="module",
            scope_path=file_path,
            scope_name=None,
            summary=file_summary_text,
            keywords=file_keywords,
            content_hash=content_hash,
        )

        # Store class and function summaries
        for cls in classes:
            self.database.store_summary(
                project_id=self.project_id,
                scope_type="class",
                scope_path=file_path,
                scope_name=cls.name,
                summary=cls.summary,
                keywords=cls.keywords,
                signature=cls.signature,
                docstring=cls.docstring,
                parent_id=module_summary.id,
                line_start=cls.line_start,
                line_end=cls.line_end,
            )

        for func in functions:
            self.database.store_summary(
                project_id=self.project_id,
                scope_type="function",
                scope_path=file_path,
                scope_name=func.name,
                summary=func.summary,
                keywords=func.keywords,
                signature=func.signature,
                docstring=func.docstring,
                parent_id=module_summary.id,
                line_start=func.line_start,
                line_end=func.line_end,
            )

        return FileSummary(
            file_path=file_path,
            summary=file_summary_text,
            keywords=file_keywords,
            classes=classes,
            functions=functions,
            imports=imports,
            content_hash=content_hash,
        )

    def summarize_package(
        self, package_path: str, child_summaries: list[str]
    ) -> tuple[str, list[str]]:
        """
        Generate summary for a package from child summaries.

        Args:
            package_path: Path to the package directory
            child_summaries: List of module summaries

        Returns:
            Tuple of (summary_text, keywords)
        """
        module_summaries = "\n".join(
            f"- {s}" for s in child_summaries if s
        )

        prompt = self.PACKAGE_SUMMARY_PROMPT.format(
            package_path=package_path,
            module_summaries=module_summaries,
        )

        try:
            response = self.client.completion(prompt)
            return self._parse_summary_response(response)
        except Exception as e:
            if self.verbose:
                print(f"   ⚠️  Error generating package summary: {e}")
            return self._extract_keywords_heuristic(module_summaries)

    def _summarize_class(
        self, file_path: str, content: str, node: ast.ClassDef
    ) -> ClassSummary:
        """Generate summary for a class."""
        # Extract class code
        class_code = ast.get_source_segment(content, node) or ""

        # Get signature (inheritance)
        bases = [
            ast.unparse(base) if hasattr(ast, "unparse") else str(base)
            for base in node.bases
        ]
        signature = f"class {node.name}({', '.join(bases)})" if bases else f"class {node.name}"

        # Get docstring
        docstring = ast.get_docstring(node)

        # Get methods
        methods = [
            n.name for n in node.body if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))
        ]

        # Generate summary
        if len(class_code) > 3000:
            # Too long - use docstring or heuristic
            if docstring:
                summary = docstring.split("\n")[0]
                keywords = self._extract_keywords_from_text(docstring)
            else:
                summary = f"Class {node.name} with methods: {', '.join(methods[:5])}"
                keywords = [node.name.lower()] + [m.lower() for m in methods[:3]]
        else:
            prompt = self.CLASS_SUMMARY_PROMPT.format(
                class_name=node.name,
                file_path=file_path,
                content=class_code,
            )
            try:
                response = self.client.completion(prompt)
                summary, keywords = self._parse_summary_response(response)
            except Exception:
                summary = docstring.split("\n")[0] if docstring else f"Class {node.name}"
                keywords = [node.name.lower()]

        return ClassSummary(
            name=node.name,
            summary=summary,
            keywords=keywords,
            signature=signature,
            docstring=docstring,
            methods=methods,
            line_start=node.lineno,
            line_end=node.end_lineno or node.lineno,
        )

    def _summarize_function(
        self, file_path: str, content: str, node: ast.FunctionDef | ast.AsyncFunctionDef
    ) -> FunctionSummary:
        """Generate summary for a function."""
        # Extract function code
        func_code = ast.get_source_segment(content, node) or ""

        # Get signature
        try:
            args = ast.unparse(node.args) if hasattr(ast, "unparse") else ""
        except Exception:
            args = ""
        async_prefix = "async " if isinstance(node, ast.AsyncFunctionDef) else ""
        signature = f"{async_prefix}def {node.name}({args})"

        # Get docstring
        docstring = ast.get_docstring(node)

        # Generate summary
        if len(func_code) > 2000:
            # Too long - use docstring or heuristic
            if docstring:
                summary = docstring.split("\n")[0]
                keywords = self._extract_keywords_from_text(docstring)
            else:
                summary = f"Function {node.name}"
                keywords = [node.name.lower()]
        else:
            prompt = self.FUNCTION_SUMMARY_PROMPT.format(
                function_name=node.name,
                file_path=file_path,
                content=func_code,
            )
            try:
                response = self.client.completion(prompt)
                summary, keywords = self._parse_summary_response(response)
            except Exception:
                summary = docstring.split("\n")[0] if docstring else f"Function {node.name}"
                keywords = [node.name.lower()]

        return FunctionSummary(
            name=node.name,
            summary=summary,
            keywords=keywords,
            signature=signature,
            docstring=docstring,
            line_start=node.lineno,
            line_end=node.end_lineno or node.lineno,
        )

    def _generate_file_summary(
        self, file_path: str, content: str
    ) -> tuple[str, list[str]]:
        """Generate file-level summary."""
        # Truncate very long files
        if len(content) > 8000:
            content = content[:8000] + "\n... (truncated)"

        prompt = self.FILE_SUMMARY_PROMPT.format(
            file_path=file_path,
            content=content,
        )

        try:
            response = self.client.completion(prompt)
            return self._parse_summary_response(response)
        except Exception as e:
            if self.verbose:
                print(f"   ⚠️  Error generating file summary: {e}")
            return self._extract_keywords_heuristic(content)

    def _generate_package_summaries(
        self, root_path: Path, files: dict[str, Any]
    ) -> None:
        """Generate package summaries from module summaries."""
        # Group files by directory
        packages: dict[str, list[str]] = {}
        for rel_path in files.keys():
            if rel_path.endswith(".py"):
                package_path = str(Path(rel_path).parent)
                if package_path not in packages:
                    packages[package_path] = []
                packages[package_path].append(rel_path)

        # Generate summaries for each package
        for package_path, module_paths in packages.items():
            if package_path == ".":
                continue  # Skip root

            # Get module summaries
            child_summaries = []
            for mod_path in module_paths:
                summary = self.database.get_summary(
                    self.project_id, "module", mod_path
                )
                if summary and summary.summary:
                    child_summaries.append(f"{Path(mod_path).name}: {summary.summary}")

            if child_summaries:
                pkg_summary, pkg_keywords = self.summarize_package(
                    package_path, child_summaries
                )
                self.database.store_summary(
                    project_id=self.project_id,
                    scope_type="package",
                    scope_path=package_path,
                    scope_name=None,
                    summary=pkg_summary,
                    keywords=pkg_keywords,
                )

    def _find_changed_files(self, content_hashes: dict[str, str]) -> list[str]:
        """Find files that have changed since last summarization."""
        changed = []
        for file_path, current_hash in content_hashes.items():
            existing = self.database.get_summary(
                self.project_id, "module", file_path
            )
            if not existing or existing.content_hash != current_hash:
                changed.append(file_path)
        return changed

    def _create_minimal_summary(
        self, file_path: str, content: str, content_hash: str
    ) -> FileSummary:
        """Create minimal summary when AST parsing fails."""
        summary, keywords = self._extract_keywords_heuristic(content)
        return FileSummary(
            file_path=file_path,
            summary=summary,
            keywords=keywords,
            classes=[],
            functions=[],
            imports=[],
            content_hash=content_hash,
        )

    def _parse_summary_response(self, response: str) -> tuple[str, list[str]]:
        """Parse LLM response to extract summary and keywords."""
        summary = ""
        keywords = []

        # Extract summary
        summary_match = re.search(r"SUMMARY:\s*(.+?)(?=KEYWORDS:|$)", response, re.DOTALL)
        if summary_match:
            summary = summary_match.group(1).strip()

        # Extract keywords
        keywords_match = re.search(r"KEYWORDS:\s*(.+?)$", response, re.DOTALL)
        if keywords_match:
            keywords_str = keywords_match.group(1).strip()
            keywords = [k.strip().lower() for k in keywords_str.split(",") if k.strip()]

        return summary, keywords

    def _extract_keywords_heuristic(self, content: str) -> tuple[str, list[str]]:
        """Extract keywords using simple heuristics (fallback)."""
        # Extract function and class names
        func_pattern = r"def\s+(\w+)\s*\("
        class_pattern = r"class\s+(\w+)"

        funcs = re.findall(func_pattern, content)
        classes = re.findall(class_pattern, content)

        keywords = list(set(
            [f.lower() for f in funcs[:5]] +
            [c.lower() for c in classes[:5]]
        ))

        summary = f"Contains {len(classes)} classes and {len(funcs)} functions"
        return summary, keywords

    def _extract_keywords_from_text(self, text: str) -> list[str]:
        """Extract keywords from text using simple patterns."""
        # Split camelCase and snake_case
        words = re.findall(r"[a-z]+(?:[A-Z][a-z]*)*|[A-Z][a-z]+", text)
        words = [w.lower() for w in words if len(w) > 2]
        # Get most common
        word_counts: dict[str, int] = {}
        for w in words:
            word_counts[w] = word_counts.get(w, 0) + 1
        sorted_words = sorted(word_counts.items(), key=lambda x: -x[1])
        return [w for w, _ in sorted_words[:5]]

    @staticmethod
    def _compute_hash(content: str) -> str:
        """Compute content hash for change detection."""
        return hashlib.sha256(content.encode()).hexdigest()[:16]
