from __future__ import annotations

from textual.app import ComposeResult
from textual.binding import Binding
from textual.widgets import DataTable, Static
from textual.widget import Widget

from django.db.models import QuerySet

from django_shreck.messages import ShowDetail, ShowCompare, ShowCreate, GoBack
from django_shreck.services.clipboard import copy_to_clipboard
from django_shreck.services.model_introspection import ModelIntrospectionService


class TableWidget(Widget):
    """Displays a Django QuerySet as a scrollable, selectable table."""

    DEFAULT_CSS = """
    TableWidget {
        height: 100%;
        layout: vertical;
    }
    TableWidget #table-header {
        height: 1;
        content-align: center middle;
        background: $primary;
        color: $text;
        text-style: bold;
    }
    TableWidget #compare-bar {
        height: 1;
        content-align: left middle;
        background: $warning;
        color: $text;
        text-style: bold;
        display: none;
    }
    TableWidget #compare-bar.visible {
        display: block;
    }
    TableWidget #queryset-table {
        height: 1fr;
    }
    """

    BINDINGS = [
        Binding("c", "enter_compare_mode", "Compare", show=True),
        Binding("e", "edit_record", "Edit", show=True),
        Binding("plus", "create_record", "New Record", show=True),
        Binding("ctrl+c", "copy_cell", "Copy Cell", show=True),
        Binding("ctrl+shift+c", "copy_row", "Copy Row", show=True),
    ]

    # Maximum rows to load at once to avoid blocking the TUI
    MAX_ROWS = 200

    def __init__(self, queryset: QuerySet, **kwargs) -> None:
        super().__init__(**kwargs)
        self._queryset = queryset
        self._model = queryset.model
        self._rows_data: list = []
        self._compare_mode: bool = False
        self._selected_pks: set[str] = set()

    def compose(self) -> ComposeResult:
        meta = self._model._meta
        count = self._queryset.count()
        title = f"{meta.app_label}.{meta.model_name} — {count} object{'s' if count != 1 else ''}"
        if count > self.MAX_ROWS:
            title += f" (showing first {self.MAX_ROWS})"
        yield Static(title, id="table-header")
        yield Static(
            "  [bold]COMPARE MODE[/bold]  Space/Insert = select row  Enter = confirm  Esc = cancel",
            id="compare-bar",
        )
        yield DataTable(id="queryset-table", cursor_type="cell")

    def on_mount(self) -> None:
        table = self.query_one("#queryset-table", DataTable)
        fields = ModelIntrospectionService.get_model_fields(self._model)

        # Only show concrete fields (those with a column in the DB)
        self._display_fields = [f for f in fields if "column" in f]

        # Add columns
        for field_info in self._display_fields:
            table.add_column(field_info["name"], key=field_info["name"])

        # Add rows
        qs = self._queryset[: self.MAX_ROWS]
        for instance in qs:
            row_values = self._build_row_values(instance)
            table.add_row(*row_values, key=str(instance.pk))
            self._rows_data.append(instance)

        # Keep focus in the result panel
        self.call_after_refresh(table.focus)

    def _build_row_values(self, instance) -> list[str]:
        row_values = []
        for field_info in self._display_fields:
            value = getattr(instance, field_info["name"], "")
            str_val = str(value)
            if len(str_val) > 50:
                str_val = str_val[:47] + "..."
            row_values.append(str_val)
        return row_values

    def _refresh_row_labels(self) -> None:
        """Re-render all rows to show/clear selection checkmarks."""
        table = self.query_one("#queryset-table", DataTable)
        for instance in self._rows_data:
            pk_str = str(instance.pk)
            row_values = self._build_row_values(instance)
            if self._compare_mode and pk_str in self._selected_pks:
                # Prefix first cell with a checkmark
                row_values[0] = f"[bold green]✓[/bold green] {row_values[0]}"
            try:
                table.update_cell(pk_str, self._display_fields[0]["name"], row_values[0])
                for i, field_info in enumerate(self._display_fields[1:], 1):
                    table.update_cell(pk_str, field_info["name"], row_values[i])
            except Exception:
                pass  # Row may not exist yet

    def action_enter_compare_mode(self) -> None:
        """Enter compare selection mode."""
        if self._compare_mode:
            return
        self._compare_mode = True
        self._selected_pks = set()
        bar = self.query_one("#compare-bar", Static)
        bar.add_class("visible")
        self._update_compare_bar()

    def _exit_compare_mode(self) -> None:
        """Exit compare selection mode and reset selection."""
        self._compare_mode = False
        self._selected_pks = set()
        bar = self.query_one("#compare-bar", Static)
        bar.remove_class("visible")
        self._refresh_row_labels()

    def _update_compare_bar(self) -> None:
        bar = self.query_one("#compare-bar", Static)
        count = len(self._selected_pks)
        bar.update(
            f"  [bold]COMPARE MODE[/bold]  {count} selected  "
            "Space/Insert = toggle  Enter = confirm  Esc = cancel"
        )

    def _toggle_current_row_selection(self) -> None:
        """Toggle selection of the currently highlighted row."""
        table = self.query_one("#queryset-table", DataTable)
        if table.cursor_row < 0 or table.cursor_row >= len(self._rows_data):
            return
        instance = self._rows_data[table.cursor_row]
        pk_str = str(instance.pk)
        if pk_str in self._selected_pks:
            self._selected_pks.discard(pk_str)
        else:
            self._selected_pks.add(pk_str)
        self._refresh_row_labels()
        self._update_compare_bar()

    def on_key(self, event) -> None:
        """Handle Space, Insert, Enter, Escape in compare mode; + for new record; e for edit."""
        if not self._compare_mode:
            if event.key in ("plus", "+"):
                event.stop()
                self.post_message(ShowCreate(model=self._model))
            elif event.key == "e":
                event.stop()
                self.action_edit_record()
            elif event.key == "escape":
                event.stop()
                self.post_message(GoBack())
            return
        if event.key in ("space", "insert"):
            event.stop()
            self._toggle_current_row_selection()
        elif event.key == "enter":
            event.stop()
            self._confirm_compare()
        elif event.key == "escape":
            event.stop()
            self._exit_compare_mode()

    def action_create_record(self) -> None:
        """Post ShowCreate to open a blank edit form for this model."""
        self.post_message(ShowCreate(model=self._model))

    def action_edit_record(self) -> None:
        """Post ShowDetail with start_editing=True for the currently highlighted row."""
        instance = self._get_current_instance()
        if instance is not None:
            self.post_message(ShowDetail(instance=instance, editable=True, start_editing=True))

    def _get_current_instance(self):
        """Return the instance under the current cursor row, or None."""
        table = self.query_one("#queryset-table", DataTable)
        if table.cursor_row < 0 or table.cursor_row >= len(self._rows_data):
            return None
        return self._rows_data[table.cursor_row]

    def _get_raw_row_values(self, instance) -> list[str]:
        """Return full (un-truncated) field values for an instance."""
        return [str(getattr(instance, f["name"], "")) for f in self._display_fields]

    def action_copy_row(self) -> None:
        """Copy all field values of the current row to the clipboard (tab-separated)."""
        instance = self._get_current_instance()
        if instance is None:
            return
        headers = "\t".join(f["name"] for f in self._display_fields)
        values = "\t".join(self._get_raw_row_values(instance))
        copy_to_clipboard(f"{headers}\n{values}")
        self.notify("Row copied to clipboard.", severity="information")

    def action_copy_cell(self) -> None:
        """Copy the value of the currently focused cell to the clipboard."""
        table = self.query_one("#queryset-table", DataTable)
        instance = self._get_current_instance()
        if instance is None:
            return
        col_index = table.cursor_column
        if col_index < 0 or col_index >= len(self._display_fields):
            return
        field_name = self._display_fields[col_index]["name"]
        value = str(getattr(instance, field_name, ""))
        copy_to_clipboard(value)
        self.notify(f"Cell '{field_name}' copied to clipboard.", severity="information")

    def _confirm_compare(self) -> None:
        """Post ShowCompare with the selected instances."""
        if len(self._selected_pks) < 2:
            self.notify("Select at least 2 rows to compare.", severity="warning")
            return
        instances = [inst for inst in self._rows_data if str(inst.pk) in self._selected_pks]
        self._exit_compare_mode()
        self.post_message(ShowCompare(instances=instances))

    def on_data_table_cell_selected(self, event: DataTable.CellSelected) -> None:
        """When a cell is selected (Enter pressed outside compare mode), show detail for that row."""
        if self._compare_mode:
            return
        row_key = event.cell_key.row_key
        for instance in self._rows_data:
            if str(instance.pk) == str(row_key.value):
                self.post_message(ShowDetail(instance=instance, editable=True, start_editing=True))
                break

