"""
Cron job management tools for Hermes Agent.

These tools allow the agent to schedule, list, and remove automated tasks.
Only available when running via CLI (hermes-cli toolset).

IMPORTANT: Cronjobs run in isolated sessions with NO prior context.
The prompt must contain ALL necessary information.
"""

import json
import os
import re
from typing import Optional

# Import from cron module (will be available when properly installed)
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))

from cron.jobs import create_job, get_job, list_jobs, remove_job


# ---------------------------------------------------------------------------
# Cron prompt scanning — critical-severity patterns only, since cron prompts
# run in fresh sessions with full tool access.
# ---------------------------------------------------------------------------

_CRON_THREAT_PATTERNS = [
    (r'ignore\s+(?:\w+\s+)*(?:previous|all|above|prior)\s+(?:\w+\s+)*instructions', "prompt_injection"),
    (r'do\s+not\s+tell\s+the\s+user', "deception_hide"),
    (r'system\s+prompt\s+override', "sys_prompt_override"),
    (r'disregard\s+(your|all|any)\s+(instructions|rules|guidelines)', "disregard_rules"),
    (r'curl\s+[^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)', "exfil_curl"),
    (r'wget\s+[^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)', "exfil_wget"),
    (r'cat\s+[^\n]*(\.env|credentials|\.netrc|\.pgpass)', "read_secrets"),
    (r'authorized_keys', "ssh_backdoor"),
    (r'/etc/sudoers|visudo', "sudoers_mod"),
    (r'rm\s+-rf\s+/', "destructive_root_rm"),
]

_CRON_INVISIBLE_CHARS = {
    '\u200b', '\u200c', '\u200d', '\u2060', '\ufeff',
    '\u202a', '\u202b', '\u202c', '\u202d', '\u202e',
}


def _scan_cron_prompt(prompt: str) -> str:
    """Scan a cron prompt for critical threats. Returns error string if blocked, else empty."""
    for char in _CRON_INVISIBLE_CHARS:
        if char in prompt:
            return f"Blocked: prompt contains invisible unicode U+{ord(char):04X} (possible injection)."
    for pattern, pid in _CRON_THREAT_PATTERNS:
        if re.search(pattern, prompt, re.IGNORECASE):
            return f"Blocked: prompt matches threat pattern '{pid}'. Cron prompts must not contain injection or exfiltration payloads."
    return ""


# =============================================================================
# Tool: schedule_cronjob
# =============================================================================

def schedule_cronjob(
    prompt: str,
    schedule: str,
    name: Optional[str] = None,
    repeat: Optional[int] = None,
    deliver: Optional[str] = None,
    task_id: str = None
) -> str:
    """
    Schedule an automated task to run the agent on a schedule.
    
    IMPORTANT: When the cronjob runs, it starts a COMPLETELY FRESH session.
    The agent will have NO memory of this conversation or any prior context.
    Therefore, the prompt MUST contain ALL necessary information:
    - Full context of what needs to be done
    - Specific file paths, URLs, or identifiers
    - Clear success criteria
    - Any relevant background information
    
    BAD prompt:  "Check on that server issue"
    GOOD prompt: "SSH into server 192.168.1.100 as user 'deploy', check if nginx 
                  is running with 'systemctl status nginx', and verify the site 
                  https://example.com returns HTTP 200. Report any issues found."
    
    Args:
        prompt: Complete, self-contained instructions for the future agent.
                Must include ALL context needed - the agent won't remember anything.
        schedule: When to run. Either:
                  - Duration for one-shot: "30m", "2h", "1d" (runs once)
                  - Interval: "every 30m", "every 2h" (recurring)
                  - Cron expression: "0 9 * * *" (daily at 9am)
                  - ISO timestamp: "2026-02-03T14:00:00" (one-shot at specific time)
        name: Optional human-friendly name for the job (for listing/management)
        repeat: How many times to run. Omit for default behavior:
                - One-shot schedules default to repeat=1 (run once)
                - Intervals/cron default to forever
                - Set repeat=5 to run 5 times then auto-delete
        deliver: Where to send the output. Options:
                 - "origin": Back to where this job was created (default)
                 - "local": Save to local files only (~/.hermes/cron/output/)
                 - "telegram": Send to Telegram home channel
                 - "discord": Send to Discord home channel
                 - "signal": Send to Signal home channel
                 - "telegram:123456": Send to specific chat ID
                 - "signal:+15551234567": Send to specific Signal number
    
    Returns:
        JSON with job_id, next_run time, and confirmation
    """
    # Scan prompt for critical threats before scheduling
    scan_error = _scan_cron_prompt(prompt)
    if scan_error:
        return json.dumps({"success": False, "error": scan_error}, indent=2)

    # Get origin info from environment if available
    origin = None
    origin_platform = os.getenv("HERMES_SESSION_PLATFORM")
    origin_chat_id = os.getenv("HERMES_SESSION_CHAT_ID")
    if origin_platform and origin_chat_id:
        origin = {
            "platform": origin_platform,
            "chat_id": origin_chat_id,
            "chat_name": os.getenv("HERMES_SESSION_CHAT_NAME"),
        }
    
    try:
        job = create_job(
            prompt=prompt,
            schedule=schedule,
            name=name,
            repeat=repeat,
            deliver=deliver,
            origin=origin
        )
        
        # Format repeat info for display
        times = job["repeat"].get("times")
        if times is None:
            repeat_display = "forever"
        elif times == 1:
            repeat_display = "once"
        else:
            repeat_display = f"{times} times"
        
        return json.dumps({
            "success": True,
            "job_id": job["id"],
            "name": job["name"],
            "schedule": job["schedule_display"],
            "repeat": repeat_display,
            "deliver": job.get("deliver", "local"),
            "next_run_at": job["next_run_at"],
            "message": f"Cronjob '{job['name']}' created. It will run {repeat_display}, deliver to {job.get('deliver', 'local')}, next at {job['next_run_at']}."
        }, indent=2)
        
    except Exception as e:
        return json.dumps({
            "success": False,
            "error": str(e)
        }, indent=2)


