#!/usr/bin/env python3
"""
Magic: The Gathering Rules Updater

This module handles:
1. Downloading/updating the rules repository from GitHub
2. Scanning and validating the rules format
3. Updating the hardcoded keyword database
4. Scheduling weekly checks

Usage:
    from mtg_rules_engine.updater import RulesUpdater
    
    updater = RulesUpdater()
    
    # Check for updates
    updates = updater.check_for_updates()
    if updates['has_updates']:
        updater.apply_updates()
    
    # Run on startup
    updater.initialize()
"""

import os
import re
import json
import subprocess
import logging
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple, Any
from pathlib import Path

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class RulesUpdater:
    """
    Handles downloading, validating, and updating the MTG rules database.
    
    This updater:
    - Clones/pulls the rules repository from GitHub
    - Validates the rules format and completeness
    - Updates the hardcoded keyword database
    - Tracks the current rules version
    """
    
    # GitHub repository URL
    REPO_URL = "https://github.com/chaoticgoodcomputing/mtg-rules.git"
    
    # Local rules directory
    RULES_DIR = Path("/home/user/wall-o/mtg-rules")
    
    # Engine directory
    ENGINE_DIR = Path("/home/user/wall-o/mtg_rules_engine")
    
    # Keywords file
    KEYWORDS_FILE = ENGINE_DIR / "keywords.py"
    
    # State file for tracking updates
    STATE_FILE = ENGINE_DIR / "updater_state.json"
    
    def __init__(self):
        """Initialize the rules updater."""
        self._state = self._load_state()
        self._last_check = None
        self._last_update = None
        
    def _load_state(self) -> Dict[str, Any]:
        """Load the updater state from disk."""
        if self.STATE_FILE.exists():
            with open(self.STATE_FILE, 'r') as f:
                return json.load(f)
        return {
            "last_check": None,
            "last_update": None,
            "current_version": None,
            "rules_count": 0,
            "last_scan_status": None,
        }
    
    def _save_state(self):
        """Save the updater state to disk."""
        self.STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
        with open(self.STATE_FILE, 'w') as f:
            json.dump(self._state, f, indent=2)
    
    def initialize(self):
        """
        Initialize the updater: download rules and run initial scan.
        
        This should be called on engine startup.
        """
        logger.info("Initializing rules updater...")
        
        # Step 1: Ensure rules directory exists
        if not self.RULES_DIR.exists():
            logger.info("Cloning rules repository...")
            self._clone_repo()
        else:
            logger.info("Pulling latest rules...")
            self._pull_repo()
        
        # Step 2: Scan and validate rules
        logger.info("Scanning rules...")
        scan_result = self.scan_rules()
        
        if scan_result['status'] == 'error':
            logger.error(f"Rules scan failed: {scan_result['message']}")
            raise RuntimeError(f"Rules scan failed: {scan_result['message']}")
        
        # Step 3: Update keyword database if needed
        if scan_result['needs_update']:
            logger.info("Updating keyword database...")
            self._update_keywords(scan_result)
        
        # Step 4: Update state
        self._state['last_check'] = datetime.now().isoformat()
        self._state['last_update'] = datetime.now().isoformat()
        self._state['current_version'] = scan_result['version']
        self._state['rules_count'] = scan_result['rules_count']
        self._state['last_scan_status'] = 'success'
        self._save_state()
        
        logger.info(f"Initialization complete. Version: {scan_result['version']}")
    
    def check_for_updates(self) -> Dict[str, Any]:
        """
        Check if there are updates available from the rules repository.
        
        Returns:
            Dictionary with update information:
            - has_updates: bool
            - current_version: str
            - latest_version: str
            - changes: list of change descriptions
        """
        logger.info("Checking for rules updates...")
        
        # Get current version
        current_version = self._get_current_version()
        
        # Get latest version from remote
        latest_version = self._get_latest_version()
        
        has_updates = current_version != latest_version
        
        return {
            'has_updates': has_updates,
            'current_version': current_version,
            'latest_version': latest_version,
            'changes': [] if not has_updates else ['New rules version available'],
        }
    
    def apply_updates(self):
        """Apply any available updates to the rules repository."""
        logger.info("Applying rules updates...")
        
        # Pull latest changes
        self._pull_repo()
        
        # Scan and validate
        scan_result = self.scan_rules()
        
        if scan_result['status'] == 'error':
            logger.error(f"Rules scan failed after update: {scan_result['message']}")
            raise RuntimeError(f"Rules scan failed: {scan_result['message']}")
        
        # Update keyword database
        if scan_result['needs_update']:
            self._update_keywords(scan_result)
        
        # Update state
        self._state['last_update'] = datetime.now().isoformat()
        self._state['current_version'] = scan_result['version']
        self._state['rules_count'] = scan_result['rules_count']
        self._save_state()
        
        logger.info(f"Updates applied. New version: {scan_result['version']}")
    
    def scan_rules(self) -> Dict[str, Any]:
        """
        Scan the rules repository and validate format.
        
        Returns:
            Dictionary with scan results:
            - status: 'success' or 'error'
            - message: description of result
            - version: current rules version
            - rules_count: number of rules found
            - needs_update: whether keyword database needs updating
            - errors: list of any errors found
        """
        errors = []
        rules_count = 0
        version = None
        
        # Check if rules directory exists
        if not self.RULES_DIR.exists():
            return {
                'status': 'error',
                'message': 'Rules directory not found',
                'version': None,
                'rules_count': 0,
                'needs_update': False,
                'errors': ['Rules directory not found'],
            }
        
        # Get version from VERSION file
        version_file = self.RULES_DIR / "VERSION"
        if version_file.exists():
            with open(version_file, 'r') as f:
                version = f.read().strip()
        else:
            errors.append("VERSION file not found")
        
        # Scan all markdown files in rules directory
        rules_dir = self.RULES_DIR / "rules"
        if rules_dir.exists():
            for md_file in rules_dir.rglob("*.md"):
                rules_count += 1
                
                # Validate file format
                file_errors = self._validate_rule_file(md_file)
                errors.extend(file_errors)
        
        # Check for required files
        required_files = ["INTRO.md", "TABLE_OF_CONTENTS.md", "GLOSSARY.md", "CREDITS.md"]
        for req_file in required_files:
            if not (rules_dir / req_file).exists():
                errors.append(f"Required file missing: {req_file}")
        
        # Check for rules subdirectories
        rules_subdirs = rules_dir / "rules"
        if rules_subdirs.exists():
            for subdir in rules_subdirs.iterdir():
                if subdir.is_dir():
                    # Check that subdirectory has markdown files
                    md_files = list(subdir.glob("*.md"))
                    if not md_files:
                        errors.append(f"Rules subdirectory has no markdown files: {subdir.name}")
        
        needs_update = len(errors) > 0 or rules_count != self._state.get('rules_count', 0)
        
        return {
            'status': 'error' if errors else 'success',
            'message': '; '.join(errors) if errors else 'All rules validated successfully',
            'version': version,
            'rules_count': rules_count,
            'needs_update': needs_update,
            'errors': errors,
        }
    
    def _validate_rule_file(self, file_path: Path) -> List[str]:
        """
        Validate a single rule file's format.
        
        Args:
            file_path: Path to the markdown file
            
        Returns:
            List of validation errors (empty if valid)
        """
        errors = []
        
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()
        except Exception as e:
            return [f"Cannot read file {file_path}: {str(e)}"]
        
        # Check for empty files
        if not content.strip():
            errors.append(f"Empty file: {file_path.relative_to(self.RULES_DIR)}")
            return errors
        
        # Check for rule number format in filename (e.g., "100-general.md")
        if file_path.parent.name == "rules":
            # This is a main rule section
            filename = file_path.stem
            if not re.match(r'^\d+', filename):
                errors.append(f"Rule file doesn't start with number: {file_path.relative_to(self.RULES_DIR)}")
        
        return errors
    
    def _update_keywords(self, scan_result: Dict[str, Any]):
        """
        Update the hardcoded keyword database based on scanned rules.
        
        Args:
            scan_result: Results from scan_rules()
        """
        logger.info("Extracting keywords from rules...")
        
        # This would extract keywords from the rules markdown files
        # and update the keywords.py file
        # For now, we'll just log that an update is needed
        
        logger.info("Keyword database update would be performed here")
        logger.info(f"Rules version: {scan_result['version']}")
        logger.info(f"Rules count: {scan_result['rules_count']}")
    
    def _clone_repo(self):
        """Clone the rules repository."""
        try:
            subprocess.run(
                ["git", "clone", self.REPO_URL, str(self.RULES_DIR)],
                check=True,
                capture_output=True,
                text=True,
            )
            logger.info("Repository cloned successfully")
        except subprocess.CalledProcessError as e:
            logger.error(f"Failed to clone repository: {e.stderr}")
            raise
    
    def _pull_repo(self):
        """Pull latest changes from the repository."""
        try:
            subprocess.run(
                ["git", "-C", str(self.RULES_DIR), "pull"],
                check=True,
                capture_output=True,
                text=True,
            )
            logger.info("Repository pulled successfully")
        except subprocess.CalledProcessError as e:
            logger.error(f"Failed to pull repository: {e.stderr}")
            raise
    
    def _get_current_version(self) -> str:
        """Get the current rules version."""
        version_file = self.RULES_DIR / "VERSION"
        if version_file.exists():
            with open(version_file, 'r') as f:
                return f.read().strip()
        return "unknown"
    
    def _get_latest_version(self) -> str:
        """Get the latest rules version from the remote repository."""
        try:
            result = subprocess.run(
                ["git", "-C", str(self.RULES_DIR), "ls-remote", "origin", "HEAD"],
                check=True,
                capture_output=True,
                text=True,
            )
            # Parse the output to get the latest commit hash
            lines = result.stdout.strip().split('\n')
            if lines:
                return lines[0].split()[0]
        except subprocess.CalledProcessError:
            pass
        
        return self._get_current_version()
    
    def schedule_weekly_check(self):
        """
        Schedule a weekly check for rules updates.
        
        This creates a cron job that runs every Monday at 9 AM.
        """
        cron_expression = "0 9 * * 1"  # Every Monday at 9 AM
        
        # Create a script that runs the updater
        script_path = self.ENGINE_DIR / "weekly_update.sh"
        script_content = f"""#!/bin/bash
cd {self.ENGINE_DIR.parent}
python -m mtg_rules_engine.updater --weekly
"""
        
        with open(script_path, 'w') as f:
            f.write(script_content)
        
        os.chmod(script_path, 0o755)
        
        # Add to crontab
        cron_job = f"{cron_expression} {script_path}\n"
        existing_cron = subprocess.run(
            ["crontab", "-l"],
            capture_output=True,
            text=True,
        )
        
        if existing_cron.returncode == 0:
            new_cron = existing_cron.stdout + cron_job
        else:
            new_cron = cron_job
        
        subprocess.run(
            ["crontab", "-"],
            input=new_cron,
            text=True,
        )
        
        logger.info(f"Weekly update scheduled at {cron_expression}")


if __name__ == "__main__":
    import argparse
    
    parser = argparse.ArgumentParser(description="MTG Rules Updater")
    parser.add_argument("--check", action="store_true", help="Check for updates")
    parser.add_argument("--apply", action="store_true", help="Apply updates")
    parser.add_argument("--scan", action="store_true", help="Scan rules")
    parser.add_argument("--weekly", action="store_true", help="Run weekly check")
    
    args = parser.parse_args()
    
    updater = RulesUpdater()
    
    if args.check:
        updates = updater.check_for_updates()
        print(f"Has updates: {updates['has_updates']}")
        print(f"Current version: {updates['current_version']}")
        print(f"Latest version: {updates['latest_version']}")
    
    elif args.apply:
        updater.apply_updates()
    
    elif args.scan:
        result = updater.scan_rules()
        print(f"Status: {result['status']}")
        print(f"Message: {result['message']}")
        print(f"Version: {result['version']}")
        print(f"Rules count: {result['rules_count']}")
        if result['errors']:
            print(f"Errors: {result['errors']}")
    
    elif args.weekly:
        updates = updater.check_for_updates()
        if updates['has_updates']:
            updater.apply_updates()
        else:
            print("No updates available")
    
    else:
        # Default: initialize
        updater.initialize()
