from datetime import datetime
from musicround import db
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
import uuid

# User-Role association table for many-to-many relationship
user_roles = db.Table('user_roles',
    db.Column('user_id', db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), primary_key=True),
    db.Column('role_id', db.Integer, db.ForeignKey('role.id', ondelete='CASCADE'), primary_key=True)
)

class Role(db.Model):
    """
    Role model for user permissions
    """
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), unique=True, nullable=False)
    description = db.Column(db.String(255))
    
    def __repr__(self):
        return f"Role(id={self.id}, name='{self.name}')"


class UserPreferences(db.Model):
    """
    User preferences model for storing user-specific settings
    """
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), unique=True)
    default_tts_service = db.Column(db.String(32), default='polly')
    enable_intro = db.Column(db.Boolean, default=True)
    theme = db.Column(db.String(16), default='light')
    user = db.relationship('User', back_populates='preferences')


class User(db.Model, UserMixin):
    """
    User model for authentication and user management
    """
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), index=True, unique=True, nullable=False)
    email = db.Column(db.String(120), index=True, unique=True, nullable=False)
    password_hash = db.Column(db.String(255), nullable=True)  # Now nullable for OAuth-only users
    first_name = db.Column(db.String(50))
    last_name = db.Column(db.String(50))
    active = db.Column(db.Boolean, default=True)
    is_admin = db.Column(db.Boolean, default=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    last_login = db.Column(db.DateTime)
    
    # Token for password reset, email verification, etc.
    reset_token = db.Column(db.String(100), index=True, unique=True)
    reset_token_expiry = db.Column(db.DateTime)
    
    # Authentication provider info
    auth_provider = db.Column(db.String(20), default='local')  # 'local', 'google', 'authentik'
    
    # OAuth provider info - Spotify
    spotify_id = db.Column(db.String(100), index=True, unique=True, nullable=True) # Spotify user ID
    spotify_token = db.Column(db.Text)         # Store Spotify access token 
    spotify_refresh_token = db.Column(db.Text)  # Store Spotify refresh token
    spotify_token_expiry = db.Column(db.DateTime)
    
    # OAuth provider info - Google
    google_id = db.Column(db.String(100))      # Google user ID
    google_token = db.Column(db.Text)          # Store Google access token
    google_refresh_token = db.Column(db.Text)   # Store Google refresh token
    
    # OAuth provider info - Authentik
    authentik_id = db.Column(db.String(100))    # Authentik user ID
    authentik_token = db.Column(db.Text)        # Store Authentik access token
    authentik_refresh_token = db.Column(db.Text) # Store Authentik refresh token
    
    # OAuth provider info - Dropbox
    dropbox_id = db.Column(db.String(100))      # Dropbox user ID
    dropbox_token = db.Column(db.Text)          # Store Dropbox access token
    dropbox_refresh_token = db.Column(db.Text)  # Store Dropbox refresh token
    dropbox_token_expiry = db.Column(db.DateTime) # Dropbox token expiration
    dropbox_export_path = db.Column(db.String(255), default='/QuizzicalBeats')  # User's preferred Dropbox export folder
    
    # User preferences
    intro_mp3 = db.Column(db.String(255))      # Custom intro MP3 path
    outro_mp3 = db.Column(db.String(255))      # Custom outro MP3 path
    replay_mp3 = db.Column(db.String(255))     # Custom replay MP3 path
    
    # Relationships
    roles = db.relationship('Role', secondary=user_roles, backref=db.backref('users', lazy='dynamic'))
    preferences = db.relationship('UserPreferences', uselist=False, back_populates='user')
    
    @property
    def password(self):
        """Password getter should raise an error"""
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self, password):
        """Hash and store the password"""
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        """Check if provided password matches the hash"""
        if not self.password_hash:
            return False
        return check_password_hash(self.password_hash, password)
        
    def set_token(self):
        """Generate a unique token for the user"""
        self.reset_token = str(uuid.uuid4())
        return self.reset_token
        
    def has_role(self, role_name):
        """Check if user has a specific role"""
        return any(role.name == role_name for role in self.roles)

    def is_admin_by_role(self):
        """Check if user is an admin via role assignment"""
        return self.has_role('admin')
        
    def __repr__(self):
        return f"User(id={self.id}, username='{self.username}', email='{self.email}')"


