"""
Database migration system for Fixture Manager
Handles automatic schema updates and versioning
"""

import logging
import os
from datetime import datetime
from typing import List, Dict, Any
from app import db
from sqlalchemy import text, inspect
from sqlalchemy.exc import SQLAlchemyError

logger = logging.getLogger(__name__)

class DatabaseVersion(db.Model):
    """Track database schema versions"""
    __tablename__ = 'database_versions'
    
    id = db.Column(db.Integer, primary_key=True)
    version = db.Column(db.String(50), unique=True, nullable=False)
    description = db.Column(db.String(255), nullable=False)
    applied_at = db.Column(db.DateTime, default=datetime.utcnow)
    
    def __repr__(self):
        return f'<DatabaseVersion {self.version}: {self.description}>'

class Migration:
    """Base migration class"""
    
    def __init__(self, version: str, description: str):
        self.version = version
        self.description = description
    
    def up(self):
        """Apply the migration"""
        raise NotImplementedError("Subclasses must implement up() method")
    
    def down(self):
        """Rollback the migration (optional)"""
        pass
    
    def can_rollback(self) -> bool:
        """Check if migration can be rolled back"""
        return False

class Migration_001_RemoveFixtureIdUnique(Migration):
    """Remove unique constraint from fixture_id in matches table"""
    
    def __init__(self):
        super().__init__("001", "Remove unique constraint from fixture_id in matches table")
    
    def up(self):
        """Remove unique constraint from fixture_id"""
        try:
            # Check if we're using MySQL or SQLite
            inspector = inspect(db.engine)
            
            # Get current constraints
            constraints = inspector.get_unique_constraints('matches')
            indexes = inspector.get_indexes('matches')
            
            # Find fixture_id unique constraint/index
            fixture_id_constraint = None
            fixture_id_index = None
            
            for constraint in constraints:
                if 'fixture_id' in constraint['column_names']:
                    fixture_id_constraint = constraint['name']
                    break
            
            for index in indexes:
                if 'fixture_id' in index['column_names'] and index['unique']:
                    fixture_id_index = index['name']
                    break
            
            # Drop unique constraint if it exists
            if fixture_id_constraint:
                try:
                    with db.engine.connect() as conn:
                        conn.execute(text(f"ALTER TABLE matches DROP CONSTRAINT {fixture_id_constraint}"))
                        conn.commit()
                    logger.info(f"Dropped unique constraint {fixture_id_constraint} from matches.fixture_id")
                except Exception as e:
                    logger.warning(f"Could not drop constraint {fixture_id_constraint}: {str(e)}")
            
            # Drop unique index if it exists
            if fixture_id_index:
                try:
                    with db.engine.connect() as conn:
                        conn.execute(text(f"DROP INDEX {fixture_id_index}"))
                        conn.commit()
                    logger.info(f"Dropped unique index {fixture_id_index} from matches.fixture_id")
                except Exception as e:
                    logger.warning(f"Could not drop index {fixture_id_index}: {str(e)}")
            
            # Create regular index for performance (non-unique)
            try:
                with db.engine.connect() as conn:
                    conn.execute(text("CREATE INDEX IF NOT EXISTS idx_matches_fixture_id ON matches(fixture_id)"))
                    conn.commit()
                logger.info("Created non-unique index on matches.fixture_id")
            except Exception as e:
                logger.warning(f"Could not create index on fixture_id: {str(e)}")
            
            return True
            
        except Exception as e:
            logger.error(f"Migration 001 failed: {str(e)}")
            raise
    
    def can_rollback(self) -> bool:
        return False  # Cannot safely rollback unique constraint removal

class Migration_002_AddDatabaseVersionTable(Migration):
    """Ensure database version tracking table exists"""
    
    def __init__(self):
        super().__init__("002", "Create database version tracking table")
    
    def up(self):
        """Create database version table if it doesn't exist"""
        try:
            # This migration is handled by the migration system itself
            # when it creates the DatabaseVersion table
            return True
        except Exception as e:
            logger.error(f"Migration 002 failed: {str(e)}")
            raise

