#!/usr/bin/env python3
"""
RSS Feed Monitor
Tracks new entries across multiple RSS/Atom feeds with local state.
"""

import feedparser
import json
import hashlib
import sys
from pathlib import Path
from datetime import datetime, timezone
from typing import Dict, List, Any

SKILL_DIR = Path(__file__).parent
DEFAULT_CONFIG = SKILL_DIR / "feeds.json"
DEFAULT_STATE = SKILL_DIR / "feed-state.json"


def load_config(config_path: Path = DEFAULT_CONFIG) -> Dict:
    """Load feed configuration."""
    if not config_path.exists():
        return {"feeds": [], "stateFile": str(DEFAULT_STATE), "maxEntriesPerFeed": 50}
    
    with open(config_path) as f:
        return json.load(f)


def load_state(state_path: Path) -> Dict:
    """Load feed state (seen entries)."""
    if not state_path.exists():
        return {"feeds": {}}
    
    try:
        with open(state_path) as f:
            return json.load(f)
    except json.JSONDecodeError:
        # Corrupted state - rebuild
        return {"feeds": {}}


def save_state(state: Dict, state_path: Path):
    """Save feed state."""
    with open(state_path, 'w') as f:
        json.dump(state, f, indent=2)


def entry_id(entry: Any) -> str:
    """Generate unique ID for feed entry."""
    # Try GUID/ID first
    if hasattr(entry, 'id'):
        return entry.id
    
    # Fallback: hash of link + title
    key = f"{entry.get('link', '')}:{entry.get('title', '')}"
    return hashlib.sha256(key.encode()).hexdigest()[:16]


def check_feeds(config: Dict) -> Dict:
    """Check all feeds for new entries."""
    state_path = Path(config.get("stateFile", DEFAULT_STATE))
    state = load_state(state_path)
    
    new_entries = []
    errors = []
    
    for feed_config in config.get("feeds", []):
        url = feed_config["url"]
        name = feed_config.get("name", url)
        tags = feed_config.get("tags", [])
        
        # Get previous state
        feed_state = state["feeds"].get(url, {
            "lastChecked": None,
            "seenIds": [],
            "lastTitle": None
        })
        
        try:
            # Parse feed
            feed = feedparser.parse(url)
            
            if feed.bozo and not feed.entries:
                # Parse error and no entries
                errors.append({
                    "feed": name,
                    "error": str(getattr(feed, 'bozo_exception', 'Parse error'))
                })
                continue
            
            # Check each entry
            for entry in feed.entries[:config.get("maxEntriesPerFeed", 50)]:
                eid = entry_id(entry)
                
                if eid not in feed_state["seenIds"]:
                    # New entry!
                    new_entries.append({
                        "feed": name,
                        "title": entry.get("title", "Untitled"),
                        "link": entry.get("link", ""),
                        "published": entry.get("published", ""),
                        "summary": entry.get("summary", "")[:200],
                        "tags": tags
                    })
                    
                    feed_state["seenIds"].append(eid)
            
            # Update state
            feed_state["lastChecked"] = datetime.now(timezone.utc).isoformat()
            if feed.entries:
                feed_state["lastTitle"] = feed.entries[0].get("title", "")
            
            # Prune old seen IDs (keep last 200)
            feed_state["seenIds"] = feed_state["seenIds"][-200:]
            
            state["feeds"][url] = feed_state
            
        except Exception as e:
            errors.append({
                "feed": name,
                "error": str(e)
            })
    
    # Save updated state
    save_state(state, state_path)
    
    return {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "newEntries": new_entries,
        "errors": errors
    }


def list_feeds(config: Dict):
    """List configured feeds."""
    for feed in config.get("feeds", []):
        tags_str = ", ".join(feed.get("tags", []))
        notify = "✓" if feed.get("notify", False) else " "
        print(f"[{notify}] {feed.get('name', 'Unnamed')}")
        print(f"    URL: {feed['url']}")
        if tags_str:
            print(f"    Tags: {tags_str}")
        print()


def add_feed(config: Dict, name: str, url: str, tags: List[str] = None, notify: bool = False):
    """Add a new feed to config."""
    feed = {
        "name": name,
        "url": url,
        "tags": tags or [],
        "notify": notify
    }
    
    config.setdefault("feeds", []).append(feed)
    
    with open(DEFAULT_CONFIG, 'w') as f:
        json.dump(config, f, indent=2)
    
    print(f"✓ Added feed: {name}")


def reset_state(config: Dict):
    """Reset state (mark all current entries as seen)."""
    state_path = Path(config.get("stateFile", DEFAULT_STATE))
    state = {"feeds": {}}
    
    for feed_config in config.get("feeds", []):
        url = feed_config["url"]
        try:
            feed = feedparser.parse(url)
            seen_ids = [entry_id(e) for e in feed.entries[:50]]
            
            state["feeds"][url] = {
                "lastChecked": datetime.now(timezone.utc).isoformat(),
                "seenIds": seen_ids,
                "lastTitle": feed.entries[0].get("title", "") if feed.entries else None
            }
        except Exception as e:
            print(f"Error resetting {feed_config.get('name', url)}: {e}", file=sys.stderr)
    
    save_state(state, state_path)
    print(f"✓ Reset state for {len(state['feeds'])} feeds")


def main():
    if len(sys.argv) < 2:
        print("Usage: monitor.py [check|list|add|reset]")
        sys.exit(1)
    
    command = sys.argv[1]
    config = load_config()
    
    if command == "check":
        result = check_feeds(config)
        print(json.dumps(result, indent=2))
    
    elif command == "list":
        list_feeds(config)
    
    elif command == "add":
        if len(sys.argv) < 4:
            print("Usage: monitor.py add <name> <url> [--tags tag1,tag2] [--notify]")
            sys.exit(1)
        
        name = sys.argv[2]
        url = sys.argv[3]
        tags = []
        notify = False
        
        for i, arg in enumerate(sys.argv[4:], start=4):
            if arg == "--tags" and i + 1 < len(sys.argv):
                tags = sys.argv[i + 1].split(",")
            elif arg == "--notify":
                notify = True
        
        add_feed(config, name, url, tags, notify)
    
    elif command == "reset":
        reset_state(config)
    
    else:
        print(f"Unknown command: {command}")
        sys.exit(1)


if __name__ == "__main__":
    main()
