"""
Classification Rules Module

Learns patterns from video_metadata_index.csv and generates rules for automatic
subdirectory assignment. Creates a compact JSON ruleset that can be used to
predict target directories for new videos based on metadata.

The module analyzes existing videos and creates rules based on:
- Genre patterns
- Keyword/grouping patterns  
- Artist/channel patterns

Rules are stored in classification_rules.json and can be regenerated as needed.
"""

import os
import csv
import json
import traceback
from datetime import datetime
from typing import Dict, List, Optional, Any, Tuple
from collections import defaultdict, Counter

from .config import CONFIG


# Configuration constants for rule generation
MIN_KEYWORD_COUNT = 3  # Keyword must appear at least 3 times to generate a rule
MIN_ARTIST_COUNT = 2   # Artist must appear at least 2 times to generate a rule
CONFIDENCE_BOOST_BASE = 0.1  # Confidence increase per additional match
CONFIDENCE_BOOST_MAX = 0.2   # Maximum confidence boost for multiple matches


def extract_subdirectory_from_filepath(filepath: str) -> Optional[str]:
    """
    Extract the subdirectory (target) from a filepath.
    
    Example: "Patrick/SpaceX/video.m4v" -> "Patrick"
             "Kinder/Peppa/video.m4v" -> "Kinder"
    
    Args:
        filepath: Full or relative path to video file
    
    Returns:
        Subdirectory name or None if not extractable
    """
    if not filepath:
        return None
    
    # Remove rclone prefix if present (e.g., "lyssach-nas:/Internetfilme/")
    # Split by / and find the target directory (usually first after prefix removal)
    parts = filepath.split('/')
    
    # Find the part that comes after "Internetfilme" - this is the target directory
    for i, part in enumerate(parts):
        if part == 'Internetfilme':
            # Next part is the target directory
            if i + 1 < len(parts):
                return parts[i + 1]
    
    # Fallback: assume first meaningful part is target
    # Skip empty parts and known prefixes
    for part in parts:
        if part and ':' not in part and part not in ['Emby', 'media']:
            return part
    
    return None