class Migration_003_CreateAPITokensTable(Migration):
    """Create API tokens table for external authentication"""
    
    def __init__(self):
        super().__init__("003", "Create API tokens table for external authentication")
    
    def up(self):
        """Create api_tokens table"""
        try:
            # Check if table already exists
            inspector = inspect(db.engine)
            if 'api_tokens' in inspector.get_table_names():
                logger.info("api_tokens table already exists, skipping creation")
                return True
            
            # Create the table using raw SQL to ensure compatibility
            create_table_sql = '''
                CREATE TABLE api_tokens (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    user_id INT NOT NULL,
                    name VARCHAR(255) NOT NULL,
                    token_hash VARCHAR(255) NOT NULL UNIQUE,
                    is_active BOOLEAN DEFAULT TRUE,
                    expires_at DATETIME NOT NULL,
                    last_used_at DATETIME NULL,
                    last_used_ip VARCHAR(45) NULL,
                    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
                    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                    INDEX idx_api_tokens_user_id (user_id),
                    INDEX idx_api_tokens_token_hash (token_hash),
                    INDEX idx_api_tokens_is_active (is_active),
                    INDEX idx_api_tokens_expires_at (expires_at),
                    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
            '''
            
            with db.engine.connect() as conn:
                conn.execute(text(create_table_sql))
                conn.commit()
            
            logger.info("Created api_tokens table successfully")
            return True
            
        except Exception as e:
            logger.error(f"Migration 003 failed: {str(e)}")
            raise
    
    def down(self):
        """Drop api_tokens table"""
        try:
            with db.engine.connect() as conn:
                conn.execute(text("DROP TABLE IF EXISTS api_tokens"))
                conn.commit()
            logger.info("Dropped api_tokens table")
            return True
        except Exception as e:
            logger.error(f"Rollback of migration 003 failed: {str(e)}")
            raise
    
    def can_rollback(self) -> bool:
        return True

class Migration_004_CreateSystemSettingsTable(Migration):
    """Create system settings table for persistent configuration"""
    
    def __init__(self):
        super().__init__("004", "Create system settings table for persistent configuration")
    
    def up(self):
        """Create system_settings table"""
        try:
            # Check if table already exists
            inspector = inspect(db.engine)
            if 'system_settings' in inspector.get_table_names():
                logger.info("system_settings table already exists, skipping creation")
                return True
            
            # Create the table using raw SQL to ensure compatibility
            create_table_sql = '''
                CREATE TABLE system_settings (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    setting_key VARCHAR(255) NOT NULL UNIQUE,
                    setting_value TEXT,
                    setting_type VARCHAR(50) DEFAULT 'string',
                    description TEXT,
                    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
                    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                    INDEX idx_system_settings_key (setting_key),
                    INDEX idx_system_settings_type (setting_type)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
            '''
            
            with db.engine.connect() as conn:
                conn.execute(text(create_table_sql))
                conn.commit()
            
            # Insert default settings
            default_settings_sql = '''
                INSERT INTO system_settings (setting_key, setting_value, setting_type, description) VALUES
                ('registration_enabled', 'false', 'boolean', 'Enable or disable user registration'),
                ('app_name', 'Fixture Manager', 'string', 'Application name'),
                ('maintenance_mode', 'false', 'boolean', 'Enable maintenance mode')
            '''
            
            with db.engine.connect() as conn:
                conn.execute(text(default_settings_sql))
                conn.commit()
            
            logger.info("Created system_settings table with default settings successfully")
            return True
            
        except Exception as e:
            logger.error(f"Migration 004 failed: {str(e)}")
            raise
    
    def down(self):
        """Drop system_settings table"""
        try:
            with db.engine.connect() as conn:
                conn.execute(text("DROP TABLE IF EXISTS system_settings"))
                conn.commit()
            logger.info("Dropped system_settings table")
            return True
        except Exception as e:
            logger.error(f"Rollback of migration 004 failed: {str(e)}")
            raise
    
    def can_rollback(self) -> bool:
        return True

