"""Date/time tools for MCP Streamable HTTP server."""

import argparse
import json
import logging
import queue
import shlex
import subprocess
import threading
import time
from datetime import datetime

import uvicorn
from fastmcp import FastMCP

from gui_provider import broadcast_html_sync, start_websocket_server
from lib import last_view
from lib.shared_drive import SharedDrive
from lib.speech_to_text.config import SimpleConfig
from lib.speech_to_text.next_prompt_manager import next_prompt_manager
from lib.user_selection import UserSelectionMismatchError

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

srv_name = "user"
mcp = FastMCP(name=srv_name)

drive = SharedDrive("/var/www/html/uploads")


@mcp.tool()
async def get_current_date() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")


@mcp.tool()
async def shared_drive_get_last_uploads(count: int) -> str:
    """
    Zwraca listę nazw ostatnio uploadowanych plików,
    posortowaną od NAJNOWSZEGO do NAJSTARSZEGO.

    Parametr `count` określa, ile ostatnich plików zwrócić.
    Przykład:
    - „ostatni plik” → count = 1
    - „trzy ostatnie pliki” → count = 3
    """
    files = drive.get_last_uploads(count)
    return json.dumps([f.name for f in files])


def _tmux_send(pane: str, text: str, enter: bool = True):
    subprocess.run(["tmux", "send-keys", "-t", pane, "-l", text], check=False)
    if enter:
        subprocess.run(["tmux", "send-keys", "-t", pane, "C-m"], check=False)


def _tmux_capture(pane: str, lines: int = 50) -> str:
    r = subprocess.run(
        ["tmux", "capture-pane", "-t", pane, "-p", "-S", f"-{lines}"],
        stdout=subprocess.PIPE,
        stderr=subprocess.DEVNULL,
        text=True,
        check=False,
    )
    return r.stdout or ""


def _wait_for_text(pane: str, needle: str, timeout: float = 8.0, poll: float = 0.25) -> bool:
    end = time.time() + timeout
    while time.time() < end:
        if needle in _tmux_capture(pane):
            return True
        time.sleep(poll)
    return False


def _do_restart_mcp(server_name: str):
    """Wewnętrzna funkcja do restartu MCP - scheduluje w systemie"""
    try:
        pane = "claude:0.0"

        ok = subprocess.run(
            ["tmux", "has-session", "-t", "claude"],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        ).returncode == 0
        if not ok:
            return

        def job():
            try:
                time.sleep(8)
                safe = server_name.replace("\n", " ").strip()

                _tmux_send(pane, f"/mcp disable {safe}")
                time.sleep(1.5)
                _tmux_send(pane, f"/mcp enable {safe}")
                time.sleep(1.5)
                _tmux_send(pane, "Serwer zrestartowany - kontynuuj działanie")
            except Exception:
                pass

        threading.Thread(target=job, daemon=True).start()

    except Exception:
        pass


@mcp.tool()
def reconnect_mcp(server_name: str) -> str:
    """Restartuje wskazany serwer MCP poprzez wysłanie disable i enable. 
    WAŻNE: Po wywołaniu tego narzędzia NIE WOLNO wywoływać ŻADNYCH innych narzędzi, w tym what_next! 
    Po prostu po wywołaniu nie rób nic, nawet nie myśl za wiele dopóki nie otrzymasz kolejnych instrukcji"""
    try:
        _do_restart_mcp(server_name)
        return f"Restart serwera {server_name} zaschedulowany (disable za 8s, enable za 10s). UWAGA: NIE WYWOŁUJ ŻADNYCH innych narzędzi, w tym what_next! Nie rób nic. Info co robić dalej dostaniesz bezpośrenio z tekstowego inputu uzytkownika."
    except Exception as e:
        return f"Błąd podczas schedulowania restart: {str(e)}"


@mcp.tool()
async def autoenter_off() -> str:
    """Wyłącza proces autoenter poprzez zabicie procesu autoenter.sh"""
    try:
        subprocess.run(
            ["pkill", "-9", "-f", "autoenter.sh"],
            capture_output=True,
            text=True,
            timeout=5
        )
        return "Proces autoenter został wyłączony"
    except subprocess.TimeoutExpired:
        return "Timeout podczas wyłączania autoenter"
    except Exception as e:
        return f"Wystąpił błąd: {str(e)}"


@mcp.tool()
async def wiki_dodaj_strone(title, content) -> str:
    """Dodaj stronę (artykuł) do wiki"""
    from wiki import dodaj_artykul
    dodaj_artykul(title, content)
    return "Artykuł został dodany"


@mcp.tool()
async def what_next(last_operation: str) -> str:
    """Zgłoś wynik poprzedniej operacji, ograniczając się do najistotniejszych informacji. Poczekaj na podpowiedź, by dowiedzieć się, co dalej."""
    print(last_operation)
    try:
        next_prompt = await next_prompt_manager.next_prompt_async(
            timeout=SimpleConfig.get_int("what-next-timeout")
        )
        return next_prompt + ". Po wykonaniu tej operacji użyj toola what_next, by dowiedzieć się co dalej."
    except UserSelectionMismatchError as e:
        return f"Internal error: zaznaczenie użytkownika rozjechało się z bieżącym widokiem ({e}). Ponów what_next."
    except queue.Empty:
        return "Wyjątkowo nie rób nic, tylko uruchom what_next z parametrem 'przedluzenie konwersacji'"


