"""
Database models for MHA Flow Production System
"""
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
import json

db = SQLAlchemy()


class User(UserMixin, db.Model):
    """User model for authentication"""
    __tablename__ = 'users'
    
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False, index=True)
    email = db.Column(db.String(120), unique=True, nullable=False, index=True)
    password_hash = db.Column(db.String(255), nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    last_login = db.Column(db.DateTime)
    is_active = db.Column(db.Boolean, default=True)
    is_admin = db.Column(db.Boolean, default=False)
    
    # Relationships
    optimization_results = db.relationship('OptimizationResult', backref='user', lazy='dynamic', cascade='all, delete-orphan')
    custom_algorithms = db.relationship('CustomAlgorithm', foreign_keys='CustomAlgorithm.user_id', backref='user', lazy='dynamic', cascade='all, delete-orphan')
    
    def set_password(self, password):
        """Hash and set password"""
        self.password_hash = generate_password_hash(password)
    
    def check_password(self, password):
        """Check password against hash"""
        return check_password_hash(self.password_hash, password)
    
    def update_last_login(self):
        """Update last login timestamp"""
        self.last_login = datetime.utcnow()
        db.session.commit()
    
    def __repr__(self):
        return f'<User {self.username}>'


class OptimizationResult(db.Model):
    """Store optimization results"""
    __tablename__ = 'optimization_results'
    
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True)
    algorithm_name = db.Column(db.String(100), nullable=False, index=True)
    objective_function = db.Column(db.String(100), nullable=False)
    
    # Optimization parameters
    population_size = db.Column(db.Integer)
    max_iterations = db.Column(db.Integer)
    dimensions = db.Column(db.Integer)
    lower_bound = db.Column(db.Float)
    upper_bound = db.Column(db.Float)
    
    # Results
    best_fitness = db.Column(db.Float, nullable=True)
    best_position = db.Column(db.Text)  # JSON stored as text
    execution_time = db.Column(db.Float)  # seconds
    convergence_data = db.Column(db.Text)  # JSON stored as text
    
    # Metadata
    created_at = db.Column(db.DateTime, default=datetime.utcnow, index=True)
    status = db.Column(db.String(20), default='completed')  # completed, failed, running
    error_message = db.Column(db.Text)
    
    def set_best_position(self, position):
        """Convert numpy array to JSON string"""
        if isinstance(position, list):
            self.best_position = json.dumps(position)
        else:
            self.best_position = json.dumps(position.tolist())
    
    def get_best_position(self):
        """Convert JSON string back to list"""
        return json.loads(self.best_position) if self.best_position else []
    
    def set_convergence_data(self, data):
        """Store convergence history as JSON"""
        if isinstance(data, list):
            self.convergence_data = json.dumps(data)
        else:
            self.convergence_data = json.dumps(data.tolist())
    
    def get_convergence_data(self):
        """Retrieve convergence history"""
        return json.loads(self.convergence_data) if self.convergence_data else []
    
    def to_dict(self):
        """Convert to dictionary for API responses"""
        return {
            'id': self.id,
            'algorithm_name': self.algorithm_name,
            'objective_function': self.objective_function,
            'population_size': self.population_size,
            'max_iterations': self.max_iterations,
            'dimensions': self.dimensions,
            'best_fitness': self.best_fitness,
            'best_position': self.get_best_position(),
            'execution_time': self.execution_time,
            'convergence_data': self.get_convergence_data(),
            'created_at': self.created_at.isoformat() if self.created_at else None,
            'status': self.status
        }
    
    def __repr__(self):
        return f'<OptimizationResult {self.algorithm_name} - {self.best_fitness:.6f}>'


