"""Tests for _on_proc_done, _stop, _clear, _write_output, _append_output."""

from __future__ import annotations

import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING, Any
from unittest.mock import MagicMock

from python_pkg.repo_explorer._execution import ExecutionMixin

if TYPE_CHECKING:
    from pathlib import Path
    import subprocess


class StubExecution(ExecutionMixin):
    """Concrete stub for testing ExecutionMixin methods."""

    _IDLE_FLUSH_TICKS = 2

    def __init__(self) -> None:
        self._proc: subprocess.Popen[bytes] | None = None
        self._master_fd: int | None = None
        self._terminal_args: list[str] = ["kitty", "--"]
        self._args_var = MagicMock(spec=tk.StringVar)
        self._stdin_var = MagicMock(spec=tk.StringVar)
        self._status_var = MagicMock(spec=tk.StringVar)
        self._run_btn = MagicMock(spec=ttk.Button)
        self._stop_btn = MagicMock(spec=ttk.Button)
        self._output = MagicMock(spec=tk.Text)
        self._path: Any = None
        self._after_calls: list[tuple[Any, ...]] = []

    def _selected_path(self) -> Path | None:
        return self._path

    def after(self, ms: int, *args: object) -> str:
        self._after_calls.append((ms, *args))
        return "after_id"


# ── _on_proc_done ────────────────────────────────────────────────────


class TestOnProcDone:
    def test_exit_code_zero(self) -> None:
        obj = StubExecution()
        obj._on_proc_done(0)
        obj._status_var.set.assert_called_once_with("✓ done")
        obj._run_btn.configure.assert_called_once_with(state=tk.NORMAL)
        obj._stop_btn.configure.assert_called_once_with(state=tk.DISABLED)
        assert any("exited with code 0" in str(c) for c in obj._after_calls)

    def test_exit_code_nonzero(self) -> None:
        obj = StubExecution()
        obj._on_proc_done(1)
        obj._status_var.set.assert_called_once_with("✗ exit 1")
        obj._run_btn.configure.assert_called_once_with(state=tk.NORMAL)
        obj._stop_btn.configure.assert_called_once_with(state=tk.DISABLED)
        assert any("exited with code 1" in str(c) for c in obj._after_calls)


# ── _stop ────────────────────────────────────────────────────────────


class TestStop:
    def test_proc_none(self) -> None:
        obj = StubExecution()
        obj._proc = None
        obj._stop()
        obj._status_var.set.assert_not_called()

    def test_proc_already_exited(self) -> None:
        obj = StubExecution()
        proc = MagicMock()
        proc.poll.return_value = 0
        obj._proc = proc
        obj._stop()
        proc.terminate.assert_not_called()
        obj._status_var.set.assert_not_called()


# ── _clear ───────────────────────────────────────────────────────────


class TestClear:
    def test_clears_output(self) -> None:
        obj = StubExecution()
        obj._clear()
        obj._output.configure.assert_any_call(state=tk.NORMAL)
        obj._output.delete.assert_called_once_with("1.0", tk.END)
        obj._output.configure.assert_any_call(state=tk.DISABLED)
        obj._status_var.set.assert_called_once_with("")


# ── _write_output ────────────────────────────────────────────────────


class TestWriteOutput:
    def test_write_output_with_tag(self) -> None:
        obj = StubExecution()
        obj._write_output("hello", "info")
        assert len(obj._after_calls) == 1

    def test_write_output_no_tag(self) -> None:
        obj = StubExecution()
        obj._write_output("hello")
        assert len(obj._after_calls) == 1


# ── _append_output ───────────────────────────────────────────────────


class TestAppendOutput:
    def test_append_with_tag(self) -> None:
        obj = StubExecution()
        obj._append_output("hello", "info")
        obj._output.configure.assert_any_call(state=tk.NORMAL)
        obj._output.insert.assert_called_once_with(tk.END, "hello", "info")
        obj._output.see.assert_called_once_with(tk.END)
        obj._output.configure.assert_any_call(state=tk.DISABLED)

    def test_append_without_tag(self) -> None:
        obj = StubExecution()
        obj._append_output("world", None)
        obj._output.insert.assert_called_once_with(tk.END, "world")
        obj._output.see.assert_called_once_with(tk.END)