def analyze_csv_patterns(csv_path: str) -> Dict[str, Any]:
    """
    Analyze video_metadata_index.csv to extract classification patterns.
    
    Returns a dictionary with pattern statistics for each target directory:
    {
        "Patrick": {
            "genres": Counter({"Wissenschaft & Technik": 50, "Dokumentation": 30}),
            "keywords": Counter({"Space": 45, "SpaceX": 40, "Rakete": 30}),
            "artists": Counter({"SpaceX Official": 40, "Everyday Astronaut": 30}),
            "total_videos": 100
        },
        ...
    }
    
    Args:
        csv_path: Path to video_metadata_index.csv
    
    Returns:
        Dictionary with pattern statistics per target directory
    """
    if not os.path.exists(csv_path):
        raise FileNotFoundError(f"CSV file not found: {csv_path}")
    
    patterns = defaultdict(lambda: {
        "genres": Counter(),
        "keywords": Counter(),
        "artists": Counter(),
        "total_videos": 0
    })
    
    with open(csv_path, 'r', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        
        for row in reader:
            # Skip rows with errors
            if row.get('error'):
                continue
            
            # Extract target from filepath
            filepath = row.get('filepath', '')
            target = extract_subdirectory_from_filepath(filepath)
            
            if not target:
                continue
            
            # Increment video count
            patterns[target]["total_videos"] += 1
            
            # Collect genre
            genre = row.get('genre', '').strip()
            if genre:
                patterns[target]["genres"][genre] += 1
            
            # Collect keywords (split by / separator)
            grouping = row.get('grouping', '')
            if grouping:
                keywords = [kw.strip() for kw in grouping.split('/')]
                for keyword in keywords:
                    if keyword:
                        patterns[target]["keywords"][keyword] += 1
            
            # Collect artist/channel
            artist = row.get('artist', '').strip()
            if artist:
                patterns[target]["artists"][artist] += 1
    
    return dict(patterns)


def generate_classification_rules(patterns: Dict[str, Any], 
                                  min_confidence: float = 0.3) -> Dict[str, Any]:
    """
    Generate classification rules from pattern analysis.
    
    Rules are generated with confidence scores based on how strongly each
    pattern correlates with a target directory.
    
    Args:
        patterns: Pattern statistics from analyze_csv_patterns()
        min_confidence: Minimum confidence threshold for including a rule (0.0-1.0)
    
    Returns:
        Dictionary with classification rules
    """
    rules = {
        "metadata": {
            "generated_at": None,  # Will be set by caller
            "total_videos_analyzed": sum(p["total_videos"] for p in patterns.values()),
            "targets": list(patterns.keys()),
            "min_confidence": min_confidence
        },
        "rules": {
            "genre": {},
            "keyword": {},
            "artist": {}
        }
    }
    
    # Calculate total occurrences for normalization
    all_genres = Counter()
    all_keywords = Counter()
    all_artists = Counter()
    
    for target_patterns in patterns.values():
        all_genres.update(target_patterns["genres"])
        all_keywords.update(target_patterns["keywords"])
        all_artists.update(target_patterns["artists"])
    
    # Generate genre rules
    for target, target_patterns in patterns.items():
        for genre, count in target_patterns["genres"].items():
            # Calculate confidence: percentage of this genre that belongs to this target
            total_genre_count = all_genres[genre]
            confidence = count / total_genre_count if total_genre_count > 0 else 0
            
            if confidence >= min_confidence:
                if genre not in rules["rules"]["genre"]:
                    rules["rules"]["genre"][genre] = []
                
                rules["rules"]["genre"][genre].append({
                    "target": target,
                    "confidence": round(confidence, 3),
                    "sample_count": count
                })
    
    # Generate keyword rules (only for keywords that appear frequently)
    for target, target_patterns in patterns.items():
        for keyword, count in target_patterns["keywords"].items():
            if count < MIN_KEYWORD_COUNT:
                continue
            
            total_keyword_count = all_keywords[keyword]
            confidence = count / total_keyword_count if total_keyword_count > 0 else 0
            
            if confidence >= min_confidence:
                if keyword not in rules["rules"]["keyword"]:
                    rules["rules"]["keyword"][keyword] = []
                
                rules["rules"]["keyword"][keyword].append({
                    "target": target,
                    "confidence": round(confidence, 3),
                    "sample_count": count
                })
    
    # Generate artist rules (only for artists that appear frequently)
    for target, target_patterns in patterns.items():
        for artist, count in target_patterns["artists"].items():
            if count < MIN_ARTIST_COUNT:
                continue
            
            total_artist_count = all_artists[artist]
            confidence = count / total_artist_count if total_artist_count > 0 else 0
            
            if confidence >= min_confidence:
                if artist not in rules["rules"]["artist"]:
                    rules["rules"]["artist"][artist] = []
                
                rules["rules"]["artist"][artist].append({
                    "target": target,
                    "confidence": round(confidence, 3),
                    "sample_count": count
                })
    
    # Sort rules by confidence for each pattern
    for rule_type in rules["rules"].values():
        for pattern, target_list in rule_type.items():
            rule_type[pattern] = sorted(target_list, key=lambda x: x["confidence"], reverse=True)
    
    return rules


def save_classification_rules(rules: Dict[str, Any], output_path: str) -> None:
    """
    Save classification rules to JSON file.
    
    Args:
        rules: Classification rules dictionary
        output_path: Path to save JSON file
    """
    # Set generation timestamp
    rules["metadata"]["generated_at"] = datetime.now().isoformat()
    
    with open(output_path, 'w', encoding='utf-8') as f:
        json.dump(rules, f, indent=2, ensure_ascii=False)
    
    print(f"✅ Classification rules saved to: {output_path}")


def load_classification_rules(rules_path: str) -> Optional[Dict[str, Any]]:
    """
    Load classification rules from JSON file.
    
    Args:
        rules_path: Path to classification_rules.json
    
    Returns:
        Rules dictionary or None if file doesn't exist
    """
    if not os.path.exists(rules_path):
        return None
    
    try:
        with open(rules_path, 'r', encoding='utf-8') as f:
            return json.load(f)
    except Exception as e:
        print(f"⚠️  Error loading classification rules: {e}")
        return None


def classify_video(video_metadata: Dict[str, str], 
                   rules: Dict[str, Any]) -> List[Tuple[str, float, str]]:
    """
    Classify a video based on its metadata and classification rules.
    
    Returns a list of (target, confidence, reason) tuples sorted by confidence.
    
    Args:
        video_metadata: Dictionary with video metadata (genre, grouping, artist, etc.)
        rules: Classification rules from load_classification_rules()
    
    Returns:
        List of (target, confidence, reason) tuples, sorted by confidence descending
    """
    if not rules:
        return []
    
    # Collect scores for each target
    target_scores = defaultdict(list)  # target -> [(confidence, reason), ...]
    
    # Check genre match
    genre = video_metadata.get('genre', '').strip()
    if genre and genre in rules["rules"]["genre"]:
        for rule in rules["rules"]["genre"][genre]:
            target = rule["target"]
            confidence = rule["confidence"]
            target_scores[target].append((confidence, f"Genre: {genre}"))
    
    # Check keyword matches
    grouping = video_metadata.get('grouping', '')
    if grouping:
        keywords = [kw.strip() for kw in grouping.split('/')]
        for keyword in keywords:
            if keyword and keyword in rules["rules"]["keyword"]:
                for rule in rules["rules"]["keyword"][keyword]:
                    target = rule["target"]
                    confidence = rule["confidence"]
                    target_scores[target].append((confidence, f"Keyword: {keyword}"))
    
    # Check artist match
    artist = video_metadata.get('artist', '').strip()
    if artist and artist in rules["rules"]["artist"]:
        for rule in rules["rules"]["artist"][artist]:
            target = rule["target"]
            confidence = rule["confidence"]
            target_scores[target].append((confidence, f"Artist: {artist}"))
    
    # Calculate aggregate scores for each target
    target_results = []
    for target, scores in target_scores.items():
        # Use weighted average with boosting for multiple matches
        confidences = [score[0] for score in scores]
        reasons = [score[1] for score in scores]
        
        # Average confidence, with bonus for multiple matches
        avg_confidence = sum(confidences) / len(confidences)
        match_count_bonus = min(CONFIDENCE_BOOST_MAX, (len(confidences) - 1) * CONFIDENCE_BOOST_BASE)
        final_confidence = min(1.0, avg_confidence + match_count_bonus)
        
        # Combine reasons
        reason = ", ".join(reasons)
        
        target_results.append((target, final_confidence, reason))
    
    # Sort by confidence descending
    target_results.sort(key=lambda x: x[1], reverse=True)
    
    return target_results


def learn_classification_rules(csv_path: Optional[str] = None, 
                               output_path: Optional[str] = None,
                               min_confidence: float = 0.3) -> bool:
    """
    Main function to learn classification rules from CSV and save them.
    
    Args:
        csv_path: Path to video_metadata_index.csv (default: from config work_dir)
        output_path: Path to save classification_rules.json (default: from config work_dir)
        min_confidence: Minimum confidence threshold for rules
    
    Returns:
        True if successful, False otherwise
    """
    # Determine paths
    work_dir = CONFIG.get("work_dir")
    if not work_dir:
        work_dir = os.path.expanduser("~/Movies/Internetfilme/Work")
    
    if csv_path is None:
        csv_path = os.path.join(work_dir, "video_metadata_index.csv")
    
    if output_path is None:
        output_path = os.path.join(work_dir, "classification_rules.json")
    
    print("\n" + "=" * 80)
    print("🧠 CLASSIFICATION RULES LEARNING")
    print("=" * 80)
    print(f"Input:  {csv_path}")
    print(f"Output: {output_path}")
    print(f"Min Confidence: {min_confidence}")
    print()
    
    try:
        # Analyze patterns
        print("📊 Analyzing video metadata patterns...")
        patterns = analyze_csv_patterns(csv_path)
        
        if not patterns:
            print("⚠️  No patterns found in CSV")
            return False
        
        # Show statistics
        print(f"\n📈 Found patterns for {len(patterns)} target directories:")
        for target, stats in patterns.items():
            print(f"   • {target}: {stats['total_videos']} videos")
        
        # Generate rules
        print("\n🔧 Generating classification rules...")
        rules = generate_classification_rules(patterns, min_confidence)
        
        # Show rule statistics
        print(f"\n📋 Generated rules:")
        print(f"   • Genre rules: {len(rules['rules']['genre'])}")
        print(f"   • Keyword rules: {len(rules['rules']['keyword'])}")
        print(f"   • Artist rules: {len(rules['rules']['artist'])}")
        
        # Save rules
        print()
        save_classification_rules(rules, output_path)
        
        print("\n✅ Classification rules learning completed successfully!")
        print("=" * 80)
        
        return True
    
    except Exception as e:
        print(f"\n❌ Error during classification rules learning: {e}")
        traceback.print_exc()
        return False


def suggest_target_for_video(video_metadata: Dict[str, str],
                             rules_path: Optional[str] = None,
                             top_n: int = 3) -> List[Tuple[str, float, str]]:
    """
    Suggest target directories for a video based on classification rules.
    
    Args:
        video_metadata: Dictionary with video metadata
        rules_path: Path to classification_rules.json (default: from config work_dir)
        top_n: Number of top suggestions to return
    
    Returns:
        List of top N (target, confidence, reason) tuples
    """
    # Determine rules path
    if rules_path is None:
        work_dir = CONFIG.get("work_dir")
        if not work_dir:
            work_dir = os.path.expanduser("~/Movies/Internetfilme/Work")
        rules_path = os.path.join(work_dir, "classification_rules.json")
    
    # Load rules
    rules = load_classification_rules(rules_path)
    if not rules:
        return []
    
    # Classify video
    results = classify_video(video_metadata, rules)
    
    # Return top N
    return results[:top_n]
