from __future__ import annotations

import tempfile
from pathlib import Path
from typing import Any, Dict, List, Optional
from collections import Counter

import reapy
import numpy as np

from reaper_mcp.core.mcp_core import mcp
from reaper_mcp.audio.audio_analysis import analyze_audio_file
from reaper_mcp.project.project import get_project_structure, get_all_midi_notes_from_track, render_track_to_file


@mcp.tool()
def analyze_project_structure() -> Dict[str, Any]:
    """Analyze the current project structure including key, tempo, track types, markers, and regions.
    
    Returns:
        Dict with comprehensive project analysis including:
        - key: Detected or estimated musical key
        - tempo: Project BPM
        - track_count: Number of tracks
        - tracks: List of tracks with type classification (drums, bass, melody, chord, etc.)
        - markers: List of markers with positions and names
        - regions: List of regions with start/end and names
        - length: Total project length in seconds
    """
    try:
        project = reapy.Project()
        
        # Get basic project info
        bpm = project.bpm
        length = project.length
        
        # Get tracks and classify them
        tracks_info = []
        for i, track in enumerate(project.tracks):
            track_type = _classify_track(track)
            track_info = {
                "index": i,
                "name": track.name,
                "type": track_type,
                "n_items": track.n_items,
                "has_midi": any(hasattr(item, 'takes') and item.takes for item in track.items),
                "is_muted": track.is_muted,
                "is_soloed": track.is_solo,
            }
            tracks_info.append(track_info)
        
        # Get markers
        markers = []
        for marker in project.markers:
            markers.append({
                "position": marker.position,
                "name": marker.name,
            })
        
        # Get regions
        regions = []
        for region in project.regions:
            regions.append({
                "start": region.start,
                "end": region.end,
                "name": region.name,
            })
        
        # Attempt to detect key from MIDI tracks
        detected_key = _detect_project_key(project)
        
        return {
            "tempo": bpm,
            "key": detected_key,
            "length": length,
            "track_count": len(tracks_info),
            "tracks": tracks_info,
            "markers": markers,
            "regions": regions,
        }
    except Exception as e:
        return {"error": f"Failed to analyze project structure: {e}"}


@mcp.tool()
def analyze_track_audio(track_index: int) -> Dict[str, Any]:
    """Render and analyze a track's audio content to extract musical characteristics.
    
    Args:
        track_index: Index of the track to analyze
    
    Returns:
        Dict with audio analysis results including tempo, key, energy, spectral characteristics
    """
    try:
        project = reapy.Project()
        if track_index < 0 or track_index >= len(project.tracks):
            return {"error": f"Invalid track index: {track_index}"}
        
        track = project.tracks[track_index]
        
        # Create temporary file for rendering
        with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmp:
            temp_path = tmp.name
        
        try:
            # Render the track (0 to project length)
            track.solo()
            # Use REAPER's render API if available
            try:
                project.render(temp_path, start_time=0, end_time=project.length)
            except (AttributeError, TypeError):
                # Fallback: provide manual rendering guidance
                track.unsolo()
                return {
                    "error": "Automatic rendering not available. Please render track manually and use analyze_audio_file instead.",
                    "track_index": track_index,
                    "track_name": track.name,
                }
            track.unsolo()
            
            # Analyze the rendered file
            temp_path_obj = Path(temp_path)
            if temp_path_obj.exists() and temp_path_obj.stat().st_size > 0:
                analysis = analyze_audio_file(temp_path)
                if "error" in analysis:
                    return analysis
                
                # Add track info to analysis
                analysis["track_index"] = track_index
                analysis["track_name"] = track.name
                return analysis
            else:
                return {"error": "Rendered file is empty or was not created"}
        finally:
            # Clean up temp file
            temp_path_obj = Path(temp_path)
            if temp_path_obj.exists():
                try:
                    temp_path_obj.unlink()
                except Exception:
                    pass
    except Exception as e:
        return {"error": f"Failed to analyze track audio: {e}"}