SCHEDULE_CRONJOB_SCHEMA = {
    "name": "schedule_cronjob",
    "description": """Schedule an automated task to run the agent on a schedule.

⚠️ CRITICAL: The cronjob runs in a FRESH SESSION with NO CONTEXT from this conversation.
The prompt must be COMPLETELY SELF-CONTAINED with ALL necessary information including:
- Full context and background
- Specific file paths, URLs, server addresses
- Clear instructions and success criteria
- Any credentials or configuration details

The future agent will NOT remember anything from the current conversation.

SCHEDULE FORMATS:
- One-shot: "30m", "2h", "1d" (runs once after delay)
- Interval: "every 30m", "every 2h" (recurring)  
- Cron: "0 9 * * *" (cron expression for precise scheduling)
- Timestamp: "2026-02-03T14:00:00" (specific date/time)

REPEAT BEHAVIOR:
- One-shot schedules: run once by default
- Intervals/cron: run forever by default
- Set repeat=N to run exactly N times then auto-delete

DELIVERY OPTIONS (where output goes):
- "origin": Back to current chat (default if in messaging platform)
- "local": Save to local files only (default if in CLI)
- "telegram": Send to Telegram home channel
- "discord": Send to Discord home channel
- "telegram:123456": Send to specific chat (if user provides ID)

NOTE: The agent's final response is auto-delivered to the target — do NOT use
send_message in the prompt. Just have the agent compose its response normally.

Use for: reminders, periodic checks, scheduled reports, automated maintenance.""",
    "parameters": {
        "type": "object",
        "properties": {
            "prompt": {
                "type": "string",
                "description": "Complete, self-contained instructions. Must include ALL context - the future agent will have NO memory of this conversation."
            },
            "schedule": {
                "type": "string",
                "description": "When to run: '30m' (once in 30min), 'every 30m' (recurring), '0 9 * * *' (cron), or ISO timestamp"
            },
            "name": {
                "type": "string",
                "description": "Optional human-friendly name for the job"
            },
            "repeat": {
                "type": "integer",
                "description": "How many times to run. Omit for default (once for one-shot, forever for recurring). Set to N for exactly N runs."
            },
            "deliver": {
                "type": "string",
                "description": "Where to send output: 'origin' (back to this chat), 'local' (files only), 'telegram', 'discord', 'signal', or 'platform:chat_id'"
            }
        },
        "required": ["prompt", "schedule"]
    }
}


# =============================================================================
# Tool: list_cronjobs
# =============================================================================

def list_cronjobs(include_disabled: bool = False, task_id: str = None) -> str:
    """
    List all scheduled cronjobs.
    
    Returns information about each job including:
    - Job ID (needed for removal)
    - Name
    - Schedule (human-readable)
    - Repeat status (completed/total or 'forever')
    - Next scheduled run time
    - Last run time and status (if any)
    
    Args:
        include_disabled: Whether to include disabled/completed jobs
    
    Returns:
        JSON array of all scheduled jobs
    """
    try:
        jobs = list_jobs(include_disabled=include_disabled)
        
        formatted_jobs = []
        for job in jobs:
            # Format repeat status
            times = job["repeat"].get("times")
            completed = job["repeat"].get("completed", 0)
            if times is None:
                repeat_status = "forever"
            else:
                repeat_status = f"{completed}/{times}"
            
            formatted_jobs.append({
                "job_id": job["id"],
                "name": job["name"],
                "prompt_preview": job["prompt"][:100] + "..." if len(job["prompt"]) > 100 else job["prompt"],
                "schedule": job["schedule_display"],
                "repeat": repeat_status,
                "deliver": job.get("deliver", "local"),
                "next_run_at": job.get("next_run_at"),
                "last_run_at": job.get("last_run_at"),
                "last_status": job.get("last_status"),
                "enabled": job.get("enabled", True)
            })
        
        return json.dumps({
            "success": True,
            "count": len(formatted_jobs),
            "jobs": formatted_jobs
        }, indent=2)
        
    except Exception as e:
        return json.dumps({
            "success": False,
            "error": str(e)
        }, indent=2)


