#!/usr/bin/env python3
"""Memory information panel component."""

import curses
from typing import Dict, List, Optional, Tuple

from ...core.system.memory import MemoryStats
from ..display import Display


class MemoryPanel:
    """Renders system memory information panel."""

    def __init__(self, display: Display):
        """Initialize the memory panel.

        Args:
            display: Display instance
        """
        self.display = display

    def _theme_attr(self, role: str, fallback_pair: int = 0) -> int:
        """Resolve theme attribute with compatibility fallback for tests."""
        getter = getattr(self.display, "get_theme_attr", None)
        if callable(getter):
            return int(getter(role))
        return curses.color_pair(fallback_pair)

    @staticmethod
    def _bytes_to_gb(value: float) -> float:
        """Convert bytes to GiB for display."""
        return float(value) / (1024**3)

    @staticmethod
    def _clamp_percent(value: float) -> float:
        """Clamp percentages to a safe display range."""
        return max(0.0, min(100.0, float(value)))

    def _render_capacity_line(
        self,
        title: str,
        value: float,
        total: float,
        y: int,
        x: int,
        width: int,
    ) -> int:
        """Render one capacity-oriented memory line."""
        total = max(0.0, float(total))
        value = max(0.0, float(value))
        percent = (value / total * 100.0) if total > 0 else 0.0
        percent = self._clamp_percent(percent)
        bar_width = max(8, min(24, width - 32))
        bar, color = self.display.create_bar(percent, bar_width)
        value_gb = self._bytes_to_gb(value)
        total_gb = self._bytes_to_gb(total)
        line = (
            f"{title:<9} [{bar}] {value_gb:5.1f}/{total_gb:5.1f}GB "
            f"({percent:5.1f}%)"
        )
        self.display.safe_addstr(y, x, line, color)
        return y + 1

    def _render_breakdown_line(
        self,
        title: str,
        value: float,
        total: float,
        y: int,
        x: int,
        width: int,
    ) -> int:
        """Render one RAM-layout breakdown line anchored to total RAM."""
        total = max(0.0, float(total))
        value = max(0.0, float(value))
        percent = (value / total * 100.0) if total > 0 else 0.0
        percent = self._clamp_percent(percent)
        bar_width = max(6, min(18, width - 26))
        bar, color = self.display.create_bar(percent, bar_width)
        value_gb = self._bytes_to_gb(value)
        line = f"{title:<10} [{bar}] {value_gb:5.1f}GB ({percent:5.1f}%)"
        self.display.safe_addstr(y, x, line, color)
        return y + 1

    def _breakdown_items(
        self,
        memory_stats: MemoryStats,
        memory_types: Dict[str, float],
    ) -> List[Tuple[str, float]]:
        """Build an intuitive RAM-layout breakdown from Linux memory categories."""
        cached = max(0.0, float(memory_types.get("cached", 0.0)))
        buffers = max(0.0, float(memory_types.get("buffers", 0.0)))
        shared = max(0.0, float(memory_types.get("shared", 0.0)))
        slab = max(0.0, float(memory_types.get("slab", 0.0)))
        app_anon = max(0.0, float(memory_stats.used) - cached - buffers)

        items = [
            ("App/Anon", app_anon),
            ("Cached", cached),
            ("Buffers", buffers),
            ("Shared", shared),
            ("Slab", slab),
        ]
        return [(label, value) for label, value in items if value > 0.0]

    def _render_key_value_grid(
        self,
        rows: List[Tuple[str, str]],
        y: int,
        x: int,
        width: int,
    ) -> int:
        """Render a compact key/value grid."""
        if not rows:
            return y

        columns = 2 if width >= 54 else 1
        rows_per_col = (len(rows) + columns - 1) // columns
        col_width = max(20, width // columns)

        for index, (label, value) in enumerate(rows):
            col = index // rows_per_col
            row = index % rows_per_col
            cur_x = x + (col * col_width)
            cur_y = y + row
            line = f"{label:<12} {value:>8}"
            self.display.safe_addstr(
                cur_y,
                cur_x,
                line,
                self._theme_attr("muted", fallback_pair=5),
            )

        return y + rows_per_col

    def _render_capacity_block(
        self,
        memory_stats: MemoryStats,
        memory_types: Dict[str, float],
        y: int,
        x: int,
        width: int,
    ) -> int:
        """Render headline RAM/swap capacity usage."""
        self.display.safe_addstr(
            y,
            x,
            "Capacity",
            self._theme_attr("label", fallback_pair=5) | curses.A_BOLD,
        )
        y += 1

        y = self._render_capacity_line(
            "RAM",
            memory_stats.used,
            memory_stats.total,
            y,
            x,
            width,
        )
        y = self._render_capacity_line(
            "Headroom",
            memory_types.get("available", getattr(memory_stats, "free", 0.0)),
            memory_stats.total,
            y,
            x,
            width,
        )

        if memory_stats.swap_total > 0:
            y = self._render_capacity_line(
                "Swap",
                memory_stats.swap_used,
                memory_stats.swap_total,
                y,
                x,
                width,
            )

        return y

    def _render_breakdown_block(
        self,
        memory_stats: MemoryStats,
        memory_types: Dict[str, float],
        y: int,
        x: int,
        width: int,
    ) -> int:
        """Render a RAM-layout breakdown anchored to total RAM."""
        self.display.safe_addstr(
            y,
            x,
            "RAM Layout",
            self._theme_attr("label", fallback_pair=5) | curses.A_BOLD,
        )
        y += 1

        for label, value in self._breakdown_items(memory_stats, memory_types):
            y = self._render_breakdown_line(
                label,
                value,
                memory_stats.total,
                y,
                x,
                width,
            )

        return y

    def _render_signals_block(
        self,
        memory_stats: MemoryStats,
        memory_types: Dict[str, float],
        y: int,
        x: int,
        width: int,
    ) -> int:
        """Render compact secondary memory signals."""
        cached = max(0.0, float(memory_types.get("cached", 0.0)))
        buffers = max(0.0, float(memory_types.get("buffers", 0.0)))
        reclaimable = cached + buffers
        available_gb = self._bytes_to_gb(memory_types.get("available", 0.0))
        rows = [
            ("Used", f"{self._bytes_to_gb(memory_stats.used):.1f} GB"),
            ("Available", f"{available_gb:.1f} GB"),
            ("Reclaimable", f"{self._bytes_to_gb(reclaimable):.1f} GB"),
        ]
        if memory_stats.swap_total > 0:
            swap_free = getattr(
                memory_stats,
                "swap_free",
                max(
                    0.0,
                    float(memory_stats.swap_total) - float(memory_stats.swap_used),
                ),
            )
            rows.append(("Swap Free", f"{self._bytes_to_gb(swap_free):.1f} GB"))

        self.display.safe_addstr(
            y,
            x,
            "Signals",
            self._theme_attr("label", fallback_pair=5) | curses.A_BOLD,
        )
        y += 1
        return self._render_key_value_grid(rows, y, x, width)

    def render(
        self,
        memory_stats: MemoryStats,
        memory_types: Dict[str, float],
        start_y: int = 3,
        indent: int = 2,
    ) -> int:
        """Render the complete memory panel.

        Args:
            memory_stats: Memory statistics
            memory_types: Detailed memory type information
            start_y: Starting Y coordinate
            indent: Left indentation

        Returns:
            Next Y coordinate
        """
        self.display.safe_addstr(
            start_y,
            indent,
            "System Memory",
            self._theme_attr("header", fallback_pair=1) | curses.A_BOLD,
        )
        y = start_y + 2
        content_width = max(40, self.display.width - (indent * 2) - 2)

        if content_width >= 96:
            gap = 4
            left_width = (content_width - gap) // 2
            right_width = content_width - gap - left_width
            left_y = self._render_capacity_block(
                memory_stats,
                memory_types,
                y,
                indent,
                left_width,
            )
            right_y = self._render_breakdown_block(
                memory_stats,
                memory_types,
                y,
                indent + left_width + gap,
                right_width,
            )
            y = max(left_y, right_y) + 1
            y = self._render_signals_block(
                memory_stats,
                memory_types,
                y,
                indent,
                content_width,
            )
            return y + 1

        y = self._render_capacity_block(
            memory_stats,
            memory_types,
            y,
            indent,
            content_width,
        )
        y += 1
        y = self._render_breakdown_block(
            memory_stats,
            memory_types,
            y,
            indent,
            content_width,
        )
        y += 1
        y = self._render_signals_block(
            memory_stats,
            memory_types,
            y,
            indent,
            content_width,
        )
        return y + 1

    def create_warning_message(
        self, threshold: float = 90.0, memory_stats: Optional[MemoryStats] = None
    ) -> str:
        """Create a warning message if memory usage is high.

        Args:
            threshold: Warning threshold percentage
            memory_stats: Memory statistics

        Returns:
            Warning message if threshold exceeded, empty string otherwise
        """
        if not memory_stats:
            return ""

        messages = []
        if memory_stats.percent >= threshold:
            messages.append(f"High RAM usage: {memory_stats.percent:.1f}%")

        if memory_stats.swap_total > 0 and memory_stats.swap_percent >= threshold:
            messages.append(f"High swap usage: {memory_stats.swap_percent:.1f}%")

        return " | ".join(messages)
