from sqlalchemy import create_engine, Column, Integer, String, Text, Date, DateTime, ForeignKey, Index, UniqueConstraint, text, Boolean
from sqlalchemy.orm import declarative_base, sessionmaker, relationship, scoped_session
from datetime import datetime
import os
import uuid
import bcrypt

# Load dotenv to get PostgreSQL connection string
try:
    from dotenv import load_dotenv
    load_dotenv()
except ImportError:
    pass

# Get PostgreSQL URI from environment or use default
POSTGRESQL_URI = os.environ.get('POSTGRESQL_URI', 'postgresql://songlyric_user:your_password@localhost:5432/church_songlyric')

# Validate connection string doesn't contain default password in production
if 'your_password' in POSTGRESQL_URI and os.environ.get('FLASK_ENV') == 'production':
    raise ValueError("SECURITY: Cannot use default password in production! Set POSTGRESQL_URI environment variable.")
# Optimized engine settings for production
engine = create_engine(
    POSTGRESQL_URI, 
    echo=False,  # Disable SQL logging for performance
    future=True, 
    pool_pre_ping=True,  # Verify connections before using
    pool_recycle=3600,  # Recycle connections after 1 hour
    pool_size=10,  # Connection pool size
    max_overflow=20,  # Max overflow connections
    pool_timeout=30,  # Timeout for getting connection from pool
    connect_args={
        'connect_timeout': 10,  # Connection timeout in seconds
        'options': '-c statement_timeout=60000'  # 60 second query timeout
    }
)
SessionLocal = scoped_session(sessionmaker(bind=engine, autoflush=False, autocommit=False, expire_on_commit=False))
Base = declarative_base()

class Profile(Base):
    __tablename__ = 'profiles'
    id = Column(String(255), primary_key=True)
    first_name = Column(String(255), default='')
    last_name = Column(String(255), default='')
    name = Column(String(255), default='', nullable=False)
    email = Column(String(255), default='')
    contact_number = Column(String(50), default='')
    notes = Column(Text, default='')
    default_key = Column(String(10), default='C')
    
    __table_args__ = (
        Index('idx_profile_name', 'name'),
    )

class Song(Base):
    __tablename__ = 'songs'
    id = Column(String(255), primary_key=True)
    title = Column(String(500), nullable=False)
    artist = Column(String(500), default='')
    band = Column(String(500), default='')
    singer = Column(String(500), default='')
    lyrics = Column(Text, default='')
    chords = Column(Text, default='')
    memo = Column(Text, default='')
    created_at = Column(Integer, default=lambda: int(datetime.now().timestamp()))
    updated_at = Column(Integer, default=lambda: int(datetime.now().timestamp()))
    
    __table_args__ = (
        Index('idx_song_title', 'title'),
        Index('idx_song_artist', 'artist'),
        Index('idx_song_band', 'band'),
    )

class Plan(Base):
    __tablename__ = 'plans'
    id = Column(String(255), primary_key=True)
    date = Column(String(50), nullable=False)
    profile_id = Column(String(255), ForeignKey('profiles.id', ondelete='SET NULL'))
    notes = Column(Text, default='')
    created_at = Column(Integer, default=lambda: int(datetime.now().timestamp()))
    
    __table_args__ = (
        Index('idx_plan_date', 'date'),
        Index('idx_plan_profile', 'profile_id'),
    )

class PlanSong(Base):
    __tablename__ = 'plan_songs'
    id = Column(String(255), primary_key=True, default=lambda: str(uuid.uuid4()))
    plan_id = Column(String(255), ForeignKey('plans.id', ondelete='CASCADE'))
    song_id = Column(String(255), ForeignKey('songs.id', ondelete='CASCADE'))
    order_index = Column(Integer, default=0)
    
    __table_args__ = (
        UniqueConstraint('plan_id', 'song_id', name='uq_plan_song'),
        Index('idx_plan_songs_plan', 'plan_id'),
        Index('idx_plan_songs_order', 'plan_id', 'order_index'),
    )

class ProfileSong(Base):
    __tablename__ = 'profile_songs'
    id = Column(String(255), primary_key=True)
    profile_id = Column(String(255), ForeignKey('profiles.id', ondelete='CASCADE'))
    song_id = Column(String(255), ForeignKey('songs.id', ondelete='CASCADE'))
    
    __table_args__ = (
        UniqueConstraint('profile_id', 'song_id', name='uq_profile_song'),
        Index('idx_profile_songs_profile', 'profile_id'),
    )

class ProfileSongKey(Base):
    __tablename__ = 'profile_song_keys'
    id = Column(String(255), primary_key=True)
    profile_id = Column(String(255), ForeignKey('profiles.id', ondelete='CASCADE'))
    song_id = Column(String(255), ForeignKey('songs.id', ondelete='CASCADE'))
    song_key = Column(String(10), default='C')
    
    __table_args__ = (
        UniqueConstraint('profile_id', 'song_id', name='uq_profile_song_key'),
        Index('idx_profile_song_keys', 'profile_id', 'song_id'),
    )

