from __future__ import annotations

import ast
import traceback
from io import StringIO
from contextlib import redirect_stdout, redirect_stderr

from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Vertical
from textual.widgets import Input, RichLog
from textual.widget import Widget

from django.db import models
from django.db.models import QuerySet

from django_shreck.messages import ShowDetail, ShowTable, ShowCompare


class ShellWidget(Widget):
    """Interactive Python shell with auto-imported Django models.

    Evaluates expressions and routes results to the appropriate display widget.
    """

    DEFAULT_CSS = """
    ShellWidget {
        height: 100%;
        layout: vertical;
        border: solid $secondary;
        border-title-color: $success;
    }
    ShellWidget #shell-output {
        height: 1fr;
        scrollbar-size: 1 1;
    }
    ShellWidget #shell-input {
        dock: bottom;
        height: 1;
        border: none;
        padding: 0 1;
        background: $surface-darken-1;
    }
    ShellWidget #shell-input:focus {
        border: none;
        background: $surface-darken-2;
    }
    """

    BINDINGS = [
        Binding("up", "history_back", "Previous command", show=False),
        Binding("down", "history_forward", "Next command", show=False),
    ]

    def __init__(self, namespace: dict | None = None, **kwargs) -> None:
        super().__init__(**kwargs)
        self._namespace: dict = namespace or {}
        self._history: list[str] = []
        self._history_index: int = -1
        # Add common builtins to the namespace
        self._namespace.setdefault("__builtins__", __builtins__)

    def set_namespace(self, namespace: dict) -> None:
        """Update the shell namespace (e.g. with auto-imported models)."""
        self._namespace.update(namespace)

    def compose(self) -> ComposeResult:
        with Vertical():
            yield RichLog(id="shell-output", highlight=True, markup=True, wrap=True)
            yield Input(id="shell-input", placeholder=">>> Type a Python expression...")

    def on_mount(self) -> None:
        self.border_title = "🟢 Shreck Shell"
        output = self.query_one("#shell-output", RichLog)
        output.write("All Django models are auto-imported. Type any Python expression.")
        output.write(f"Available models: {', '.join(sorted(k for k in self._namespace if not k.startswith('_')))}")
        output.write("")

    def on_input_submitted(self, event: Input.Submitted) -> None:
        """Handle shell input submission."""
        code = event.value.strip()
        if not code:
            return

        input_widget = self.query_one("#shell-input", Input)
        output = self.query_one("#shell-output", RichLog)
        input_widget.value = ""

        # Record history
        self._history.append(code)
        self._history_index = len(self._history)

        output.write(f"[bold]>>> {code}[/bold]")

        # Capture stdout/stderr
        stdout_capture = StringIO()
        stderr_capture = StringIO()

        try:
            # Try eval first (expression), then exec (statement)
            result = self._eval_or_exec(code, stdout_capture, stderr_capture)

            # Show captured stdout
            stdout_text = stdout_capture.getvalue()
            if stdout_text:
                output.write(stdout_text.rstrip())

            stderr_text = stderr_capture.getvalue()
            if stderr_text:
                output.write(f"[yellow]{stderr_text.rstrip()}[/yellow]")

            if result is not None:
                # Store result in namespace as _ (like standard Python REPL)
                self._namespace["_"] = result
                output.write(repr(result))

                # Route the result to the appropriate display widget
                self._route_result(result, code)

        except Exception:
            tb = traceback.format_exc()
            output.write(f"[red]{tb}[/red]")

    def _eval_or_exec(self, code: str, stdout_buf: StringIO, stderr_buf: StringIO) -> object | None:
        """Try to eval the code as an expression; fall back to exec as a statement."""
        with redirect_stdout(stdout_buf), redirect_stderr(stderr_buf):
            try:
                # Try to parse as expression
                ast.parse(code, mode="eval")
                return eval(code, self._namespace)
            except SyntaxError:
                # It's a statement — exec it
                exec(code, self._namespace)
                return None

    def _route_result(self, result: object, code: str) -> None:
        """Inspect the result and post the appropriate message to display it."""
        # Check if it's a queryset — always display as a table
        if isinstance(result, QuerySet):
            self.post_message(ShowTable(queryset=result))
            return

        # Check if it's a tuple/list of model instances — show compare
        if isinstance(result, (list, tuple)) and len(result) >= 2:
            if all(isinstance(item, models.Model) for item in result):
                self.post_message(ShowCompare(instances=list(result)))
                return

        # Check if it's a single model instance
        if isinstance(result, models.Model):
            # It's editable if it was assigned to a variable
            # Simple heuristic: if code is an assignment like `x = ...`, it's editable
            editable = "=" in code and not code.startswith("=") and "==" not in code
            self.post_message(ShowDetail(instance=result, editable=editable))
            return


    def action_history_back(self) -> None:
        """Navigate to the previous command in history."""
        if self._history and self._history_index > 0:
            self._history_index -= 1
            input_widget = self.query_one("#shell-input", Input)
            input_widget.value = self._history[self._history_index]

    def action_history_forward(self) -> None:
        """Navigate to the next command in history."""
        input_widget = self.query_one("#shell-input", Input)
        if self._history_index < len(self._history) - 1:
            self._history_index += 1
            input_widget.value = self._history[self._history_index]
        else:
            self._history_index = len(self._history)
            input_widget.value = ""

