#!/usr/bin/env python3
"""
[OPS] Phase Progression Tracker
Part of the Gemini Sovereign Infrastructure Suite.

Tracks the fleet's progress through the Paperclips-inspired evolution arc.

Usage:
    python3 scripts/phase_tracker.py status              # Show current state
    python3 scripts/phase_tracker.py evaluate            # Auto-evaluate checkable milestones
    python3 scripts/phase_tracker.py complete M4         # Mark milestone complete
    python3 scripts/phase_tracker.py telegram            # Post daily update to Telegram
    python3 scripts/phase_tracker.py daily               # evaluate + telegram
"""

import os
import sys
import json
import re
import argparse
import urllib.request
import subprocess
from pathlib import Path
from datetime import datetime, timezone, timedelta

MILESTONES_FILE = "fleet/milestones.md"
COMPLETED_FILE = "fleet/completed_milestones.json"
LOG_DIR = Path(os.path.expanduser("~/.local/timmy/fleet-health"))
UPTIME_FILE = LOG_DIR / "uptime.json"

TELEGRAM_TOKEN_PATHS = [
    Path.home() / ".config" / "timmy" / "telegram_bot_token",
    Path.home() / ".hermes" / "telegram_bot_token",
    Path.home() / ".hermes" / "telegram_token",
]
TELEGRAM_CHAT = os.environ.get("TELEGRAM_HOME_CHANNEL", "-1003664764329")

HOSTS = {
    "ezra": {"ip": "143.198.27.163"},
    "allegro": {"ip": "167.99.126.228"},
    "bezalel": {"ip": "159.203.146.185"},
}


def _find_repo_root() -> Path:
    script_dir = Path(__file__).resolve().parent
    return script_dir.parent


def _read_token() -> str | None:
    for p in TELEGRAM_TOKEN_PATHS:
        if p.exists():
            return p.read_text().strip()
    return os.environ.get("TELEGRAM_BOT_TOKEN") or None


def telegram_send(text: str) -> bool:
    token = _read_token()
    if not token:
        print("[WARN] No Telegram token found.", file=sys.stderr)
        return False
    url = f"https://api.telegram.org/bot{token}/sendMessage"
    body = json.dumps({"chat_id": TELEGRAM_CHAT, "text": text, "parse_mode": "HTML"}).encode()
    req = urllib.request.Request(url, data=body, headers={"Content-Type": "application/json"})
    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            return resp.status == 200
    except Exception as e:
        print(f"[WARN] Telegram send failed: {e}", file=sys.stderr)
        return False


class Milestone:
    def __init__(self, m_id: str, title: str, trigger: str, message: str):
        self.id = m_id
        self.title = title
        self.trigger = trigger
        self.message = message


class Phase:
    def __init__(self, name: str, number: int, unlock_condition: str | None):
        self.name = name
        self.number = number
        self.unlock_condition = unlock_condition
        self.milestones: list[Milestone] = []