class BiometricCredential(Base):
    __tablename__ = 'biometric_credentials'
    id = Column(String(255), primary_key=True, default=lambda: str(uuid.uuid4()))
    username = Column(String(255), nullable=False)  # Username for authentication (backwards compatibility)
    user_id = Column(String(255), ForeignKey('users.id'), nullable=True)  # Link to User table
    credential_id = Column(Text, nullable=False)  # Base64 encoded credential ID
    public_key = Column(Text, nullable=False)  # Base64 encoded public key
    device_name = Column(String(255), default='')  # User-friendly device name
    device_info = Column(Text, default='')  # Device information (browser, OS)
    device_fingerprint = Column(Text, default='')  # Device fingerprint for mobile compatibility
    enabled = Column(Integer, default=1)  # 1=enabled, 0=disabled
    created_at = Column(DateTime, default=datetime.now)
    last_used = Column(DateTime, default=datetime.now)
    
    # Relationship to User
    user = relationship('User', backref='biometric_credentials', foreign_keys=[user_id])
    
    __table_args__ = (
        UniqueConstraint('credential_id', name='uq_credential_id'),
        Index('idx_biometric_username', 'username'),
        Index('idx_biometric_user_id', 'user_id'),
        Index('idx_biometric_enabled', 'username', 'enabled'),
    )

class User(Base):
    __tablename__ = 'users'
    id = Column(String(255), primary_key=True, default=lambda: str(uuid.uuid4()))
    username = Column(String(255), nullable=False, unique=True)
    password_hash = Column(String(255), nullable=False)  # bcrypt hash (60 chars)
    role = Column(String(100), default='viewer')  # admin, worship_leader, bass_guitar, piano, acoustic, viewer
    permissions = Column(Text, default='view')  # Comma-separated: view,edit,modify,settings
    active = Column(Boolean, default=True)
    created_at = Column(DateTime, default=datetime.now)
    updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
    last_login = Column(DateTime)
    
    __table_args__ = (
        Index('idx_user_username', 'username'),
        Index('idx_user_role', 'role'),
        Index('idx_user_active', 'active'),
    )
    
    def set_password(self, password):
        """Hash password using bcrypt with salt"""
        salt = bcrypt.gensalt(rounds=12)  # 12 rounds provides good balance
        self.password_hash = bcrypt.hashpw(password.encode('utf-8'), salt).decode('utf-8')
    
    def check_password(self, password):
        """Verify password against bcrypt hash"""
        try:
            return bcrypt.checkpw(password.encode('utf-8'), self.password_hash.encode('utf-8'))
        except (ValueError, AttributeError):
            return False
    
    def has_permission(self, permission):
        """Check if user has a specific permission"""
        if not self.active:
            return False
        if self.role == 'admin':
            return True  # Admin has all permissions
        perms = [p.strip() for p in (self.permissions or '').split(',')]
        return permission in perms
    
    def get_permissions_list(self):
        """Get list of permissions"""
        if not self.permissions:
            return []
        return [p.strip() for p in self.permissions.split(',') if p.strip()]


def init_db():
    """Initialize database - create tables and apply optimizations"""
    Base.metadata.create_all(engine)
    apply_optimizations()

def apply_optimizations():
    """Apply database optimizations (indexes) if they don't exist"""
    try:
        with engine.begin() as conn:
            # Try to add recommended indexes (will silently fail if no permissions)
            try:
                conn.execute(text("CREATE INDEX IF NOT EXISTS idx_song_title ON songs(title)"))
                conn.execute(text("CREATE INDEX IF NOT EXISTS idx_song_artist ON songs(artist)"))
                conn.execute(text("CREATE INDEX IF NOT EXISTS idx_song_band ON songs(band)"))
                conn.execute(text("CREATE INDEX IF NOT EXISTS idx_plan_date ON plans(date)"))
                conn.execute(text("CREATE INDEX IF NOT EXISTS idx_plan_profile ON plans(profile_id)"))
                conn.execute(text("CREATE INDEX IF NOT EXISTS idx_profile_name ON profiles(name)"))
                conn.execute(text("CREATE INDEX IF NOT EXISTS idx_plan_songs_order ON plan_songs(plan_id, order_index)"))
            except Exception:
                # Silently ignore permission errors - indexes will be added by DBA later
                pass
    except Exception:
        pass

def get_db():
    """Create a new database session"""
    return SessionLocal()

def close_db(db):
    """Close database session"""
    if db:
        db.close()

# Song helper functions
def get_all_songs(db):
    return db.query(Song).all()

def get_song_by_id(db, song_id):
    return db.query(Song).filter(Song.id == song_id).first()

def create_song(db, title, artist='', band='', singer='', lyrics='', chords=''):
    song_id = str(uuid.uuid4())
    song = Song(id=song_id, title=title, artist=artist, band=band, singer=singer, lyrics=lyrics, chords=chords)
    db.add(song)
    db.commit()
    db.refresh(song)
    return song

def update_song(db, song_id, **kwargs):
    song = get_song_by_id(db, song_id)
    if not song:
        return None
    for key, value in kwargs.items():
        if hasattr(song, key):
            setattr(song, key, value)
    song.updated_at = datetime.now()
    db.commit()
    db.refresh(song)
    return song

def delete_song(db, song_id):
    song = get_song_by_id(db, song_id)
    if song:
        db.delete(song)
        db.commit()
        return True
    return False

def search_songs(db, query):
    search = f"%{query}%"
    return db.query(Song).filter(
        (Song.title.ilike(search)) |
        (Song.artist.ilike(search)) |
        (Song.band.ilike(search)) |
        (Song.singer.ilike(search)) |
        (Song.lyrics.ilike(search))
    ).all()

# Profile helper functions
def get_all_profiles(db):
    return db.query(Profile).all()

def get_profile_by_id(db, profile_id):
    return db.query(Profile).filter(Profile.id == profile_id).first()

# Plan helper functions
def get_all_plans(db):
    return db.query(Plan).all()

def get_plan_by_id(db, plan_id):
    return db.query(Plan).filter(Plan.id == plan_id).first()