@mcp.tool()
def detect_chord_progression(track_index: int) -> Dict[str, Any]:
    """Detect the chord progression from a MIDI track.
    
    Args:
        track_index: Index of the MIDI track to analyze
    
    Returns:
        Dict with detected chord progression as a list of chords with timestamps
    """
    try:
        # Get all MIDI notes from the track
        notes_result = get_all_midi_notes_from_track(track_index)
        if "error" in notes_result:
            return notes_result
        
        notes = notes_result.get("notes", [])
        if not notes:
            return {
                "track_index": track_index,
                "chords": [],
                "message": "No MIDI notes found on track"
            }
        
        # Group notes into time windows (e.g., 1-second windows)
        window_size = 0.5  # seconds
        chords = []
        
        # Find the time range
        if notes:
            start_time = min(note["start"] for note in notes)
            end_time = max(note["end"] for note in notes)
            
            current_time = start_time
            while current_time < end_time:
                # Find all notes active in this window
                active_notes = []
                for note in notes:
                    if note["start"] <= current_time < note["end"] or \
                       (current_time <= note["start"] < current_time + window_size):
                        active_notes.append(note["pitch"] % 12)  # Pitch class
                
                if active_notes:
                    # Count pitch classes
                    pitch_classes = list(set(active_notes))
                    pitch_classes.sort()
                    
                    # Attempt to identify chord
                    chord_name = _identify_chord(pitch_classes)
                    
                    # Avoid duplicate consecutive chords
                    if not chords or chords[-1]["chord"] != chord_name:
                        chords.append({
                            "time": current_time,
                            "chord": chord_name,
                            "pitch_classes": pitch_classes,
                        })
                
                current_time += window_size
        
        return {
            "track_index": track_index,
            "track_name": notes_result.get("track_name", ""),
            "chord_count": len(chords),
            "chords": chords,
        }
    except Exception as e:
        return {"error": f"Failed to detect chord progression: {e}"}


@mcp.tool()
def extract_rhythm_pattern(track_index: int, bars: int = 4) -> Dict[str, Any]:
    """Extract rhythm pattern from a drum/percussion track as MIDI events.
    
    Args:
        track_index: Index of the track to analyze
        bars: Number of bars to analyze (default: 4)
    
    Returns:
        Dict with rhythm pattern as list of MIDI events (pitch, time, velocity)
    """
    try:
        project = reapy.Project()
        if track_index < 0 or track_index >= len(project.tracks):
            return {"error": f"Invalid track index: {track_index}"}
        
        # Get MIDI notes from the track
        notes_result = get_all_midi_notes_from_track(track_index)
        if "error" in notes_result:
            return notes_result
        
        notes = notes_result.get("notes", [])
        if not notes:
            return {
                "track_index": track_index,
                "pattern": [],
                "message": "No MIDI notes found on track"
            }
        
        # Calculate time range for the requested bars
        bpm = project.bpm
        seconds_per_beat = 60.0 / bpm
        beats_per_bar = 4  # Assume 4/4 time
        bar_duration = seconds_per_beat * beats_per_bar
        total_duration = bar_duration * bars
        
        # Filter notes to the first N bars
        start_time = min(note["start"] for note in notes) if notes else 0
        end_time = start_time + total_duration
        
        pattern = []
        for note in notes:
            if start_time <= note["start"] < end_time:
                # Normalize time relative to pattern start
                relative_time = note["start"] - start_time
                pattern.append({
                    "pitch": note["pitch"],
                    "time": relative_time,
                    "velocity": note["velocity"],
                    "duration": note["end"] - note["start"],
                })
        
        # Sort by time
        pattern.sort(key=lambda x: x["time"])
        
        # Analyze drum hits (common MIDI drum pitches)
        drum_map = {
            36: "Kick", 35: "Kick",
            38: "Snare", 40: "Snare",
            42: "HiHat Closed", 44: "HiHat Pedal", 46: "HiHat Open",
            49: "Crash", 51: "Ride", 57: "Crash",
        }
        
        drum_pattern = []
        for event in pattern:
            drum_name = drum_map.get(event["pitch"], f"Note {event['pitch']}")
            drum_pattern.append({
                "drum": drum_name,
                "time": event["time"],
                "velocity": event["velocity"],
            })
        
        return {
            "track_index": track_index,
            "track_name": notes_result.get("track_name", ""),
            "bars": bars,
            "bpm": bpm,
            "pattern": pattern,
            "drum_pattern": drum_pattern,
            "note_count": len(pattern),
        }
    except Exception as e:
        return {"error": f"Failed to extract rhythm pattern: {e}"}