class CustomAlgorithm(db.Model):
    """Store user-uploaded custom algorithms"""
    __tablename__ = 'custom_algorithms'
    
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True)
    algorithm_name = db.Column(db.String(100), nullable=False)
    class_name = db.Column(db.String(100), nullable=False)
    filename = db.Column(db.String(200), nullable=False)
    file_content = db.Column(db.Text, nullable=False)
    
    # Metadata
    description = db.Column(db.Text)
    category = db.Column(db.String(50), default='custom')
    is_validated = db.Column(db.Boolean, default=False)
    validation_message = db.Column(db.Text)
    upload_date = db.Column(db.DateTime, default=datetime.utcnow, index=True)
    last_used = db.Column(db.DateTime)
    usage_count = db.Column(db.Integer, default=0)
    
    # Variable mapping (JSON: {custom_param: standard_param})
    parameter_mapping = db.Column(db.Text)  # JSON
    
    # GitHub Integration - for admin review workflow
    status = db.Column(db.String(20), default='pending')  # pending, approved, rejected
    github_branch = db.Column(db.String(200))
    github_pr_number = db.Column(db.Integer)
    pr_url = db.Column(db.String(500))
    admin_notes = db.Column(db.Text)
    reviewed_at = db.Column(db.DateTime)
    reviewed_by = db.Column(db.Integer, db.ForeignKey('users.id'))
    
    # Contributor credit tracking
    contributor_name = db.Column(db.String(200))   # Display name for credits
    contributor_email = db.Column(db.String(200))
    version_included = db.Column(db.String(20))     # Library version where included (e.g. "2.1.0")
    credit_acknowledged = db.Column(db.Boolean, default=False)
    local_algo_id = db.Column(db.String(200))       # ID in local registry for personal use
    
    def set_parameter_mapping(self, mapping):
        """Store parameter mapping as JSON"""
        self.parameter_mapping = json.dumps(mapping)
    
    def get_parameter_mapping(self):
        """Retrieve parameter mapping from JSON"""
        return json.loads(self.parameter_mapping) if self.parameter_mapping else {}
    
    def increment_usage(self):
        """Track algorithm usage"""
        self.usage_count += 1
        self.last_used = datetime.utcnow()
        db.session.commit()
    
    def to_dict(self):
        """Convert to dictionary"""
        return {
            'id': self.id,
            'algorithm_name': self.algorithm_name,
            'class_name': self.class_name,
            'filename': self.filename,
            'description': self.description,
            'category': self.category,
            'is_validated': self.is_validated,
            'validation_message': self.validation_message,
            'upload_date': self.upload_date.isoformat() if self.upload_date else None,
            'usage_count': self.usage_count,
            'status': self.status,
            'github_branch': self.github_branch,
            'pr_number': self.github_pr_number,
            'pr_url': self.pr_url,
            'admin_notes': self.admin_notes,
            'parameter_mapping': self.get_parameter_mapping(),
            'contributor_name': self.contributor_name,
            'contributor_email': self.contributor_email,
            'version_included': self.version_included,
            'credit_acknowledged': self.credit_acknowledged,
            'local_algo_id': self.local_algo_id
        }
    
    def __repr__(self):
        return f'<CustomAlgorithm {self.algorithm_name} by {self.contributor_name}>'


class ComparisonSession(db.Model):
    """Store comparison results for multiple algorithms"""
    __tablename__ = 'comparison_sessions'
    
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True)
    session_name = db.Column(db.String(200))
    objective_function = db.Column(db.String(100), nullable=False)
    
    # Parameters (same for all algorithms in comparison)
    population_size = db.Column(db.Integer)
    max_iterations = db.Column(db.Integer)
    dimensions = db.Column(db.Integer)
    
    # Results summary stored as JSON
    algorithms_tested = db.Column(db.Text)  # JSON list of algorithm names
    results_summary = db.Column(db.Text)  # JSON with all results
    
    created_at = db.Column(db.DateTime, default=datetime.utcnow, index=True)
    
    def set_algorithms_tested(self, algorithms):
        """Store list of algorithms"""
        self.algorithms_tested = json.dumps(algorithms)
    
    def get_algorithms_tested(self):
        """Get list of algorithms"""
        return json.loads(self.algorithms_tested) if self.algorithms_tested else []
    
    def set_results_summary(self, results):
        """Store comparison results"""
        self.results_summary = json.dumps(results)
    
    def get_results_summary(self):
        """Get comparison results"""
        return json.loads(self.results_summary) if self.results_summary else {}
    
    def __repr__(self):
        return f'<ComparisonSession {self.session_name}>'


class SystemSettings(db.Model):
    """System-wide settings"""
    __tablename__ = 'system_settings'
    
    id = db.Column(db.Integer, primary_key=True)
    key = db.Column(db.String(100), unique=True, nullable=False, index=True)
    value = db.Column(db.Text)
    description = db.Column(db.Text)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    
    @staticmethod
    def get_setting(key, default=None):
        """Get setting value"""
        setting = SystemSettings.query.filter_by(key=key).first()
        return setting.value if setting else default
    
    @staticmethod
    def set_setting(key, value, description=None):
        """Set or update setting"""
        setting = SystemSettings.query.filter_by(key=key).first()
        if setting:
            setting.value = value
            if description:
                setting.description = description
        else:
            setting = SystemSettings(key=key, value=value, description=description)
            db.session.add(setting)
        db.session.commit()
    
    def __repr__(self):
        return f'<SystemSettings {self.key}={self.value}>'
