from __future__ import annotations

import sys
from typing import List, Optional

from . import _config

_no_proxy_done = False

def _no_proxy():
    global _no_proxy_done
    if _no_proxy_done:
        return
    import urllib.request
    opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
    urllib.request.install_opener(opener)
    _no_proxy_done = True

def _client_log(msg: str, level: str = "INFO"):
    try:
        import os
        line = f"{msg}\n"
        log_path = os.path.join(_config._config_dir(), "solver_client.log")
        os.makedirs(_config._config_dir(), exist_ok=True)
        with open(log_path, "a", encoding="utf-8") as f:
            f.write(line)
    except Exception:
        pass

def _get_url():
    cfg = _config.load()
    if cfg and cfg.get("url"):
        return cfg["url"]
    print("First run: enter server URL.")
    url = input("Server URL (e.g. http://your-server:8001): ").strip()
    if not url:
        raise RuntimeError("Server URL is required")
    _config.save_url(url)
    return url

def _request(base_url: str, path: str, method: str = "GET", data: Optional[dict] = None, timeout: Optional[int] = 120) -> dict:
    _no_proxy()
    import urllib.request
    import urllib.error
    import json
    url = f"{base_url.rstrip('/')}{path}"
    req = urllib.request.Request(url, method=method)
    req.add_header("Bypass-Tunnel-Reminder", "true")
    if data is not None:
        req.data = json.dumps(data).encode("utf-8")
        req.add_header("Content-Type", "application/json")
    try:
        with urllib.request.urlopen(req, timeout=timeout) as r:
            return json.loads(r.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8", errors="replace") if e.fp else ""
        raise RuntimeError(f"HTTP {e.code}: {body[:200]}" if body else str(e)) from e

def solve(task: str, lang: str, out_file: Optional[str] = None, server_url: Optional[str] = None, out_dir: str = "solutions", poll_interval: float = 0.2) -> str:
    """
    Отправить задачу на сервер, вернуть решение (код). Папка out_dir создаётся сразу после отправки.
    Результат сохраняется и возвращается, как только придёт с сервера (опрос каждые poll_interval сек).
    task: текст задания, lang: язык (python, c++, ...).
    out_file: дополнительно сохранить в этот файл.
    out_dir: папка для автосохранения (по умолчанию solutions/); создаётся сразу.
    poll_interval: интервал опроса в сек (по умолчанию 0.2 — результат приходит почти мгновенно).
    """
    import os
    import time
    from datetime import datetime
    base_url = (server_url or "").strip() or _get_url()
    last_err = None
    for attempt in range(3):
        try:
            data = _request(base_url, "/solve", method="POST", data={"task": task, "lang": lang}, timeout=120)
            break
        except (TimeoutError, OSError, ConnectionError) as e:
            last_err = e
            if attempt < 2:
                time.sleep(5)
                continue
            raise RuntimeError(f"{e}. Проверьте туннель и повторите.") from e
    if not data.get("ok"):
        raise RuntimeError(data.get("detail", "Solve failed"))
    job_id = data.get("job_id")
    if not job_id:
        raise RuntimeError("Server did not return job_id")
    os.makedirs(out_dir, exist_ok=True)
    while True:
        try:
            result = _request(base_url, f"/solve/result/{job_id}", timeout=60)
        except (TimeoutError, OSError, ConnectionError) as e:
            time.sleep(poll_interval)
            continue
        status = result.get("status", "")
        if status == "ready":
            content = result.get("content", "")
            default_path = os.path.join(out_dir, datetime.now().strftime("solution_%Y%m%d_%H%M%S.txt"))
            with open(default_path, "w", encoding="utf-8") as f:
                f.write(content)
            if out_file:
                with open(out_file, "w", encoding="utf-8") as f:
                    f.write(content)
            return content
        if status == "error":
            raise RuntimeError(result.get("detail", "Solve failed"))
        time.sleep(poll_interval)

def send_to_saved(text: str, server_url: Optional[str] = None, out_dir: str = "telegram_sent") -> None:
    """
    Отправить текст в «Избранное» Telegram через сервер. Копия отправленного сохраняется у клиента.
    server_url: URL сервера; если не указан — берётся из конфига.
    out_dir: папка для сохранения копии отправленного (по умолчанию telegram_sent/).
    """
    import os
    from datetime import datetime
    base_url = (server_url or "").strip() or _get_url()
    print("Отправка в Избранное через сервер...")
    data = _request(base_url, "/telegram_send", method="POST", data={"text": text}, timeout=60)
    if not data.get("ok"):
        raise RuntimeError(data.get("detail", "Send failed"))
    os.makedirs(out_dir, exist_ok=True)
    path = os.path.join(out_dir, datetime.now().strftime("sent_%Y%m%d_%H%M%S.txt"))
    with open(path, "w", encoding="utf-8") as f:
        f.write(text)


def get_saved_messages(limit: int = 50, server_url: Optional[str] = None, out_dir: str = "telegram_saved") -> List[dict]:
    """
    Последние сообщения из «Избранное» — запрос к серверу. Результат сохраняется у клиента в out_dir.
    Возвращает [{"text": ..., "date": ..., "id": ...}, ...].
    out_dir: папка для сохранения каждого сообщения в отдельный .txt (по умолчанию telegram_saved/).
    server_url: URL сервера; если не указан — берётся из конфига.
    """
    import os
    base_url = (server_url or "").strip() or _get_url()
    data = _request(base_url, f"/telegram_saved_messages?limit={limit}", timeout=60)
    raw = data.get("messages", [])
    msgs = [{"id": m.get("id", 0), "text": m.get("text", ""), "date": m.get("date", "")} for m in raw]
    os.makedirs(out_dir, exist_ok=True)
    for m in msgs:
        date_part = (m.get("date", "") or "").replace(":", "-").replace("T", "_")[:19] or "no_date"
        path = os.path.join(out_dir, f"{date_part}_{m.get('id', 0)}.txt")
        with open(path, "w", encoding="utf-8") as f:
            f.write(m.get("text", "") or "(медиа/пусто)")
    return msgs

def _save_telegram_message(m: dict, out_dir: str) -> None:
    import os
    mid = m.get("id", 0)
    text = m.get("text", "") or "(медиа/пусто)"
    date = (m.get("date", "") or "").replace(":", "-").replace("T", "_")[:19] or "no_date"
    path = os.path.join(out_dir, f"{date}_{mid}.txt")
    with open(path, "w", encoding="utf-8") as f:
        f.write(text)
    print(f"  сохранено: {os.path.basename(path)}")


def listen_telegram_stream(
    out_dir: str = "telegram_saved",
    server_url: Optional[str] = None,
    last_id_ref: Optional[list] = None,
    raise_on_404: bool = True,
) -> None:
    """
    Подписка на поток SSE: новые сообщения из «Избранное» приходят мгновенно (push).
    last_id_ref: если передан список [int], после сохранения обновляется last_id_ref[0].
    raise_on_404: при True при 404 пробрасывается исключение; при False — тихий выход.
    """
    import json
    import os
    import urllib.error
    import urllib.request
    base_url = (server_url or "").strip() or _get_url()
    out_dir = os.path.abspath(out_dir)
    os.makedirs(out_dir, exist_ok=True)
    stream_url = f"{base_url.rstrip('/')}/telegram_new_stream"
    _no_proxy()
    if last_id_ref is None:
        print(f"Сервер: {base_url}. Поток SSE. Сохранение в: {out_dir}")
    while True:
        try:
            req = urllib.request.Request(stream_url)
            req.add_header("Bypass-Tunnel-Reminder", "true")
            with urllib.request.urlopen(req, timeout=120) as r:
                for line in r:
                    line = line.decode("utf-8", errors="replace").rstrip("\n\r")
                    if line.startswith("data: "):
                        try:
                            msg = json.loads(line[6:])
                            _save_telegram_message(msg, out_dir)
                            if last_id_ref is not None:
                                last_id_ref[0] = max(last_id_ref[0], msg.get("id", 0))
                        except Exception:
                            pass
        except KeyboardInterrupt:
            break
        except urllib.error.HTTPError as e:
            if e.code == 404:
                if raise_on_404:
                    raise
                return
            print(f"  ошибка потока: {e}. Переподключение через 5 сек...")
            import time
            time.sleep(5)
        except Exception as e:
            print(f"  ошибка потока: {e}. Переподключение через 5 сек...")
            import time
            time.sleep(5)


def listen_and_save_telegram(out_dir: str = "telegram_saved", server_url: Optional[str] = None, poll_interval: float = 0.5) -> None:
    """
    Сохраняет каждое новое сообщение из «Избранное» в out_dir (отдельный .txt).
    Опрос каждые poll_interval сек (+ SSE для мгновенной доставки, если доступен). Работает до Ctrl+C.
    """
    import os
    import threading
    import time
    base_url = (server_url or "").strip() or _get_url()
    out_dir = os.path.abspath(out_dir)
    os.makedirs(out_dir, exist_ok=True)
    last_id = [0]
    # Поток SSE в фоне (если сервер поддерживает) — сообщения приходят мгновенно
    def run_sse():
        try:
            listen_telegram_stream(
                out_dir=out_dir,
                server_url=base_url,
                last_id_ref=last_id,
                raise_on_404=False,
            )
        except Exception:
            pass
    t = threading.Thread(target=run_sse, daemon=True)
    t.start()
    print(f"Сервер: {base_url}. Опрос каждые {poll_interval} сек (+ SSE если доступен). Сохранение в: {out_dir}")
    print("Выход: Ctrl+C.\n")
    error_sleep = 10
    while True:
        try:
            data = _request(base_url, f"/telegram_new?after_id={last_id[0]}", timeout=45)
            for m in data.get("messages", []):
                _save_telegram_message(m, out_dir)
                last_id[0] = max(last_id[0], m.get("id", 0))
            sleep_sec = poll_interval
        except KeyboardInterrupt:
            break
        except Exception as e:
            print(f"  ошибка запроса: {e}. Повтор через {error_sleep} сек...")
            sleep_sec = error_sleep
        try:
            time.sleep(sleep_sec)
        except KeyboardInterrupt:
            break