@mcp.tool()
def suggest_next_section(analysis: Dict[str, Any], section_type: str = "verse") -> Dict[str, Any]:
    """Suggest musical content for the next section based on existing project analysis.
    
    Args:
        analysis: Project analysis dictionary (from analyze_project_structure)
        section_type: Type of section to suggest (verse, chorus, bridge, outro)
    
    Returns:
        Dict with suggestions for the next section including:
        - chord_progression: Suggested chords
        - tempo: Recommended tempo
        - key: Musical key
        - duration: Suggested duration in bars
        - arrangement_tips: Text suggestions for arrangement
    """
    try:
        # Extract info from analysis
        current_key = analysis.get("key", "C")
        current_tempo = analysis.get("tempo", 120)
        current_length = analysis.get("length", 0)
        
        # Section-specific suggestions
        suggestions = {
            "verse": {
                "duration_bars": 8,
                "chord_progression": ["I", "V", "vi", "IV"],
                "arrangement": "Keep energy moderate. Use fewer instruments than chorus. Focus on vocals/melody.",
            },
            "chorus": {
                "duration_bars": 8,
                "chord_progression": ["I", "V", "vi", "IV"],
                "arrangement": "Increase energy. Add more instruments. Make it memorable and repetitive.",
            },
            "bridge": {
                "duration_bars": 4,
                "chord_progression": ["ii", "IV", "I", "V"],
                "arrangement": "Create contrast. Change chord progression or key. Break the pattern.",
            },
            "outro": {
                "duration_bars": 4,
                "chord_progression": ["I", "V", "I"],
                "arrangement": "Wind down energy. Repeat main motif. Fade out or resolve cleanly.",
            },
            "intro": {
                "duration_bars": 4,
                "chord_progression": ["I", "V"],
                "arrangement": "Build anticipation. Start sparse and build up gradually.",
            },
        }
        
        section_type = section_type.lower()
        if section_type not in suggestions:
            return {"error": f"Unknown section type: {section_type}. Use: verse, chorus, bridge, outro, intro"}
        
        suggestion = suggestions[section_type]
        
        # Convert Roman numerals to actual chords based on key
        actual_chords = _roman_to_chords(suggestion["chord_progression"], current_key)
        
        return {
            "section_type": section_type,
            "key": current_key,
            "tempo": current_tempo,
            "duration_bars": suggestion["duration_bars"],
            "chord_progression": actual_chords,
            "roman_numerals": suggestion["chord_progression"],
            "arrangement_tips": suggestion["arrangement"],
            "suggested_start_time": current_length,
        }
    except Exception as e:
        return {"error": f"Failed to suggest next section: {e}"}


# Helper functions

def _classify_track(track) -> str:
    """Classify a track based on its name and characteristics."""
    name = track.name.lower()
    
    # Check for common keywords
    if any(word in name for word in ["drum", "kick", "snare", "hat", "perc"]):
        return "drums"
    elif any(word in name for word in ["bass", "sub"]):
        return "bass"
    elif any(word in name for word in ["vocal", "vox", "voice", "lead", "melody"]):
        return "melody"
    elif any(word in name for word in ["chord", "piano", "guitar", "keys", "pad"]):
        return "chords"
    elif any(word in name for word in ["fx", "effect", "atmosphere", "ambient"]):
        return "fx"
    else:
        return "unknown"