class Tag(db.Model):
    """
    Tag model for storing song tags
    """
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), unique=True, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    
    # Define relationship to songs via SongTag
    songs = db.relationship('Song', secondary='song_tag', back_populates='tags')
    
    def __repr__(self):
        return f"Tag(id={self.id}, name='{self.name}')"


class SongTag(db.Model):
    """
    Association table for Song-Tag many-to-many relationship
    """
    __tablename__ = 'song_tag'
    
    song_id = db.Column(db.Integer, db.ForeignKey('song.id', ondelete='CASCADE'), primary_key=True)
    tag_id = db.Column(db.Integer, db.ForeignKey('tag.id', ondelete='CASCADE'), primary_key=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    
    def __repr__(self):
        return f"SongTag(song_id={self.song_id}, tag_id={self.tag_id})"


class Song(db.Model):
    """
    Song model for storing song details from Spotify and Deezer
    """
    id = db.Column(db.Integer, primary_key=True)
    spotify_id = db.Column(db.String(100), unique=True, nullable=True)
    deezer_id = db.Column(db.Integer, unique=True, nullable=True)
    isrc = db.Column(db.String(20), index=True, nullable=True)  # Add ISRC code
    title = db.Column(db.String(200), nullable=False)
    artist = db.Column(db.String(200), nullable=False)
    album_name = db.Column(db.String(200), nullable=True)  # Album name
    genre = db.Column(db.String(100))
    year = db.Column(db.Integer)
    preview_url = db.Column(db.String(500))  # Increased length for longer URLs - primary preview URL
    cover_url = db.Column(db.String(500))    # Increased length - primary cover URL
    spotify_preview_url = db.Column(db.String(500), nullable=True)  # Spotify-specific preview
    deezer_preview_url = db.Column(db.String(500), nullable=True)   # Deezer-specific preview
    apple_preview_url = db.Column(db.String(500), nullable=True)    # Apple Music preview
    youtube_preview_url = db.Column(db.String(500), nullable=True)  # YouTube preview
    spotify_cover_url = db.Column(db.String(500), nullable=True)    # Spotify cover
    deezer_cover_url = db.Column(db.String(500), nullable=True)     # Deezer cover
    apple_cover_url = db.Column(db.String(500), nullable=True)      # Apple Music cover
    popularity = db.Column(db.Integer)
    used_count = db.Column(db.Integer, default=0)
    source = db.Column(db.String(20), default='spotify')  # 'spotify', 'deezer', or 'acrcloud'
    import_date = db.Column(db.DateTime, default=datetime.utcnow)  # Add import date
    added_at = db.Column(db.DateTime, default=datetime.utcnow)
    last_used = db.Column(db.DateTime)
    metadata_sources = db.Column(db.String(500), nullable=True)  # Comma-separated list of metadata sources
    
    # Spotify Audio Features
    acousticness = db.Column(db.Float, nullable=True)  # Confidence of track being acoustic (0.0 to 1.0)
    danceability = db.Column(db.Float, nullable=True)  # How suitable for dancing (0.0 to 1.0)
    energy = db.Column(db.Float, nullable=True)        # Intensity and activity measure (0.0 to 1.0)
    instrumentalness = db.Column(db.Float, nullable=True)  # Predicts if track has no vocals (0.0 to 1.0) 
    key = db.Column(db.Integer, nullable=True)         # Key of the track (standard Pitch Class notation, -1 = no key)
    liveness = db.Column(db.Float, nullable=True)      # Presence of audience in recording (0.0 to 1.0)
    loudness = db.Column(db.Float, nullable=True)      # Overall loudness in dB (-60 to 0 typically)
    mode = db.Column(db.Integer, nullable=True)        # Modality - major (1) or minor (0)
    speechiness = db.Column(db.Float, nullable=True)   # Presence of spoken words (0.0 to 1.0)
    tempo = db.Column(db.Float, nullable=True)         # Estimated tempo in BPM
    time_signature = db.Column(db.Integer, nullable=True)  # Estimated time signature (3 to 7 representing 3/4 to 7/4)
    valence = db.Column(db.Float, nullable=True)       # Musical positiveness (0.0 to 1.0)
    duration_ms = db.Column(db.Integer, nullable=True) # Duration of track in milliseconds
    analysis_url = db.Column(db.String(500), nullable=True)  # URL to access full audio analysis
    
    # Additional properties as JSON strings
    additional_data = db.Column(db.Text, nullable=True)  # Store additional data as JSON
    
    # Relationship with tags
    tags = db.relationship('Tag', secondary='song_tag', back_populates='songs')

    def __repr__(self):
        return (
            f"Song('{self.id}', '{self.title}', '{self.artist}', "
            f"'{self.genre}', '{self.year}', source='{self.source}', "
            f"isrc='{self.isrc}', spotify_id='{self.spotify_id}', deezer_id='{self.deezer_id}')"
        )
    
    def to_dict(self):
        return {
            'id': self.id,
            'title': self.title,
            'artist': self.artist,
            'album_name': self.album_name,
            'cover_url': self.cover_url,
            'preview_url': self.preview_url,
            'spotify_id': self.spotify_id,
            'deezer_id': self.deezer_id,
            'isrc': self.isrc,
            'year': self.year,
            'genre': self.genre,
            'popularity': self.popularity,
            'used_count': self.used_count,
            'last_used': self.last_used.strftime('%Y-%m-%d %H:%M:%S') if self.last_used else None,
            'metadata_sources': self.metadata_sources,
            'tags': [{'id': tag.id, 'name': tag.name} for tag in self.tags],
            # Include audio features in the dictionary
            'acousticness': self.acousticness,
            'danceability': self.danceability,
            'energy': self.energy,
            'instrumentalness': self.instrumentalness,
            'key': self.key,
            'liveness': self.liveness,
            'loudness': self.loudness,
            'mode': self.mode,
            'speechiness': self.speechiness,
            'tempo': self.tempo,
            'time_signature': self.time_signature,
            'valence': self.valence,
            'duration_ms': self.duration_ms
        }


class Round(db.Model):
    """
    Round model for storing quiz rounds and their associated songs
    """
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(200), nullable=True)  # Optional name for the round
    round_type = db.Column(db.String(50), nullable=False)
    round_criteria_used = db.Column(db.String(500), nullable=False)
    songs = db.Column(db.Text, nullable=False)  # JSON string of song IDs in order
    genre = db.Column(db.String(100))
    decade = db.Column(db.String(10))
    tag = db.Column(db.String(50))
    created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
    updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow)
    mp3_generated = db.Column(db.Boolean, default=False)  # Track if MP3 has been generated
    pdf_generated = db.Column(db.Boolean, default=False)  # Track if PDF has been generated
    last_generated_at = db.Column(db.DateTime, nullable=True)  # When files were last generated

    def __repr__(self):
        return (
            f"Round('{self.name or self.id}', '{self.round_type}', '{self.round_criteria_used}', "
            f"'{self.songs}', '{self.created_at}')"
        )

    @property
    def song_list(self):
        """
        Returns a list of Song objects associated with this round
        """
        song_ids = self.songs.split(',')
        return Song.query.filter(Song.id.in_(song_ids)).all()
        
    def reset_generated_status(self):
        """
        Reset the MP3 and PDF generated flags when a round is modified
        """
        self.mp3_generated = False
        self.pdf_generated = False


