from models.training import Training
from models.TrainingProjectDetails import TrainingProjectDetails
from models.TrainingSize import TrainingSize
from database.database import db

def push_yolox_exp_to_db(settings):
    """Save YOLOX settings to database"""
    normalized = dict(settings)

    # Map common frontend aliases to DB column names
    alias_map = {
        'act': 'activation',
        'nmsthre': 'nms_thre',
        'select_model': 'selected_model'
    }
    for a, b in alias_map.items():
        if a in normalized and b not in normalized:
            normalized[b] = normalized.pop(a)

    # Convert 'on'/'off' or 'true'/'false' strings to boolean for known boolean fields
    for bool_field in ['save_history_ckpt', 'ema', 'enable_mixup']:
        if bool_field in normalized:
            val = normalized[bool_field]
            if isinstance(val, str):
                normalized[bool_field] = val.lower() in ('1', 'true', 'on')
            else:
                normalized[bool_field] = bool(val)

    # Extract size arrays for separate TrainingSize table (3NF)
    size_arrays = {}
    for key in ['input_size', 'test_size', 'mosaic_scale', 'mixup_scale']:
        if key in normalized:
            if isinstance(normalized[key], str):
                parts = [p.strip() for p in normalized[key].split(',') if p.strip()]
                try:
                    arr = [float(p) for p in parts]
                except Exception:
                    arr = parts
                size_arrays[key] = arr[0] if len(arr) == 1 else (arr if isinstance(arr, list) else [arr])
            elif isinstance(normalized[key], list):
                size_arrays[key] = normalized[key]
            elif normalized[key] is not None:
                size_arrays[key] = [float(normalized[key])]
            # Remove from normalized dict since it won't be stored in training table
            del normalized[key]

    # Ensure we have a TrainingProjectDetails row for project_id
    project_id = normalized.get('project_id')
    if not project_id:
        raise Exception('Missing project_id in settings')
    details = TrainingProjectDetails.query.filter_by(project_id=project_id).first()
    if not details:
        raise Exception(f'TrainingProjectDetails not found for project_id {project_id}')
    normalized['project_details_id'] = details.id

    # Filter normalized to only columns that exist on the Training model
    valid_cols = {c.name: c for c in Training.__table__.columns}
    filtered = {}
    for k, v in normalized.items():
        if k in valid_cols:
            col_type = valid_cols[k].type.__class__.__name__
            # Try to coerce types for numeric/boolean columns
            try:
                if 'Integer' in col_type:
                    if v is None or v == '':
                        filtered[k] = None
                    else:
                        filtered[k] = int(float(v))
                elif 'Float' in col_type:
                    if v is None or v == '':
                        filtered[k] = None
                    else:
                        filtered[k] = float(v)
                elif 'Boolean' in col_type:
                    if isinstance(v, str):
                        filtered[k] = v.lower() in ('1', 'true', 'on')
                    else:
                        filtered[k] = bool(v)
                elif 'LargeBinary' in col_type:
                    # If a file path was passed, store its bytes; otherwise store raw bytes
                    if isinstance(v, str):
                        try:
                            filtered[k] = v.encode('utf-8')
                        except Exception:
                            filtered[k] = None
                    else:
                        filtered[k] = v
                else:
                    filtered[k] = v
            except Exception:
                # If conversion fails, just assign raw value
                filtered[k] = v

    # Create DB row
    training = Training(**filtered)
    db.session.add(training)
    db.session.flush()  # Get training.id
    
    # Save size arrays to TrainingSize table (3NF)
    for size_type, values in size_arrays.items():
        if values and isinstance(values, list):
            for order, value in enumerate(values):
                size_record = TrainingSize(
                    training_id=training.id,
                    size_type=size_type,
                    value_order=order,
                    value=float(value)
                )
                db.session.add(size_record)
    
    db.session.commit()

    return training