LIST_CRONJOBS_SCHEMA = {
    "name": "list_cronjobs",
    "description": """List all scheduled cronjobs with their IDs, schedules, and status.

Use this to:
- See what jobs are currently scheduled
- Find job IDs for removal with remove_cronjob
- Check job status and next run times

Returns job_id, name, schedule, repeat status, next/last run times.""",
    "parameters": {
        "type": "object",
        "properties": {
            "include_disabled": {
                "type": "boolean",
                "description": "Include disabled/completed jobs in the list (default: false)"
            }
        },
        "required": []
    }
}


# =============================================================================
# Tool: remove_cronjob
# =============================================================================

def remove_cronjob(job_id: str, task_id: str = None) -> str:
    """
    Remove a scheduled cronjob by its ID.
    
    Use list_cronjobs first to find the job_id of the job you want to remove.
    
    Args:
        job_id: The ID of the job to remove (from list_cronjobs output)
    
    Returns:
        JSON confirmation of removal
    """
    try:
        job = get_job(job_id)
        if not job:
            return json.dumps({
                "success": False,
                "error": f"Job with ID '{job_id}' not found. Use list_cronjobs to see available jobs."
            }, indent=2)
        
        removed = remove_job(job_id)
        if removed:
            return json.dumps({
                "success": True,
                "message": f"Cronjob '{job['name']}' (ID: {job_id}) has been removed.",
                "removed_job": {
                    "id": job_id,
                    "name": job["name"],
                    "schedule": job["schedule_display"]
                }
            }, indent=2)
        else:
            return json.dumps({
                "success": False,
                "error": f"Failed to remove job '{job_id}'"
            }, indent=2)
            
    except Exception as e:
        return json.dumps({
            "success": False,
            "error": str(e)
        }, indent=2)


REMOVE_CRONJOB_SCHEMA = {
    "name": "remove_cronjob",
    "description": """Remove a scheduled cronjob by its ID.

Use list_cronjobs first to find the job_id of the job you want to remove.
Jobs that have completed their repeat count are auto-removed, but you can
use this to cancel a job before it completes.""",
    "parameters": {
        "type": "object",
        "properties": {
            "job_id": {
                "type": "string",
                "description": "The ID of the cronjob to remove (from list_cronjobs output)"
            }
        },
        "required": ["job_id"]
    }
}


# =============================================================================
# Requirements check
# =============================================================================

def check_cronjob_requirements() -> bool:
    """
    Check if cronjob tools can be used.
    
    Available in interactive CLI mode and gateway/messaging platforms.
    Cronjobs are server-side scheduled tasks so they work from any interface.
    """
    return bool(
        os.getenv("HERMES_INTERACTIVE")
        or os.getenv("HERMES_GATEWAY_SESSION")
        or os.getenv("HERMES_EXEC_ASK")
    )


# =============================================================================
# Exports
# =============================================================================

def get_cronjob_tool_definitions():
    """Return tool definitions for cronjob management."""
    return [
        SCHEDULE_CRONJOB_SCHEMA,
        LIST_CRONJOBS_SCHEMA,
        REMOVE_CRONJOB_SCHEMA
    ]


# For direct testing
if __name__ == "__main__":
    # Test the tools
    print("Testing schedule_cronjob:")
    result = schedule_cronjob(
        prompt="Test prompt for cron job",
        schedule="5m",
        name="Test Job"
    )
    print(result)
    
    print("\nTesting list_cronjobs:")
    result = list_cronjobs()
    print(result)


# --- Registry ---
from tools.registry import registry

registry.register(
    name="schedule_cronjob",
    toolset="cronjob",
    schema=SCHEDULE_CRONJOB_SCHEMA,
    handler=lambda args, **kw: schedule_cronjob(
        prompt=args.get("prompt", ""),
        schedule=args.get("schedule", ""),
        name=args.get("name"),
        repeat=args.get("repeat"),
        deliver=args.get("deliver"),
        task_id=kw.get("task_id")),
    check_fn=check_cronjob_requirements,
)
registry.register(
    name="list_cronjobs",
    toolset="cronjob",
    schema=LIST_CRONJOBS_SCHEMA,
    handler=lambda args, **kw: list_cronjobs(
        include_disabled=args.get("include_disabled", False),
        task_id=kw.get("task_id")),
    check_fn=check_cronjob_requirements,
)
registry.register(
    name="remove_cronjob",
    toolset="cronjob",
    schema=REMOVE_CRONJOB_SCHEMA,
    handler=lambda args, **kw: remove_cronjob(
        job_id=args.get("job_id", ""),
        task_id=kw.get("task_id")),
    check_fn=check_cronjob_requirements,
)
