"""Echo Core message router — routes messages to Claude or commands."""

import json
import logging
import os
import signal
from datetime import datetime, timezone
from pathlib import Path
from typing import Callable

from src.config import Config
from src.fast_commands import dispatch as fast_dispatch
from src.claude_session import (
    send_message,
    clear_session,
    get_active_session,
    list_sessions,
    set_session_model,
    VALID_MODELS,
)

log = logging.getLogger(__name__)

APPROVED_TASKS_FILE = Path(__file__).parent.parent / "approved-tasks.json"

# Module-level config instance (lazy singleton)
_config: Config | None = None


def _get_config() -> Config:
    """Return the module-level config, creating it on first access."""
    global _config
    if _config is None:
        _config = Config()
    return _config


def route_message(
    channel_id: str,
    user_id: str,
    text: str,
    model: str | None = None,
    on_text: Callable[[str], None] | None = None,
) -> tuple[str, bool]:
    """Route an incoming message. Returns (response_text, is_command).

    If text starts with / it's a command (handled here for text-based commands).
    Otherwise it goes to Claude via send_message (auto start/resume).

    *on_text* — optional callback invoked with each intermediate text block
    from Claude, enabling real-time streaming to the adapter.
    """
    text = text.strip()

    # Ralph commands (!approve, !status, !stop, !propose)
    if text.lower().startswith("!approve ") or text.lower() == "!approve":
        return _ralph_approve(text), True

    if text.lower() == "!status" or text.lower().startswith("!status "):
        return _ralph_status(text), True

    if text.lower().startswith("!stop "):
        return _ralph_stop(text), True

    if text.lower().startswith("!propose "):
        return _ralph_propose(text), True

    # Text-based commands (not slash commands — these work in any adapter)
    if text.lower() == "/clear":
        default_model = _get_config().get("bot.default_model", "sonnet")
        cleared = clear_session(channel_id)
        if cleared:
            return f"Session cleared. Model reset to {default_model}.", True
        return "No active session.", True

    if text.lower() == "/status":
        return _status(channel_id), True

    if text.lower().startswith("/model"):
        return _model_command(channel_id, text), True

    if text.startswith("/"):
        parts = text[1:].split()
        cmd_name = parts[0].lower()
        cmd_args = parts[1:]
        result = fast_dispatch(cmd_name, cmd_args)
        if result is not None:
            return result, True
        return f"Unknown command: /{cmd_name}", True

    # Regular message → Claude
    if not model:
        # Check session model first, then channel default, then global default
        session = get_active_session(channel_id)
        if session and session.get("model"):
            model = session["model"]
        else:
            channel_cfg = _get_channel_config(channel_id)
            model = (channel_cfg or {}).get("default_model") or _get_config().get("bot.default_model", "sonnet")

    try:
        response = send_message(channel_id, text, model=model, on_text=on_text)
        return response, False
    except Exception as e:
        log.error("Claude error for channel %s: %s", channel_id, e)
        return f"Error: {e}", False


def _status(channel_id: str) -> str:
    """Build status message for a channel."""
    session = get_active_session(channel_id)
    if not session:
        return "No active session."

    model = session.get("model", "unknown")
    sid = session.get("session_id", "unknown")[:12]
    count = session.get("message_count", 0)

    return f"Model: {model} | Session: {sid}... | Messages: {count}"


def _model_command(channel_id: str, text: str) -> str:
    """Handle /model [choice] text command."""
    parts = text.strip().split()
    if len(parts) == 1:
        # /model — show current
        session = get_active_session(channel_id)
        if session:
            current = session.get("model", "unknown")
        else:
            channel_cfg = _get_channel_config(channel_id)
            current = (channel_cfg or {}).get("default_model") or _get_config().get("bot.default_model", "sonnet")
        available = ", ".join(sorted(VALID_MODELS))
        return f"Current model: {current}\nAvailable: {available}"

    choice = parts[1].lower()
    if choice not in VALID_MODELS:
        return f"Invalid model '{choice}'. Choose from: {', '.join(sorted(VALID_MODELS))}"

    session = get_active_session(channel_id)
    if session:
        set_session_model(channel_id, choice)
    else:
        # Pre-set for next message
        from src.claude_session import _load_sessions, _save_sessions
        from datetime import datetime, timezone
        sessions = _load_sessions()
        sessions[channel_id] = {
            "session_id": "",
            "model": choice,
            "created_at": datetime.now(timezone.utc).isoformat(),
            "last_message_at": datetime.now(timezone.utc).isoformat(),
            "message_count": 0,
        }
        _save_sessions(sessions)
    return f"Model changed to {choice}."


def _load_approved_tasks() -> dict:
    """Load approved-tasks.json, return empty structure if missing."""
    if APPROVED_TASKS_FILE.exists():
        return json.loads(APPROVED_TASKS_FILE.read_text())
    return {"projects": [], "last_updated": None}


def _save_approved_tasks(data: dict) -> None:
    data["last_updated"] = datetime.now(timezone.utc).isoformat()
    APPROVED_TASKS_FILE.write_text(json.dumps(data, indent=2, ensure_ascii=False))


