"""
Documentation generator for FMUS-VID.

This module provides tools for automatically generating documentation from the codebase.
"""

import ast
import inspect
import importlib
import pkgutil
import re
from pathlib import Path
from typing import Dict, List, Optional, Any, Tuple
from dataclasses import dataclass
import logging

logger = logging.getLogger(__name__)

@dataclass
class DocItem:
    """Documentation item containing parsed information."""
    name: str
    docstring: str
    signature: Optional[str] = None
    kind: str = "module"  # module, class, function, or method
    source: Optional[str] = None
    examples: List[str] = None
    params: Dict[str, str] = None
    returns: Optional[str] = None
    raises: List[Tuple[str, str]] = None

    def __post_init__(self):
        """Initialize optional fields."""
        self.examples = self.examples or []
        self.params = self.params or {}
        self.raises = self.raises or []

class DocGenerator:
    """
    Generates documentation from Python source code.
    """

    def __init__(self, root_path: Path):
        """
        Initialize documentation generator.

        Args:
            root_path: Root path of the project
        """
        self.root_path = Path(root_path)
        self.docs: Dict[str, DocItem] = {}

    def generate(self) -> Dict[str, DocItem]:
        """
        Generate documentation for the entire project.

        Returns:
            Dictionary of documentation items
        """
        # Memindai semua modul Python
        for module_info in self._find_modules():
            try:
                module = importlib.import_module(module_info.name)
                self._process_module(module)
            except Exception as e:
                logger.error(f"Failed to process module {module_info.name}: {e}")

        return self.docs

    def _find_modules(self) -> List[pkgutil.ModuleInfo]:
        """Find all Python modules in the project."""
        modules = []
        for module_info in pkgutil.walk_packages(
            [str(self.root_path)],
            prefix="fmusvid."
        ):
            modules.append(module_info)
        return modules

    def _process_module(self, module: Any) -> None:
        """
        Process a module and its contents.

        Args:
            module: Python module to process
        """
        module_doc = self._parse_docstring(module.__doc__ or "")
        self.docs[module.__name__] = DocItem(
            name=module.__name__,
            docstring=module_doc,
            kind="module"
        )

        # Memproses kelas dan fungsi dalam modul
        for name, obj in inspect.getmembers(module):
            if name.startswith('_'):
                continue

            if inspect.isclass(obj):
                self._process_class(obj)
            elif inspect.isfunction(obj):
                self._process_function(obj, module.__name__)

    def _process_class(self, cls: type) -> None:
        """
        Process a class and its methods.

        Args:
            cls: Python class to process
        """
        doc = self._parse_docstring(cls.__doc__ or "")
        signature = str(inspect.signature(cls))

        self.docs[cls.__qualname__] = DocItem(
            name=cls.__qualname__,
            docstring=doc,
            signature=signature,
            kind="class"
        )

        # Memproses metode kelas
        for name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
            if not name.startswith('_') or name == '__init__':
                self._process_method(method, cls.__qualname__)

    def _process_function(self, func: Any, module_name: str) -> None:
        """
        Process a function.

        Args:
            func: Python function to process
            module_name: Name of the containing module
        """
        self._process_callable(func, module_name, "function")

    def _process_method(self, method: Any, class_name: str) -> None:
        """
        Process a class method.

        Args:
            method: Python method to process
            class_name: Name of the containing class
        """
        self._process_callable(method, class_name, "method")

    def _process_callable(
        self,
        func: Any,
        parent_name: str,
        kind: str
    ) -> None:
        """
        Process a callable (function or method).

        Args:
            func: Python callable to process
            parent_name: Name of the parent (module or class)
            kind: Type of callable ("function" or "method")
        """
        if not hasattr(func, '__name__'):
            return

        full_name = f"{parent_name}.{func.__name__}"
        doc = self._parse_docstring(func.__doc__ or "")
        signature = str(inspect.signature(func))

        # Mengekstrak informasi parameter
        params = {}
        for name, param in inspect.signature(func).parameters.items():
            params[name] = str(param)

        # Mengekstrak informasi yang dilempar dari docstring
        raises = []
        if func.__doc__:
            raises_matches = re.findall(
                r'Raises:\s*\n\s*(\w+):\s*(.+?)(?=\n\n|\Z)',
                func.__doc__,
                re.DOTALL
            )
            raises = [(exc, desc.strip()) for exc, desc in raises_matches]

        self.docs[full_name] = DocItem(
            name=full_name,
            docstring=doc,
            signature=signature,
            kind=kind,
            source=inspect.getsource(func),
            params=params,
            raises=raises
        )

    def _parse_docstring(self, docstring: str) -> str:
        """
        Parse a docstring to extract structured information.

        Args:
            docstring: Raw docstring

        Returns:
            Processed docstring
        """
        if not docstring:
            return ""

        # Menghapus indentasi
        lines = docstring.expandtabs().splitlines()

        # Menghapus baris kosong di awal dan akhir
        while lines and not lines[0].strip():
            lines.pop(0)
        while lines and not lines[-1].strip():
            lines.pop()

        # Menentukan indentasi minimum
        indent = float('inf')
        for line in lines:
            stripped = line.lstrip()
            if stripped:
                indent = min(indent, len(line) - len(stripped))

        # Menghapus indentasi minimum dari semua baris
        if indent < float('inf'):
            lines = [
                line[indent:] if len(line) >= indent else ""
                for line in lines
            ]

        return '\n'.join(lines)