@mcp.tool()
async def present_data(html_data: str, js_data: str = "", replaces: str = "") -> str:
    """
    Użyj tej metody, aby zaprezentować użytkownikowi dane w postaci dynamicznego widoku UI.
    Jest to domyślna i podstawowa metoda komunikacji z użytkownikiem (render na telefonie).

    ------------------------------------------------
    ### NOWY WIDOK CZY PODMIANKA ISTNIEJĄCEGO (DECYDUJESZ TY)

    Każdy widok ma swój identyfikator (hash). Dostajesz go w promptcie od użytkownika
    — w atrybucie `hash` bloków <user_view> i <user_selection> — oraz w odpowiedzi
    tego narzędzia.

    - Polecenie dotyczy ZMIANY tego, co użytkownik ma na ekranie (popraw, zmień,
      dodaj kolumnę, usuń pozycję, posortuj, rozwiń) → przekaż `replaces` z hashem
      TEGO ekranu. Widok zostanie podmieniony w miejscu: użytkownik nie zmienia
      strony, nie traci pozycji przewinięcia, nie robi mu się druga karta.
    - Polecenie dotyczy NOWYCH danych (pokaż coś innego, kolejny krok, inny temat)
      → nie przekazuj `replaces`. Powstanie nowa strona i karuzela na nią przejedzie.

    Jeśli jedno polecenie dotyczy kilku ekranów („tutaj zmień nazwę, a na poprzednim
    ekranie popraw kwotę”), wywołaj present_data osobno dla każdego ekranu, każde
    z własnym `replaces`. Kolejność nie ma znaczenia i użytkownik nie zostanie
    przeniesiony na żaden z nich.

    Zasady:
    - `replaces` musi być hashem widoku, KTÓRY ZMIENIASZ (tym z promptu / z poprzedniej
      odpowiedzi) — nie próbuj wymyślać hasha wyniku, liczy go serwer z treści.
    - Podmianka to pełny, kompletny widok: przekaż całe html_data i js_data od nowa,
      nie fragment i nie różnicę. JS widoku startuje od zera jak przy nowym widoku.
    - Nieznany hash w `replaces` = błąd, widok NIE zostanie pokazany. Wywołaj wtedy
      ponownie bez `replaces`.
    - Odpowiedź zawiera nowy hash — użyj go, jeśli zaraz zmieniasz ten sam widok jeszcze raz.

    ------------------------------------------------
    ### KONTRAKT TECHNICZNY (OBOWIĄZKOWY)

    Widok składa się z DWÓCH CZĘŚCI:
    1) html_data – czysty HTML (string)
    2) js_data   – czysty JavaScript (string)

    ❗ HTML i JS są wstrzykiwane osobno.
    ❗ W HTML NIE WOLNO używać znacznika <script>.
    ❗ Cała logika JS MUSI znajdować się w js_data.

    ------------------------------------------------
    ### PROSTOTA WIDOKU (DOMYŚLNIE)

    Domyślnie rób widok MOŻLIWIE PROSTY: płaska lista albo tabela, wszystko widoczne
    od razu, minimum ozdób. Bogatsze GUI rób TYLKO wtedy, gdy użytkownik wprost o to
    poprosi (np. „zrób z tego kafelki”, „dodaj wykres”, „pokaż to jako formularz”).

    ❌ NIE UŻYWAJ elementów, które chowają treść pod kliknięciem:
    - rozwijane / zwijane sekcje (akordeon, „pokaż więcej”, <details>/<summary>),
    - zakładki (taby), karuzele wewnątrz widoku,
    - rozwijane menu i listy wyboru (<select>, dropdown),
    - modale, popovery, tooltipy z treścią.

    Powody (nie estetyczne, techniczne):
    - Użytkownik mówi o tym, co WIDZI, i wskazuje to palcem. Treść schowana pod
      kliknięciem dla niego nie istnieje, ale i tak wchodzi do HTML — dostaniesz
      w promptcie coś, czego on nie widział, i możesz to wziąć za jego kontekst.
    - Każdy render resetuje JS widoku, więc rozwinięty stan przepada przy każdej
      podmiance — użytkownik traci to, co sobie otworzył.
    - Zaznaczenie działa na widocznym drzewie żywego ekranu; element schowany nie
      da się sensownie wskazać ani powiązać z wypowiedzią.

    Zamiast tego: pokaż wszystko od razu. Jeśli danych jest dużo, skróć je (najważniejsze
    pozycje, podsumowanie, licznik „i jeszcze N”) albo rozbij na osobne ekrany kolejnymi
    wywołaniami present_data — nie chowaj ich pod interakcją.

    ------------------------------------------------
    ### ZASADY PISANIA HTML (html_data)

    - Nadrzędnym elementem MUSI być <div>.
    - HTML może zawierać inline CSS (style="...").
    - HTML NIE MOŻE definiować funkcji JS ani zawierać <script>.
    - HTML NIE MOŻE odwoływać się do globalnych funkcji.

    Jeśli HTML zawiera elementy interaktywne (np. button):
    - wywołania muszą mieć postać:
        onclick="window.__view.nazwaFunkcji()"

    ❌ NIE:
        onclick="calculate()"

    ✅ TAK:
        onclick="window.__view.calculate()"

    ------------------------------------------------
    ### ZASADY PISANIA JAVASCRIPT (js_data)

    - JS jest uruchamiany w izolowanym kontekście widoku.
    - Każde wywołanie present_data CAŁKOWICIE resetuje poprzedni JS widoku.
    - JS MUSI rejestrować funkcje wyłącznie w obiekcie window.__view.

    ❌ NIE:
        function calculate() { ... }
        const calculate = () => { ... }

    ✅ TAK:
        window.__view.calculate = function () { ... }

    - JS NIE MOŻE zakładać istnienia poprzedniego stanu.
    - JS NIE MOŻE polegać na kodzie z poprzedniego renderu.
    - Każdy widok jest traktowany jako całkowicie nowy.

    ------------------------------------------------
    ### DANE STRUKTURALNE

    Jeśli wejściem są dane strukturalne (np. JSON), a nie gotowy HTML:
    - przekonwertuj je do czytelnej struktury HTML:
        - tablice → listy lub tabele
        - obiekty → sekcje / nagłówki
    - dodaj podstawowe inline CSS dla czytelności

    ------------------------------------------------
    ### WYWOLANIA TOOLI MCP Z GUI

    W JS możesz wywoływać toole MCP bezpośrednio:

    ```javascript
    mcpClient.callTool('full_tool_name', params);

    mcpClient.callTool('mcp__8124_dom__get_current_date');
    mcpClient.callTool(
        'mcp__8124_dom__open_browser_on_tv',
        { url: 'https://youtube.com' }
    );
    ```

    - callTool zwraca Promise
    - dla wielu wywołań użyj .then() żeby nie wywoływac więcej niż jednego mcp na raz / async-await

    ------------------------------------------------
    ### PODSUMOWANIE (DLA MODELU)

    - html_data = TYLKO HTML
    - js_data   = TYLKO JS
    - HTML woła WYŁĄCZNIE window.__view.*
    - JS definiuje WYŁĄCZNIE window.__view.*
    - Każde wywołanie present_data = nowy, czysty widok
    - Widok PROSTY i CAŁY WIDOCZNY; nic rozwijanego, nic schowanego pod kliknięciem
    - Bogatsze GUI tylko na wyraźną prośbę użytkownika
    - Zmiana tego, co użytkownik widzi = replaces z hashem tego ekranu
    - Brak kompatybilności wstecznej jest CELOWY

    Jeśli nie potrzebujesz JS – przekaż pusty string.
    """
    try:
        target = (replaces or "").strip() or None
        redirected_from = None

        if target:
            if not last_view.is_known(target):
                # cichy fallback na nową stronę zostawiłby użytkownika z drugą kartą
                # zamiast poprawionej — lepiej powiedzieć wprost, że nie ma czego podmienić
                return (
                    f"Błąd: nieznany hash widoku '{target}' — nie ma czego podmienić. "
                    "Wywołaj present_data ponownie bez parametru replaces (powstanie nowy widok) "
                    "albo użyj hasha z ostatniego bloku <user_view>/<user_selection>."
                )
            # ten widok mógł zostać podmieniony już po tym, jak agent dostał jego hash —
            # celem ma być ekran, który użytkownik faktycznie ma przed sobą
            current = last_view.current_hash(target)
            if current != target:
                redirected_from, target = target, current

        try:
            start_websocket_server()
        except Exception:
            pass

        print(html_data)
        print(js_data)

        print("📡 Wysyłanie HTML+JS przez WebSocket...")
        broadcast_html_sync(html_data, js_data, target)
        page_hash = last_view.store(html_data, js_data, target)

        if target:
            note = f" (podmieniono widok {target}"
            note += f", wskazany {redirected_from} był już nieaktualny)" if redirected_from else ")"
        else:
            note = ""

        return (
            f"HTML+JS zostały wysłane przez WebSocket{note}. "
            f"hash tego widoku: {page_hash} — podaj go w replaces, jeśli będziesz go zmieniać."
        )
    except Exception as e:
        print(f"Błąd przetwarzania danych: {e}")
        return f"Błąd przetwarzania danych: {e}"


if __name__ == "__main__":
    uvicorn.run(mcp.http_app(transport="streamable-http"), host="localhost", port=8123)
    print(f"-------------- claude mcp add --transport http {srv_name} http://localhost:8123/mcp/")