def _ralph_approve(text: str) -> str:
    """!approve P1,P2 sau !approve roa2web — aprobă proiecte pentru night-execute."""
    parts = text.split(None, 1)
    if len(parts) < 2:
        data = _load_approved_tasks()
        pending = [p for p in data["projects"] if p.get("status") == "pending"]
        if not pending:
            return "Niciun proiect pending. Folosește !propose <nume> <descriere> pentru a adăuga."
        lines = [f"Proiecte pending (aprobă cu !approve <nume>):"]
        for p in pending:
            lines.append(f"  - {p['name']}: {p['description'][:60]}")
        return "\n".join(lines)

    names_raw = parts[1].strip()
    names = [n.strip() for n in names_raw.replace(",", " ").split() if n.strip()]

    data = _load_approved_tasks()
    approved = []
    not_found = []

    for name in names:
        found = False
        for p in data["projects"]:
            if p["name"].lower() == name.lower():
                p["status"] = "approved"
                p["approved_at"] = datetime.now(timezone.utc).isoformat()
                approved.append(p["name"])
                found = True
                break
        if not found:
            not_found.append(name)

    if not_found:
        return f"Nu am găsit proiectele: {', '.join(not_found)}. Verifică !status pentru lista completă."

    _save_approved_tasks(data)
    names_str = ", ".join(approved)
    return f"✅ Aprobat pentru tonight: {names_str}\nNight-execute rulează la 23:00 și va implementa stories autonom."


def _ralph_status(text: str) -> str:
    """!status sau !status <proiect> — status Ralph pentru proiecte."""
    parts = text.split(None, 1)
    filter_name = parts[1].strip().lower() if len(parts) > 1 else None

    data = _load_approved_tasks()
    projects = data.get("projects", [])

    if filter_name:
        projects = [p for p in projects if filter_name in p["name"].lower()]

    if not projects:
        return "Niciun proiect în approved-tasks.json. Adaugă cu !propose."

    lines = ["📊 Status proiecte Ralph:"]
    for p in projects:
        status = p.get("status", "unknown")
        name = p["name"]
        pid = p.get("pid")
        started = p.get("started_at", "")[:16] if p.get("started_at") else "-"

        # Verifică dacă procesul mai rulează
        if pid and status == "running":
            try:
                os.kill(pid, 0)
                running_indicator = f"🟢 PID {pid}"
            except (ProcessLookupError, PermissionError):
                running_indicator = "🔴 PID mort"
                p["status"] = "stopped"
                _save_approved_tasks(data)
        else:
            running_indicator = {"approved": "⏳ aștept 23:00", "pending": "📋 pending",
                                  "complete": "✅ complet", "failed": "❌ eșuat", "stopped": "⏹ oprit"}.get(status, status)

        # Stories complete din prd.json
        prd_path = Path(f"/home/moltbot/workspace/{name}/scripts/ralph/prd.json")
        stories_info = ""
        if prd_path.exists():
            try:
                prd = json.loads(prd_path.read_text())
                total = len(prd.get("userStories", []))
                done = sum(1 for s in prd.get("userStories", []) if s.get("passes"))
                stories_info = f" | Stories: {done}/{total}"
            except Exception:
                pass

        lines.append(f"  {name}: {running_indicator}{stories_info} | Start: {started}")

    return "\n".join(lines)


def _ralph_stop(text: str) -> str:
    """!stop <proiect> — oprește Ralph loop pentru un proiect."""
    parts = text.split(None, 1)
    if len(parts) < 2:
        return "Folosire: !stop <nume-proiect>"

    name = parts[1].strip()
    data = _load_approved_tasks()

    for p in data["projects"]:
        if p["name"].lower() == name.lower():
            pid = p.get("pid")
            if pid:
                try:
                    os.kill(pid, signal.SIGTERM)
                    p["status"] = "stopped"
                    p["stopped_at"] = datetime.now(timezone.utc).isoformat()
                    _save_approved_tasks(data)
                    return f"⏹ Ralph oprit pentru {name} (PID {pid} terminat)."
                except ProcessLookupError:
                    p["status"] = "stopped"
                    _save_approved_tasks(data)
                    return f"PID {pid} nu mai rula pentru {name}. Status actualizat."
                except PermissionError:
                    return f"❌ Nu am permisiune să opresc PID {pid}."
            else:
                return f"{name} nu are un PID activ (status: {p.get('status', 'unknown')})."

    return f"Proiect '{name}' nu găsit în approved-tasks.json."


def _ralph_propose(text: str) -> str:
    """!propose <nume> <descriere> — adaugă un proiect pentru aprobare."""
    parts = text.split(None, 2)
    if len(parts) < 3:
        return "Folosire: !propose <nume-proiect> <descriere scurtă>\nEx: !propose roa2web Homepage redesign cu hero section și animații"

    name = parts[1].strip()
    description = parts[2].strip()

    data = _load_approved_tasks()

    for p in data["projects"]:
        if p["name"].lower() == name.lower():
            return f"Proiectul '{name}' există deja cu status: {p.get('status', 'unknown')}."

    data["projects"].append({
        "name": name,
        "description": description,
        "status": "pending",
        "proposed_at": datetime.now(timezone.utc).isoformat(),
        "approved_at": None,
        "started_at": None,
        "pid": None
    })
    _save_approved_tasks(data)
    return f"📋 Adăugat: {name}\n{description}\n\nAprobă cu: !approve {name}"


def _get_channel_config(channel_id: str) -> dict | None:
    """Find channel config by ID."""
    channels = _get_config().get("channels", {})
    for alias, ch in channels.items():
        if ch.get("id") == channel_id:
            return ch
    return None