class RoundExport(db.Model):
    """
    Model to track round exports to various destinations (Dropbox, email, etc.)
    """
    id = db.Column(db.Integer, primary_key=True)
    round_id = db.Column(db.Integer, db.ForeignKey('round.id', ondelete='CASCADE'), nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='SET NULL'), nullable=True)
    export_type = db.Column(db.String(20), nullable=False)  # 'dropbox', 'email', etc.
    timestamp = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
    destination = db.Column(db.String(500), nullable=True)  # Path, email address, etc.
    include_mp3s = db.Column(db.Boolean, default=False)  # Whether MP3s were included
    status = db.Column(db.String(20), default='success')  # 'success', 'failed'
    error_message = db.Column(db.Text, nullable=True)  # Error details if failed
    
    # Relationships
    round = db.relationship('Round', backref=db.backref('exports', lazy='dynamic'))
    user = db.relationship('User', backref=db.backref('exports', lazy='dynamic'))
    
    def __repr__(self):
        return f"RoundExport(id={self.id}, round_id={self.round_id}, type='{self.export_type}', timestamp='{self.timestamp}')"


class SystemSetting(db.Model):
    __tablename__ = 'system_settings'
    id = db.Column(db.Integer, primary_key=True)
    key = db.Column(db.String(64), unique=True, nullable=False)
    value = db.Column(db.Text, nullable=True)

    @staticmethod
    def get(key, default=None):
        setting = SystemSetting.query.filter_by(key=key).first()
        return setting.value if setting else default

    @staticmethod
    def set(key, value):
        setting = SystemSetting.query.filter_by(key=key).first()
        if not setting:
            setting = SystemSetting(key=key, value=value)
            db.session.add(setting)
        else:
            setting.value = value
        db.session.commit()

    @staticmethod
    def all_settings():
        return {s.key: s.value for s in SystemSetting.query.all()}