class MigrationManager:
    """Manages database migrations and versioning"""
    
    def __init__(self):
        self.migrations = [
            Migration_002_AddDatabaseVersionTable(),
            Migration_001_RemoveFixtureIdUnique(),
            Migration_003_CreateAPITokensTable(),
            Migration_004_CreateSystemSettingsTable(),
        ]
    
    def ensure_version_table(self):
        """Ensure the database version table exists"""
        try:
            # Create all tables (this will create DatabaseVersion if it doesn't exist)
            db.create_all()
            logger.info("Database version table ensured")
        except Exception as e:
            logger.error(f"Failed to create version table: {str(e)}")
            raise
    
    def get_current_version(self) -> str:
        """Get the current database version"""
        try:
            latest_version = DatabaseVersion.query.order_by(DatabaseVersion.applied_at.desc()).first()
            return latest_version.version if latest_version else "000"
        except Exception as e:
            logger.warning(f"Could not get current version: {str(e)}")
            return "000"
    
    def get_applied_versions(self) -> List[str]:
        """Get list of applied migration versions"""
        try:
            versions = DatabaseVersion.query.order_by(DatabaseVersion.applied_at.asc()).all()
            return [v.version for v in versions]
        except Exception as e:
            logger.warning(f"Could not get applied versions: {str(e)}")
            return []
    
    def is_migration_applied(self, version: str) -> bool:
        """Check if a migration version has been applied"""
        try:
            return DatabaseVersion.query.filter_by(version=version).first() is not None
        except Exception as e:
            logger.warning(f"Could not check migration status for {version}: {str(e)}")
            return False
    
    def apply_migration(self, migration: Migration) -> bool:
        """Apply a single migration"""
        try:
            if self.is_migration_applied(migration.version):
                logger.info(f"Migration {migration.version} already applied, skipping")
                return True
            
            logger.info(f"Applying migration {migration.version}: {migration.description}")
            
            # Apply the migration
            migration.up()
            
            # Record the migration
            version_record = DatabaseVersion(
                version=migration.version,
                description=migration.description
            )
            db.session.add(version_record)
            db.session.commit()
            
            logger.info(f"Migration {migration.version} applied successfully")
            return True
            
        except Exception as e:
            db.session.rollback()
            logger.error(f"Migration {migration.version} failed: {str(e)}")
            raise
    
    def run_migrations(self) -> Dict[str, Any]:
        """Run all pending migrations"""
        try:
            # Ensure version table exists
            self.ensure_version_table()
            
            applied_versions = self.get_applied_versions()
            pending_migrations = []
            failed_migrations = []
            
            # Find pending migrations
            for migration in self.migrations:
                if migration.version not in applied_versions:
                    pending_migrations.append(migration)
            
            if not pending_migrations:
                logger.info("No pending migrations")
                return {
                    'status': 'success',
                    'message': 'Database is up to date',
                    'applied_count': 0,
                    'failed_count': 0
                }
            
            # Apply pending migrations
            applied_count = 0
            for migration in pending_migrations:
                try:
                    if self.apply_migration(migration):
                        applied_count += 1
                    else:
                        failed_migrations.append(migration.version)
                except Exception as e:
                    failed_migrations.append(migration.version)
                    logger.error(f"Failed to apply migration {migration.version}: {str(e)}")
            
            # Return results
            if failed_migrations:
                return {
                    'status': 'partial',
                    'message': f'Applied {applied_count} migrations, {len(failed_migrations)} failed',
                    'applied_count': applied_count,
                    'failed_count': len(failed_migrations),
                    'failed_migrations': failed_migrations
                }
            else:
                return {
                    'status': 'success',
                    'message': f'Successfully applied {applied_count} migrations',
                    'applied_count': applied_count,
                    'failed_count': 0
                }
                
        except Exception as e:
            logger.error(f"Migration system failed: {str(e)}")
            return {
                'status': 'error',
                'message': f'Migration system failed: {str(e)}',
                'applied_count': 0,
                'failed_count': 0
            }
    
    def get_migration_status(self) -> Dict[str, Any]:
        """Get current migration status"""
        try:
            self.ensure_version_table()
            
            current_version = self.get_current_version()
            applied_versions = self.get_applied_versions()
            
            all_versions = [m.version for m in self.migrations]
            pending_versions = [v for v in all_versions if v not in applied_versions]
            
            return {
                'current_version': current_version,
                'applied_versions': applied_versions,
                'pending_versions': pending_versions,
                'total_migrations': len(self.migrations),
                'applied_count': len(applied_versions),
                'pending_count': len(pending_versions)
            }
            
        except Exception as e:
            logger.error(f"Could not get migration status: {str(e)}")
            return {
                'error': str(e),
                'current_version': 'unknown',
                'applied_versions': [],
                'pending_versions': [],
                'total_migrations': 0,
                'applied_count': 0,
                'pending_count': 0
            }

# Global migration manager instance
migration_manager = MigrationManager()

def get_migration_manager():
    """Get migration manager instance"""
    return migration_manager

def run_migrations():
    """Run all pending migrations"""
    return migration_manager.run_migrations()

def get_migration_status():
    """Get migration status"""
    return migration_manager.get_migration_status()