def _detect_project_key(project) -> str:
    """Attempt to detect the musical key from MIDI tracks."""
    try:
        # Collect all MIDI notes from all tracks
        all_pitches = []
        for track in project.tracks:
            for item in track.items:
                if hasattr(item, 'takes') and item.takes:
                    take = item.takes[0]
                    if hasattr(take, 'notes'):
                        for note in take.notes:
                            all_pitches.append(note.pitch % 12)
        
        if not all_pitches:
            return "Unknown"
        
        # Count pitch class occurrences
        pitch_counts = Counter(all_pitches)
        
        # Simple key detection: find most common pitch class
        # This is a very simplified approach
        most_common = pitch_counts.most_common(1)[0][0]
        
        # Map pitch class to note name
        note_names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
        detected_note = note_names[most_common]
        
        # Assume major for simplicity
        return f"{detected_note} major"
    except Exception:
        return "Unknown"


def _identify_chord(pitch_classes: List[int]) -> str:
    """Identify chord name from pitch classes (0-11)."""
    if not pitch_classes:
        return "N/A"
    
    note_names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
    
    # Sort and get intervals
    pitch_classes = sorted(set(pitch_classes))
    if len(pitch_classes) == 1:
        return note_names[pitch_classes[0]]
    
    # Calculate intervals from root
    root = pitch_classes[0]
    intervals = [(pc - root) % 12 for pc in pitch_classes]
    intervals_set = set(intervals)
    
    # Common chord patterns
    chord_patterns = {
        (0, 4, 7): "major",
        (0, 3, 7): "minor",
        (0, 4, 7, 11): "maj7",
        (0, 3, 7, 10): "min7",
        (0, 4, 7, 10): "7",
        (0, 3, 6): "dim",
        (0, 4, 8): "aug",
        (0, 5, 7): "sus4",
        (0, 2, 7): "sus2",
    }
    
    # Check for matches
    for pattern, chord_type in chord_patterns.items():
        if intervals_set >= set(pattern):
            return f"{note_names[root]}{chord_type}"
    
    # Default: just list the notes
    return "+".join(note_names[pc] for pc in pitch_classes)


def _roman_to_chords(roman_numerals: List[str], key: str) -> List[str]:
    """Convert Roman numeral chord progression to actual chord names."""
    # Parse key (e.g., "C major" or "C")
    key_note = key.split()[0] if " " in key else key
    
    # Map note names to pitch classes
    note_to_pc = {
        "C": 0, "C#": 1, "Db": 1, "D": 2, "D#": 3, "Eb": 3,
        "E": 4, "F": 5, "F#": 6, "Gb": 6, "G": 7, "G#": 8,
        "Ab": 8, "A": 9, "A#": 10, "Bb": 10, "B": 11,
    }
    
    pc_to_note = ["C", "C#", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B"]
    
    # Major scale intervals
    major_scale = [0, 2, 4, 5, 7, 9, 11]
    
    # Get root pitch class
    root_pc = note_to_pc.get(key_note, 0)
    
    # Build scale
    scale_notes = [(root_pc + interval) % 12 for interval in major_scale]
    
    # Map Roman numerals to scale degrees
    roman_map = {
        "I": (0, ""), "i": (0, "m"),
        "II": (1, ""), "ii": (1, "m"),
        "III": (2, ""), "iii": (2, "m"),
        "IV": (3, ""), "iv": (3, "m"),
        "V": (4, ""), "v": (4, "m"),
        "VI": (5, ""), "vi": (5, "m"),
        "VII": (6, ""), "vii": (6, "dim"),
    }
    
    actual_chords = []
    for numeral in roman_numerals:
        if numeral in roman_map:
            degree, quality = roman_map[numeral]
            chord_root = scale_notes[degree]
            chord_name = pc_to_note[chord_root] + quality
            actual_chords.append(chord_name)
        else:
            actual_chords.append(numeral)  # Keep as-is if not recognized
    
    return actual_chords