class ImportJobRecord(db.Model):
    """
    Database model for tracking import jobs
    """
    id = db.Column(db.Integer, primary_key=True)
    service_name = db.Column(db.String(50), nullable=False)
    item_type = db.Column(db.String(20), nullable=False)
    item_id = db.Column(db.String(255), nullable=False)
    priority = db.Column(db.Integer, nullable=False, default=10)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    status = db.Column(db.String(20), default='pending')  # pending, processing, completed, failed
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    started_at = db.Column(db.DateTime)
    completed_at = db.Column(db.DateTime)
    error_message = db.Column(db.Text)
    imported_count = db.Column(db.Integer, default=0)
    skipped_count = db.Column(db.Integer, default=0)
    
    # Relationships
    user = db.relationship('User', backref=db.backref('import_jobs', lazy=True))
    
    def __repr__(self):
        return f"ImportJobRecord(id={self.id}, service={self.service_name}, type={self.item_type}, item_id={self.item_id}, status={self.status})"
    
    @property
    def duration(self):
        """Calculate the job duration in seconds."""
        if self.started_at and self.completed_at:
            return (self.completed_at - self.started_at).total_seconds()
        return None

    @property
    def item_url(self):
        """Generate a URL to the imported item based on service and type."""
        if self.service_name == 'spotify':
            if self.item_type == 'playlist':
                return f"https://open.spotify.com/playlist/{self.item_id}"
            elif self.item_type == 'album':
                return f"https://open.spotify.com/album/{self.item_id}"
            elif self.item_type == 'track':
                return f"https://open.spotify.com/track/{self.item_id}"
        elif self.service_name == 'deezer':
            if self.item_type == 'playlist':
                return f"https://www.deezer.com/playlist/{self.item_id}"
            elif self.item_type == 'album':
                return f"https://www.deezer.com/album/{self.item_id}"
            elif self.item_type == 'track':
                return f"https://www.deezer.com/track/{self.item_id}"
        return None