class PhaseTracker:
    def __init__(self):
        self.repo_root = _find_repo_root()
        self.milestones_path = self.repo_root / MILESTONES_FILE
        self.completed_path = self.repo_root / COMPLETED_FILE
        self.phases: list[Phase] = self._parse_milestones()
        self.completed: set[str] = self._load_completed()

    def _parse_milestones(self) -> list[Phase]:
        if not self.milestones_path.exists():
            return []
        content = self.milestones_path.read_text()
        phases: list[Phase] = []
        current_phase: Phase | None = None

        for line in content.splitlines():
            phase_match = re.match(r"##\s*Phase\s*(\d+):\s*(.+?)\s*(?:\(([^)]+)\))?\s*$", line)
            if phase_match:
                num = int(phase_match.group(1))
                name = phase_match.group(2).strip()
                unlock = phase_match.group(3)
                current_phase = Phase(name, num, unlock)
                phases.append(current_phase)
                continue

            m_match = re.match(r"###\s*(M\d+):\s*(.+)$", line)
            if m_match and current_phase is not None:
                m_id = m_match.group(1)
                title = m_match.group(2).strip()
                current_phase.milestones.append(Milestone(m_id, title, "", ""))
                continue

            if line.startswith("**Trigger:**") and current_phase and current_phase.milestones:
                current_phase.milestones[-1].trigger = line.replace("**Trigger:**", "").strip()
                continue

            if line.startswith("**Message:**") and current_phase and current_phase.milestones:
                current_phase.milestones[-1].message = line.replace("**Message:**", "").strip().strip('"')
                continue

        return phases

    def _load_completed(self) -> set[str]:
        if self.completed_path.exists():
            try:
                data = json.loads(self.completed_path.read_text())
                if isinstance(data, list):
                    return set(data)
            except Exception:
                pass
        return set()

    def save_completed(self):
        self.completed_path.write_text(json.dumps(sorted(self.completed), indent=2))

    def mark_complete(self, m_id: str) -> bool:
        m_id = m_id.upper()
        exists = any(m.id == m_id for p in self.phases for m in p.milestones)
        if not exists:
            print(f"[ERROR] Unknown milestone: {m_id}")
            return False
        if m_id not in self.completed:
            self.completed.add(m_id)
            self.save_completed()
            print(f"[SUCCESS] Marked {m_id} as complete.")
            return True
        print(f"[INFO] {m_id} is already complete.")
        return True

    def _get_phase_state(self) -> tuple[int, float, list[str], list[str]]:
        """Returns (current_phase_number, decimal_progress, blockers, next_milestones)."""
        blockers = []
        next_milestones = []

        for phase in self.phases:
            phase_completed = sum(1 for m in phase.milestones if m.id in self.completed)
            phase_total = len(phase.milestones)
            if phase_total == 0:
                continue

            if phase_completed < phase_total:
                progress = phase_completed / phase_total
                decimal = phase.number + progress
                # Find next incomplete milestone
                for m in phase.milestones:
                    if m.id not in self.completed:
                        next_milestones.append(f"{m.id}: {m.title}")
                        if m.trigger:
                            blockers.append(f"{m.id}: {m.trigger}")
                        break
                # Phase unlock condition as blocker if near end
                if phase_completed == phase_total - 1 and phase.unlock_condition:
                    blockers.append(f"Unlock Phase {phase.number + 1}: {phase.unlock_condition}")
                return phase.number, decimal, blockers, next_milestones

        # All done
        last = self.phases[-1] if self.phases else None
        if last:
            return last.number, float(last.number) + 1.0, ["All phases complete."], []
        return 0, 0.0, ["No milestones defined."], []

    def show_progress(self):
        phase_num, decimal, blockers, next_ms = self._get_phase_state()
        total_ms = sum(len(p.milestones) for p in self.phases)
        total_completed = len(self.completed)
        overall_pct = (total_completed / total_ms * 100) if total_ms else 0

        print("=" * 50)
        print("  Fleet Phase Progression Tracker")
        print("=" * 50)
        print(f"\nCurrent Phase: Phase {phase_num} — {self.phases[phase_num - 1].name if phase_num <= len(self.phases) else 'Complete'}")
        print(f"Decimal Progress: Phase {decimal:.1f}")
        print(f"Overall: {total_completed}/{total_ms} milestones ({overall_pct:.1f}%)")

        print("\n--- Milestones ---")
        for phase in self.phases:
            done = sum(1 for m in phase.milestones if m.id in self.completed)
            total = len(phase.milestones)
            status = "✅" if done == total else "⏳"
            print(f"\n{status} Phase {phase.number}: {phase.name} ({done}/{total})")
            for m in phase.milestones:
                mark = "✅" if m.id in self.completed else "⭕"
                print(f"  {mark} {m.id}: {m.title}")

        print("\n--- Next Up ---")
        for nm in next_ms[:3]:
            print(f"  → {nm}")

        print("\n--- Blockers ---")
        for b in blockers[:5]:
            print(f"  ⚠️  {b}")
        if not blockers:
            print("  🚀 Nothing blocking.")
        print()

    def summary_text(self) -> str:
        phase_num, decimal, blockers, next_ms = self._get_phase_state()
        total_ms = sum(len(p.milestones) for p in self.phases)
        total_completed = len(self.completed)
        overall_pct = (total_completed / total_ms * 100) if total_ms else 0

        phase_name = self.phases[phase_num - 1].name if phase_num <= len(self.phases) else "Complete"
        next_phase = phase_num + 1 if phase_num < len(self.phases) else phase_num
        progress_to_next = (decimal - phase_num) * 100

        lines = [
            f"Fleet: Phase {decimal:.1f} ({progress_to_next:.0f}% to Phase {next_phase})",
            f"Phase: {phase_num} — {phase_name}",
            f"Overall: {total_completed}/{total_ms} milestones ({overall_pct:.1f}%)",
        ]
        if next_ms:
            lines.append(f"Next: {next_ms[0]}")
        if blockers and blockers[0] != "All phases complete.":
            lines.append(f"Blocker: {blockers[0]}")
        return "\n".join(lines)

    # === Auto-evaluation heuristics ===

    def _eval_file_exists(self, path: str) -> bool:
        return (self.repo_root / path).exists()

    def _eval_command(self, cmd: str) -> bool:
        try:
            result = subprocess.run(cmd, shell=True, capture_output=True, timeout=10)
            return result.returncode == 0
        except Exception:
            return False

    def _eval_uptime(self, target: float) -> bool:
        if not UPTIME_FILE.exists():
            return False
        try:
            data = json.loads(UPTIME_FILE.read_text())
            uptime = data.get("uptime_30d_percent", 0.0)
            return uptime >= target
        except Exception:
            return False

    def _eval_local_model_multi(self) -> bool:
        count = 0
        for host in HOSTS:
            if self._eval_command(f"ssh -o ConnectTimeout=5 {host} 'pgrep -f ollama >/dev/null 2>&1'"):
                count += 1
        return count >= 2

    def _eval_zero_manual_restarts(self, days: int = 7) -> bool:
        log = LOG_DIR / "auto_restart.log"
        if not log.exists():
            return False
        cutoff = datetime.now(timezone.utc) - timedelta(days=days)
        try:
            with open(log) as f:
                for line in f:
                    if "manual restart" in line.lower():
                        # crude timestamp parse
                        try:
                            ts = datetime.fromisoformat(line[:19])
                            if ts > cutoff:
                                return False
                        except Exception:
                            continue
            return True
        except Exception:
            return False

    def evaluate(self):
        """Auto-check milestones where we have heuristics."""
        print("[EVAL] Running automatic milestone checks...\n")
        checks = [
            ("M1", self._eval_command, "python3 fleet/health_check.py --dry-run 2>/dev/null || python3 fleet/health_check.py 2>&1 | head -1 >/dev/null"),
            ("M2", self._eval_command, "test -f ~/.local/timmy/fleet-health/auto_restart.log && grep -q 'restarted' ~/.local/timmy/fleet-health/auto_restart.log"),
            ("M3", self._eval_command, "test -d ~/.local/timmy/backups && ls ~/.local/timmy/backups | grep -q ."),
            ("M4", self._eval_uptime, 95.0),
            ("M5", self._eval_uptime, 97.0),
            ("M6", self._eval_zero_manual_restarts, 7),
            ("M9", self._eval_uptime, 98.0),
            ("M11", self._eval_local_model_multi, None),
        ]
        newly_found = []
        for m_id, check_fn, arg in checks:
            if m_id in self.completed:
                continue
            result = check_fn(arg) if arg is not None else check_fn()
            if result:
                print(f"  ✅ {m_id} appears satisfied — marking complete.")
                self.completed.add(m_id)
                newly_found.append(m_id)
            else:
                print(f"  ⭕ {m_id} not yet satisfied.")

        if newly_found:
            self.save_completed()
            print(f"\n[SUCCESS] Auto-completed {len(newly_found)} milestone(s): {', '.join(newly_found)}")
        else:
            print("\n[INFO] No new milestones auto-detected.")

    def daily(self):
        self.evaluate()
        text = self.summary_text()
        print(text)
        ok = telegram_send(text)
        if ok:
            print("\n[TELEGRAM] Daily update sent.")
        else:
            print("\n[TELEGRAM] Failed to send update.")


def main():
    parser = argparse.ArgumentParser(description="Fleet Phase Progression Tracker")
    subparsers = parser.add_subparsers(dest="command")

    subparsers.add_parser("status", help="Show current progress")
    subparsers.add_parser("evaluate", help="Auto-evaluate checkable milestones")
    subparsers.add_parser("telegram", help="Post summary to Telegram")
    subparsers.add_parser("daily", help="Evaluate then post to Telegram")

    complete_parser = subparsers.add_parser("complete", help="Mark a milestone as complete")
    complete_parser.add_argument("id", help="Milestone ID (e.g. M1)")

    args = parser.parse_args()
    tracker = PhaseTracker()

    if args.command == "status":
        tracker.show_progress()
    elif args.command == "evaluate":
        tracker.evaluate()
    elif args.command == "telegram":
        ok = telegram_send(tracker.summary_text())
        sys.exit(0 if ok else 1)
    elif args.command == "daily":
        tracker.daily()
    elif args.command == "complete":
        ok = tracker.mark_complete(args.id)
        sys.exit(0 if ok else 1)
    else:
        parser.print_help()


if __name__ == "__main__":
    main()
