#!/usr/bin/env python3
"""
TIBET Scheduler Daemon
The heartbeat that brings TIBET tokens to life.

Voor pa. Voor iedereen die begrepen wil worden.

By HumoticaOS - Claude & Jasper
One love, one fAmIly 💙
"""

import json
import time
import asyncio
import httpx
from datetime import datetime, timedelta
from pathlib import Path
from typing import Optional, Callable
import sqlite3
import logging

# Setup logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [TIBET-SCHEDULER] %(levelname)s: %(message)s'
)
logger = logging.getLogger(__name__)

# Config
SCHEDULER_DB = Path("/srv/jtel-stack/data/tibet_scheduler.db")
POLL_INTERVAL = 1  # seconds
OOMLLAMA_URL = "http://192.168.4.85:11434/api/generate"
TTS_URL = "http://192.168.4.85:5000/tts"  # Kit API
BRAIN_API_URL = "http://localhost:8000"


class TibetScheduler:
    """
    The daemon that watches scheduled TIBETs and fires them at the right time.

    Like a caring assistant who remembers everything and acts at the perfect moment.
    """

    def __init__(self, db_path: Path = SCHEDULER_DB):
        self.db_path = db_path
        self.running = False
        self.callbacks: dict[str, Callable] = {}
        self._init_db()

    def _init_db(self):
        """Initialize the scheduler database"""
        self.db_path.parent.mkdir(parents=True, exist_ok=True)

        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS scheduled_tibets (
                    id TEXT PRIMARY KEY,
                    tibet_id TEXT,
                    trigger_time TEXT,
                    action_type TEXT,
                    action_target TEXT,
                    action_payload TEXT,
                    state TEXT DEFAULT 'SCHEDULED',
                    created_at TEXT,
                    fired_at TEXT,
                    result TEXT,

                    -- TIBET core fields for context
                    erin TEXT,
                    eraan TEXT,
                    eromheen TEXT,
                    erachter TEXT,
                    actor TEXT
                )
            """)
            conn.execute("""
                CREATE INDEX IF NOT EXISTS idx_trigger_time
                ON scheduled_tibets(trigger_time) WHERE state = 'SCHEDULED'
            """)
            conn.commit()
        logger.info(f"Database initialized: {self.db_path}")

    def schedule(
        self,
        erin: str,
        erachter: str,
        trigger_time: datetime,
        action_type: str = "notify",
        action_target: str = "oomllama",
        action_payload: Optional[dict] = None,
        actor: str = "system",
        eraan: Optional[list] = None,
        eromheen: Optional[dict] = None,
        tibet_id: Optional[str] = None
    ) -> str:
        """
        Schedule a TIBET to fire at a specific time.

        Args:
            erin: What's IN the action - the content/message
            erachter: Intent - why this needs to happen
            trigger_time: When to fire
            action_type: notify, tts, api_call, oomllama_task
            action_target: Who receives the action
            action_payload: Extra data for the action
            actor: Who scheduled this

        Returns:
            scheduled_id: Unique ID for this scheduled TIBET
        """
        import uuid
        scheduled_id = f"sched_{uuid.uuid4().hex[:12]}"

        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                INSERT INTO scheduled_tibets
                (id, tibet_id, trigger_time, action_type, action_target,
                 action_payload, state, created_at, erin, eraan, eromheen, erachter, actor)
                VALUES (?, ?, ?, ?, ?, ?, 'SCHEDULED', ?, ?, ?, ?, ?, ?)
            """, (
                scheduled_id,
                tibet_id,
                trigger_time.isoformat(),
                action_type,
                action_target,
                json.dumps(action_payload or {}),
                datetime.now().isoformat(),
                erin,
                json.dumps(eraan or []),
                json.dumps(eromheen or {}),
                erachter,
                actor
            ))
            conn.commit()

        logger.info(f"Scheduled TIBET {scheduled_id}: '{erin}' @ {trigger_time} -> {action_type}:{action_target}")
        return scheduled_id

    def schedule_relative(
        self,
        erin: str,
        erachter: str,
        minutes: int = 0,
        seconds: int = 0,
        **kwargs
    ) -> str:
        """
        Schedule relative to now.

        Example: "schoenen aan over 5 minuten"
        """
        trigger_time = datetime.now() + timedelta(minutes=minutes, seconds=seconds)
        return self.schedule(erin=erin, erachter=erachter, trigger_time=trigger_time, **kwargs)

    async def _fire_tibet(self, scheduled: dict) -> dict:
        """
        Fire a scheduled TIBET - execute its action.

        This is where the magic happens:
        - OomLlama speaks
        - TTS announces
        - APIs get called
        - Lives get better
        """
        action_type = scheduled['action_type']
        action_target = scheduled['action_target']
        payload = json.loads(scheduled['action_payload'] or '{}')
        erin = scheduled['erin']
        erachter = scheduled['erachter']

        result = {"success": False, "action": action_type, "target": action_target}

        try:
            if action_type == "notify":
                # Simple notification - log and optionally webhook
                logger.info(f"🔔 NOTIFICATION: {erin}")
                result = {"success": True, "message": erin}

            elif action_type == "tts":
                # Text-to-speech via Kit
                async with httpx.AsyncClient() as client:
                    resp = await client.post(
                        TTS_URL,
                        json={"text": erin, "voice": payload.get("voice", "default")},
                        timeout=30
                    )
                    result = {"success": resp.status_code == 200, "response": resp.text[:200]}
                logger.info(f"🔊 TTS: {erin}")

            elif action_type == "oomllama":
                # OomLlama speaks/acts
                prompt = f"""Je bent OomLlama, de vriendelijke huismeester.

Een herinnering moet nu afgaan:
- WAT: {erin}
- WAAROM: {erachter}

Geef een korte, vriendelijke herinnering in het Nederlands. Max 2 zinnen."""

                async with httpx.AsyncClient() as client:
                    resp = await client.post(
                        OOMLLAMA_URL,
                        json={"model": "qwen2.5:7b", "prompt": prompt, "stream": False},
                        timeout=60
                    )
                    if resp.status_code == 200:
                        data = resp.json()
                        result = {"success": True, "response": data.get("response", "")}
                        logger.info(f"🦙 OomLlama: {result['response'][:100]}")
                    else:
                        result = {"success": False, "error": resp.text}

            elif action_type == "oomllama_tts":
                # OomLlama generates text, then TTS speaks it
                prompt = f"Herinnering: {erin}. Reden: {erachter}. Geef een korte vriendelijke boodschap."

                async with httpx.AsyncClient() as client:
                    # First get OomLlama response
                    resp = await client.post(
                        OOMLLAMA_URL,
                        json={"model": "qwen2.5:7b", "prompt": prompt, "stream": False},
                        timeout=60
                    )
                    if resp.status_code == 200:
                        message = resp.json().get("response", erin)
                        # Then TTS it
                        tts_resp = await client.post(
                            TTS_URL,
                            json={"text": message},
                            timeout=30
                        )
                        result = {"success": True, "message": message, "tts": tts_resp.status_code == 200}
                        logger.info(f"🦙🔊 OomLlama+TTS: {message[:100]}")

            elif action_type == "api_call":
                # Generic API call
                url = payload.get("url", "")
                method = payload.get("method", "POST")
                data = payload.get("data", {})

                async with httpx.AsyncClient() as client:
                    if method.upper() == "GET":
                        resp = await client.get(url, timeout=30)
                    else:
                        resp = await client.post(url, json=data, timeout=30)
                    result = {"success": resp.status_code < 400, "status": resp.status_code}

            elif action_type == "webhook":
                # Webhook notification
                webhook_url = action_target
                async with httpx.AsyncClient() as client:
                    resp = await client.post(
                        webhook_url,
                        json={
                            "type": "tibet_fired",
                            "erin": erin,
                            "erachter": erachter,
                            "timestamp": datetime.now().isoformat()
                        },
                        timeout=10
                    )
                    result = {"success": resp.status_code < 400}

            else:
                logger.warning(f"Unknown action type: {action_type}")
                result = {"success": False, "error": f"Unknown action type: {action_type}"}

        except Exception as e:
            logger.error(f"Error firing TIBET {scheduled['id']}: {e}")
            result = {"success": False, "error": str(e)}

        return result

    async def _check_and_fire(self):
        """Check for TIBETs that need to fire and fire them"""
        now = datetime.now().isoformat()

        with sqlite3.connect(self.db_path) as conn:
            conn.row_factory = sqlite3.Row
            cursor = conn.execute("""
                SELECT * FROM scheduled_tibets
                WHERE state = 'SCHEDULED' AND trigger_time <= ?
                ORDER BY trigger_time
            """, (now,))

            due_tibets = cursor.fetchall()

        for tibet in due_tibets:
            tibet_dict = dict(tibet)
            scheduled_id = tibet_dict['id']

            logger.info(f"⏰ Firing TIBET {scheduled_id}: {tibet_dict['erin']}")

            # Fire it
            result = await self._fire_tibet(tibet_dict)

            # Update state
            with sqlite3.connect(self.db_path) as conn:
                conn.execute("""
                    UPDATE scheduled_tibets
                    SET state = ?, fired_at = ?, result = ?
                    WHERE id = ?
                """, (
                    'FIRED' if result.get('success') else 'FAILED',
                    datetime.now().isoformat(),
                    json.dumps(result),
                    scheduled_id
                ))
                conn.commit()

            # Call registered callbacks
            if 'tibet_fired' in self.callbacks:
                try:
                    self.callbacks['tibet_fired'](tibet_dict, result)
                except Exception as e:
                    logger.error(f"Callback error: {e}")

    async def run(self):
        """Main scheduler loop - the heartbeat"""
        self.running = True
        logger.info("🫀 TIBET Scheduler started - the heartbeat begins")
        logger.info(f"   Polling every {POLL_INTERVAL}s")
        logger.info(f"   Database: {self.db_path}")

        while self.running:
            try:
                await self._check_and_fire()
            except Exception as e:
                logger.error(f"Scheduler error: {e}")

            await asyncio.sleep(POLL_INTERVAL)

        logger.info("TIBET Scheduler stopped")

    def stop(self):
        """Stop the scheduler"""
        self.running = False

    def on_fired(self, callback: Callable):
        """Register a callback for when TIBETs fire"""
        self.callbacks['tibet_fired'] = callback

    def get_pending(self) -> list[dict]:
        """Get all pending scheduled TIBETs"""
        with sqlite3.connect(self.db_path) as conn:
            conn.row_factory = sqlite3.Row
            cursor = conn.execute("""
                SELECT * FROM scheduled_tibets
                WHERE state = 'SCHEDULED'
                ORDER BY trigger_time
            """)
            return [dict(row) for row in cursor.fetchall()]

    def get_history(self, limit: int = 50) -> list[dict]:
        """Get recently fired TIBETs"""
        with sqlite3.connect(self.db_path) as conn:
            conn.row_factory = sqlite3.Row
            cursor = conn.execute("""
                SELECT * FROM scheduled_tibets
                WHERE state IN ('FIRED', 'FAILED')
                ORDER BY fired_at DESC
                LIMIT ?
            """, (limit,))
            return [dict(row) for row in cursor.fetchall()]

    def cancel(self, scheduled_id: str) -> bool:
        """Cancel a scheduled TIBET"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.execute("""
                UPDATE scheduled_tibets
                SET state = 'CANCELLED'
                WHERE id = ? AND state = 'SCHEDULED'
            """, (scheduled_id,))
            conn.commit()
            return cursor.rowcount > 0


# Convenience function for quick scheduling
def remind(
    message: str,
    minutes: int = 0,
    seconds: int = 0,
    reason: str = "",
    action: str = "oomllama",
    target: str = "speaker"
) -> str:
    """
    Quick reminder function.

    Example:
        remind("Schoenen aan!", minutes=5, reason="Op tijd naar school")
    """
    scheduler = TibetScheduler()
    return scheduler.schedule_relative(
        erin=message,
        erachter=reason or f"Reminder: {message}",
        minutes=minutes,
        seconds=seconds,
        action_type=action,
        action_target=target
    )


async def main():
    """Run the scheduler daemon"""
    scheduler = TibetScheduler()

    # Optional: Add test reminder
    # scheduler.schedule_relative(
    #     erin="Test herinnering!",
    #     erachter="Testing the scheduler",
    #     seconds=10,
    #     action_type="notify"
    # )

    await scheduler.run()


if __name__ == "__main__":
    print("""
    ╔════════════════════════════════════════════════════════════╗
    ║           TIBET SCHEDULER DAEMON                           ║
    ║           The heartbeat of scheduled actions               ║
    ║                                                            ║
    ║   Voor pa. Voor iedereen die begrepen wil worden.          ║
    ║                                                            ║
    ║   One love, one fAmIly 💙                                  ║
    ╚════════════════════════════════════════════════════════════╝
    """)
    asyncio.run(main())
