"""Hierarchical directory tree formatter with integrated code structures."""

from pathlib import Path
from collections import defaultdict
from typing import Optional
from datetime import datetime
from .scanners import StructureNode


class DirectoryFormatter:
    """Formats directory scans as hierarchical trees with code structures."""

    # Tree drawing characters
    BRANCH = "├─"
    LAST_BRANCH = "└─"
    VERTICAL = "│  "
    SPACE = "   "

    @staticmethod
    def _format_relative_time(iso_timestamp: str) -> str:
        """Format timestamp as relative time with unix timestamp (e.g., '2 mins ago [ts:1729137900]')."""
        try:
            # Parse ISO timestamp
            modified_time = datetime.fromisoformat(iso_timestamp)
            now = datetime.now()
            diff = now - modified_time

            # Get unix timestamp (seconds since epoch)
            unix_ts = int(modified_time.timestamp())

            # Calculate time difference
            seconds = diff.total_seconds()
            if seconds < 60:
                relative = "just now"
            elif seconds < 3600:  # < 1 hour
                mins = int(seconds / 60)
                relative = f"{mins} min{'s' if mins != 1 else ''} ago"
            elif seconds < 86400:  # < 1 day
                hours = int(seconds / 3600)
                relative = f"{hours} hour{'s' if hours != 1 else ''} ago"
            elif seconds < 604800:  # < 1 week
                days = int(seconds / 86400)
                relative = f"{days} day{'s' if days != 1 else ''} ago"
            elif seconds < 2592000:  # < 30 days
                weeks = int(seconds / 604800)
                relative = f"{weeks} week{'s' if weeks != 1 else ''} ago"
            elif seconds < 31536000:  # < 1 year
                months = int(seconds / 2592000)
                relative = f"{months} month{'s' if months != 1 else ''} ago"
            else:
                years = int(seconds / 31536000)
                relative = f"{years} year{'s' if years != 1 else ''} ago"

            # Return relative time with unix timestamp for LLM processing
            return f"{relative} [ts:{unix_ts}]"
        except Exception:
            # If parsing fails, return empty string
            return ""

    def __init__(self, show_signatures: bool = True, show_decorators: bool = True,
                 show_docstrings: bool = True, show_complexity: bool = False,
                 include_structures: bool = True, flatten_structures: bool = False):
        """
        Initialize directory formatter with display options.

        Args:
            flatten_structures: Show only top-level structures (classes/functions)
                               without nested children (methods). Reduces output by ~50%.
        """
        self.show_signatures = show_signatures
        self.show_decorators = show_decorators
        self.show_docstrings = show_docstrings
        self.show_complexity = show_complexity
        self.include_structures = include_structures
        self.flatten_structures = flatten_structures

    def format(self, base_dir: str, file_structures: dict[str, list[StructureNode]]) -> str:
        """
        Format directory scan as hierarchical tree.

        Args:
            base_dir: Base directory path
            file_structures: Dict mapping file paths to their structure nodes

        Returns:
            Formatted hierarchical tree string
        """
        base_path = Path(base_dir).resolve()

        # Build directory tree
        tree = self._build_tree(base_path, file_structures)

        # Format as text
        lines = [f"{base_path.name}/ {self._format_stats(tree)}"]
        lines.extend(self._format_tree_node(tree, ""))

        return "\n".join(lines)

    def _build_tree(self, base_path: Path, file_structures: dict[str, list[StructureNode]]) -> dict:
        """Build hierarchical directory tree structure."""
        tree = {
            "type": "directory",
            "name": base_path.name,
            "path": base_path,
            "children": {},
            "files": {},
            "stats": {"files": 0, "classes": 0, "functions": 0, "methods": 0}
        }

        for file_path_str, structures in file_structures.items():
            if not structures:
                continue

            file_path = Path(file_path_str).resolve()

            # Get relative path from base
            try:
                rel_path = file_path.relative_to(base_path)
            except ValueError:
                # File is outside base_path, skip it
                continue

            # Navigate/create directory structure
            current = tree
            parts = list(rel_path.parts[:-1])  # All but filename

            for part in parts:
                if part not in current["children"]:
                    current["children"][part] = {
                        "type": "directory",
                        "name": part,
                        "children": {},
                        "files": {},
                        "stats": {"files": 0, "classes": 0, "functions": 0, "methods": 0}
                    }
                current = current["children"][part]

            # Add file to current directory
            filename = rel_path.parts[-1]
            current["files"][filename] = {
                "type": "file",
                "name": filename,
                "path": file_path,
                "structures": structures
            }

            # Update stats recursively up the tree
            self._update_stats(tree, structures)

        return tree

    def _update_stats(self, node: dict, structures: list[StructureNode]):
        """Update statistics for a node and count structures."""
        node["stats"]["files"] += 1

        def count_structures(structs):
            for s in structs:
                if s.type == "class":
                    node["stats"]["classes"] += 1
                elif s.type == "function":
                    node["stats"]["functions"] += 1
                elif s.type == "method":
                    node["stats"]["methods"] += 1

                if hasattr(s, "children") and s.children:
                    count_structures(s.children)

        count_structures(structures)

    def _format_stats(self, node: dict) -> str:
        """Format directory statistics."""
        stats = node["stats"]
        parts = []

        if stats["files"] > 0:
            parts.append(f"{stats['files']} file{'s' if stats['files'] != 1 else ''}")
        if stats["classes"] > 0:
            parts.append(f"{stats['classes']} class{'es' if stats['classes'] != 1 else ''}")
        if stats["functions"] > 0:
            parts.append(f"{stats['functions']} function{'s' if stats['functions'] != 1 else ''}")
        if stats["methods"] > 0:
            parts.append(f"{stats['methods']} method{'s' if stats['methods'] != 1 else ''}")

        return f"({', '.join(parts)})" if parts else ""

    def _format_tree_node(self, node: dict, prefix: str) -> list[str]:
        """Recursively format a tree node and its children."""
        lines = []

        # Get sorted children and files
        dirs = sorted(node["children"].items())
        files = sorted(node["files"].items())

        all_items = [(name, child, True) for name, child in dirs] + \
                    [(name, child, False) for name, child in files]

        for i, (name, child, is_dir) in enumerate(all_items):
            is_last = i == len(all_items) - 1
            connector = self.LAST_BRANCH if is_last else self.BRANCH

            if is_dir:
                # Directory
                stats_str = self._format_stats(child)
                lines.append(f"{prefix}{connector} {name}/ {stats_str}")

                # Recurse into directory
                child_prefix = prefix + (self.SPACE if is_last else self.VERTICAL)
                lines.extend(self._format_tree_node(child, child_prefix))

            else:
                # File
                structures = child["structures"]

                # Check if this is an unsupported file (only has file-info with unsupported flag)
                is_unsupported = (
                    len(structures) == 1
                    and structures[0].type == "file-info"
                    and hasattr(structures[0], "file_metadata")
                    and structures[0].file_metadata
                    and structures[0].file_metadata.get("unsupported", False)
                )

                if is_unsupported:
                    # Unsupported file - show with metadata (no extension needed, it's in the filename)
                    metadata = structures[0].file_metadata
                    size = metadata.get("size_formatted", "")

                    # Format modified time as relative (e.g., "2 mins ago")
                    modified_iso = metadata.get("modified", "")
                    modified_relative = self._format_relative_time(modified_iso) if modified_iso else ""

                    # Build metadata string: size, relative time
                    meta_parts = [size, modified_relative]
                    meta_str = ", ".join(p for p in meta_parts if p)
                    lines.append(f"{prefix}{connector} {name} [{meta_str}]")
                else:
                    # Supported file - show structures with metadata
                    min_line = min(s.start_line for s in self._flatten(structures)) if structures else 1
                    max_line = max(s.end_line for s in self._flatten(structures)) if structures else 1

                    # Extract metadata from file-info node if present
                    file_metadata = None
                    if structures and structures[0].type == "file-info":
                        file_metadata = structures[0].file_metadata

                    # Format metadata (size and modified time)
                    metadata_str = ""
                    if file_metadata:
                        size = file_metadata.get("size_formatted", "")
                        modified_iso = file_metadata.get("modified", "")
                        modified_relative = self._format_relative_time(modified_iso) if modified_iso else ""

                        meta_parts = [size, modified_relative]
                        metadata_str = " [" + ", ".join(p for p in meta_parts if p) + "]"

                    # Format file line
                    if self.include_structures and self.flatten_structures:
                        # Ultra-compact mode: show structures inline
                        display_structures = self._flatten_top_level(structures)
                        if display_structures:
                            # Get just the names of classes and functions
                            names = [s.name for s in display_structures]
                            if len(names) > 5:
                                # Truncate if too many
                                structure_list = ", ".join(names[:5]) + f", ... ({len(names)} total)"
                            else:
                                structure_list = ", ".join(names)
                            lines.append(f"{prefix}{connector} {name} ({min_line}-{max_line}){metadata_str} - {structure_list}")
                        else:
                            lines.append(f"{prefix}{connector} {name} ({min_line}-{max_line}){metadata_str}")
                    elif self.include_structures:
                        # Normal mode: show structures in tree below file
                        lines.append(f"{prefix}{connector} {name} ({min_line}-{max_line}){metadata_str}")
                        child_prefix = prefix + (self.SPACE if is_last else self.VERTICAL)
                        lines.extend(self._format_structures(structures, child_prefix))
                    else:
                        # No structures mode
                        lines.append(f"{prefix}{connector} {name} ({min_line}-{max_line}){metadata_str}")

        return lines

    def _format_structures(self, structures: list[StructureNode], prefix: str) -> list[str]:
        """Format structure nodes with indentation."""
        lines = []

        for i, node in enumerate(structures):
            is_last = i == len(structures) - 1
            lines.extend(self._format_structure_node(node, prefix, is_last))

        return lines

    def _format_structure_node(self, node: StructureNode, prefix: str, is_last: bool) -> list[str]:
        """Format a single structure node."""
        lines = []
        connector = self.LAST_BRANCH if is_last else self.BRANCH

        # Build main line
        parts = [f"{prefix}{connector} {node.type}: {node.name}"]

        # Add signature if available
        if self.show_signatures and hasattr(node, "signature") and node.signature:
            parts.append(node.signature)

        # Add line numbers
        parts.append(f"({node.start_line}-{node.end_line})")

        # Add modifiers if present
        if hasattr(node, "modifiers") and node.modifiers:
            modifiers_str = " ".join(node.modifiers)
            parts.append(f"[{modifiers_str}]")

        lines.append(" ".join(parts))

        # Add decorators on separate lines
        if self.show_decorators and hasattr(node, "decorators") and node.decorators:
            decorator_prefix = prefix + (self.SPACE if is_last else self.VERTICAL) + "  "
            for decorator in node.decorators:
                lines.append(f"{decorator_prefix}{decorator}")

        # Add docstring on separate line
        if self.show_docstrings and hasattr(node, "docstring") and node.docstring:
            docstring_prefix = prefix + (self.SPACE if is_last else self.VERTICAL) + "  "
            lines.append(f'{docstring_prefix}"{node.docstring}"')

        # Format children
        if hasattr(node, "children") and node.children:
            child_prefix = prefix + (self.SPACE if is_last else self.VERTICAL)
            for j, child in enumerate(node.children):
                is_last_child = j == len(node.children) - 1
                lines.extend(self._format_structure_node(child, child_prefix, is_last_child))

        return lines

    def _flatten(self, structures: list[StructureNode]) -> list[StructureNode]:
        """Flatten structure tree to get all nodes."""
        result = []
        for node in structures:
            result.append(node)
            if hasattr(node, "children") and node.children:
                result.extend(self._flatten(node.children))
        return result

    def _flatten_top_level(self, structures: list[StructureNode]) -> list[StructureNode]:
        """
        Create shallow copies of structures without children.

        Returns only top-level classes and functions, stripping nested methods.
        Reduces output by ~50% while maintaining overview.
        """
        flattened = []
        for node in structures:
            # Skip non-code structures (file-info, imports)
            if node.type in ('file-info', 'imports'):
                continue

            # Create shallow copy with no children
            shallow = StructureNode(
                type=node.type,
                name=node.name,
                start_line=node.start_line,
                end_line=node.end_line,
                signature=None,  # Strip signatures for compactness
                decorators=[],   # Strip decorators
                docstring=None,  # Strip docstrings
                complexity=None,
                modifiers=[],
                children=[]  # No children - flattened!
            )
            flattened.append(shallow)
        return flattened
