"""
Output truncation utilities for groknroll.

Provides truncation for tool output to prevent context overflow.
"""

import os
import tempfile
import time
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Optional


class TruncationDirection(Enum):
    """Direction to truncate from."""

    HEAD = "head"  # Keep the beginning
    TAIL = "tail"  # Keep the end


@dataclass
class TruncationResult:
    """Result of truncation operation."""

    content: str
    truncated: bool
    original_lines: int = 0
    original_bytes: int = 0
    output_path: Optional[str] = None  # Path to full output if truncated


class Truncate:
    """
    Output truncation utility.

    Truncates tool output by line count or byte count, optionally saving
    the full output to a file for later retrieval.

    Example:
        result = await Truncate.output(large_content, max_lines=100)
        if result.truncated:
            print(f"Full output saved to: {result.output_path}")
    """

    # Default limits
    MAX_LINES = 2000
    MAX_BYTES = 50 * 1024  # 50KB

    # Directory for truncated output files
    DIR = Path(tempfile.gettempdir()) / "groknroll" / "truncated"

    @classmethod
    def output(
        cls,
        content: str,
        max_lines: Optional[int] = None,
        max_bytes: Optional[int] = None,
        direction: TruncationDirection = TruncationDirection.HEAD,
        save_full: bool = True,
    ) -> TruncationResult:
        """
        Truncate output content if it exceeds limits.

        Args:
            content: The content to potentially truncate
            max_lines: Maximum number of lines (default: MAX_LINES)
            max_bytes: Maximum number of bytes (default: MAX_BYTES)
            direction: Whether to keep head or tail of content
            save_full: Whether to save full output to a file when truncated

        Returns:
            TruncationResult with truncated content and metadata
        """
        if max_lines is None:
            max_lines = cls.MAX_LINES
        if max_bytes is None:
            max_bytes = cls.MAX_BYTES

        original_bytes = len(content.encode("utf-8"))
        lines = content.split("\n")
        original_lines = len(lines)

        # Check if truncation needed
        needs_line_truncation = original_lines > max_lines
        needs_byte_truncation = original_bytes > max_bytes

        if not needs_line_truncation and not needs_byte_truncation:
            return TruncationResult(
                content=content,
                truncated=False,
                original_lines=original_lines,
                original_bytes=original_bytes,
            )

        # Save full output if requested
        output_path = None
        if save_full:
            output_path = cls._save_full_output(content)

        # Truncate by lines
        if needs_line_truncation:
            truncated_lines = original_lines - max_lines

            if direction == TruncationDirection.HEAD:
                kept_lines = lines[:max_lines]
                truncation_msg = f"\n...{truncated_lines} lines truncated..."
                truncated_content = "\n".join(kept_lines) + truncation_msg
            else:  # TAIL
                kept_lines = lines[-max_lines:]
                truncation_msg = f"...{truncated_lines} lines truncated...\n"
                truncated_content = truncation_msg + "\n".join(kept_lines)

            # Build help message
            help_msg = cls._build_help_message(output_path)

            return TruncationResult(
                content=truncated_content + help_msg,
                truncated=True,
                original_lines=original_lines,
                original_bytes=original_bytes,
                output_path=output_path,
            )

        # Truncate by bytes
        if needs_byte_truncation:
            truncated_bytes = original_bytes - max_bytes

            if direction == TruncationDirection.HEAD:
                # Truncate from end, keeping beginning
                truncated_content = content[:max_bytes]
                truncation_msg = f"\n...{truncated_bytes} bytes truncated..."
            else:  # TAIL
                # Truncate from beginning, keeping end
                truncated_content = content[-max_bytes:]
                truncation_msg = f"...{truncated_bytes} bytes truncated...\n"
                truncated_content = truncation_msg + truncated_content

            # Build help message
            help_msg = cls._build_help_message(output_path)

            return TruncationResult(
                content=truncated_content + truncation_msg + help_msg,
                truncated=True,
                original_lines=original_lines,
                original_bytes=original_bytes,
                output_path=output_path,
            )

        # Should not reach here
        return TruncationResult(
            content=content,
            truncated=False,
            original_lines=original_lines,
            original_bytes=original_bytes,
        )

    @classmethod
    def _save_full_output(cls, content: str) -> str:
        """Save full output to a temporary file."""
        cls.DIR.mkdir(parents=True, exist_ok=True)

        # Generate unique filename with timestamp
        timestamp = int(time.time() * 1000)
        filename = f"tool_{timestamp}.txt"
        filepath = cls.DIR / filename

        filepath.write_text(content, encoding="utf-8")
        return str(filepath)

    @classmethod
    def _build_help_message(cls, output_path: Optional[str]) -> str:
        """
        Build help message for truncated output.

        Format matches OpenCode exactly:
        "The tool call succeeded but the output was truncated. Full output saved to: {filepath}
        Use Grep to search the full content or Read with offset/limit to view specific sections."
        """
        if output_path:
            return (
                f"\n\nThe tool call succeeded but the output was truncated. "
                f"Full output saved to: {output_path}\n"
                f"Use Grep to search the full content or Read with offset/limit to view specific sections."
            )
        else:
            return (
                "\n\nThe tool call succeeded but the output was truncated.\n"
                "Use Grep to search the full content or Read with offset/limit to view specific sections."
            )

    @classmethod
    def cleanup(cls, max_age_days: int = 7) -> int:
        """
        Clean up old truncated output files.

        Args:
            max_age_days: Maximum age of files to keep

        Returns:
            Number of files deleted
        """
        if not cls.DIR.exists():
            return 0

        deleted = 0
        max_age_seconds = max_age_days * 24 * 60 * 60
        now = time.time()

        for filepath in cls.DIR.iterdir():
            if filepath.is_file() and filepath.name.startswith("tool_"):
                # Extract timestamp from filename
                try:
                    # Filename format: tool_{timestamp}.txt
                    timestamp_str = filepath.stem.split("_")[1]
                    file_timestamp = int(timestamp_str) / 1000  # ms to seconds

                    if now - file_timestamp > max_age_seconds:
                        filepath.unlink()
                        deleted += 1
                except (ValueError, IndexError):
                    # Invalid filename format, skip
                    pass

        return deleted