class MarkdownGenerator:
    """
    Generates Markdown documentation from parsed documentation items.
    """

    def __init__(self, docs: Dict[str, DocItem]):
        """
        Initialize Markdown generator.

        Args:
            docs: Dictionary of documentation items
        """
        self.docs = docs

    def generate(self) -> str:
        """
        Generate complete Markdown documentation.

        Returns:
            Markdown formatted documentation
        """
        sections = [
            "# FMUS-VID Documentation\n",
            self._generate_toc(),
            self._generate_modules(),
            self._generate_classes(),
            self._generate_functions()
        ]

        return "\n\n".join(sections)

    def _generate_toc(self) -> str:
        """Generate table of contents."""
        toc = ["## Table of Contents\n"]

        # Modules
        toc.append("### Modules")
        for name, item in self.docs.items():
            if item.kind == "module":
                toc.append(f"- [{name}](#{name.replace('.', '')})")

        # Classes
        toc.append("\n### Classes")
        for name, item in self.docs.items():
            if item.kind == "class":
                toc.append(f"- [{name}](#{name.replace('.', '')})")

        # Functions
        toc.append("\n### Functions")
        for name, item in self.docs.items():
            if item.kind == "function":
                toc.append(f"- [{name}](#{name.replace('.', '')})")

        return "\n".join(toc)

    def _generate_modules(self) -> str:
        """Generate module documentation."""
        sections = ["## Modules\n"]

        for name, item in self.docs.items():
            if item.kind == "module":
                sections.append(self._format_module(item))

        return "\n\n".join(sections)

    def _generate_classes(self) -> str:
        """Generate class documentation."""
        sections = ["## Classes\n"]

        for name, item in self.docs.items():
            if item.kind == "class":
                sections.append(self._format_class(item))

        return "\n\n".join(sections)

    def _generate_functions(self) -> str:
        """Generate function documentation."""
        sections = ["## Functions\n"]

        for name, item in self.docs.items():
            if item.kind == "function":
                sections.append(self._format_function(item))

        return "\n\n".join(sections)

    def _format_module(self, item: DocItem) -> str:
        """Format module documentation."""
        return f"### {item.name}\n\n{item.docstring}"

    def _format_class(self, item: DocItem) -> str:
        """Format class documentation."""
        sections = [
            f"### {item.name}",
            f"```python\n{item.signature}\n```\n",
            item.docstring
        ]

        # Menambahkan metode
        methods = [
            doc for doc in self.docs.values()
            if doc.kind == "method" and doc.name.startswith(item.name + ".")
        ]

        if methods:
            sections.append("\n#### Methods\n")
            for method in methods:
                sections.append(self._format_method(method))

        return "\n\n".join(sections)

    def _format_method(self, item: DocItem) -> str:
        """Format method documentation."""
        sections = [
            f"##### {item.name.split('.')[-1]}",
            f"```python\n{item.signature}\n```\n",
            item.docstring
        ]

        if item.params:
            sections.append("\nParameters:")
            for name, param in item.params.items():
                sections.append(f"- `{name}`: {param}")

        if item.raises:
            sections.append("\nRaises:")
            for exc, desc in item.raises:
                sections.append(f"- `{exc}`: {desc}")

        return "\n".join(sections)

    def _format_function(self, item: DocItem) -> str:
        """Format function documentation."""
        sections = [
            f"### {item.name}",
            f"```python\n{item.signature}\n```\n",
            item.docstring
        ]

        if item.params:
            sections.append("\nParameters:")
            for name, param in item.params.items():
                sections.append(f"- `{name}`: {param}")

        if item.raises:
            sections.append("\nRaises:")
            for exc, desc in item.raises:
                sections.append(f"- `{exc}`: {desc}")

        return "\n\n".join(sections)

def generate_docs(root_path: Path, output_path: Path) -> None:
    """
    Generate documentation and save to file.

    Args:
        root_path: Root path of the project
        output_path: Output path for documentation
    """
    # Membuat dokumentasi
    generator = DocGenerator(root_path)
    docs = generator.generate()

    # Menghasilkan Markdown
    markdown = MarkdownGenerator(docs)
    content = markdown.generate()

    # Menyimpan ke file
    output_path.write_text(content, encoding='utf-8')
    logger.info(f"Documentation generated at {output_path}")
