from typing import Optional, List, Dict, Any
from sqlalchemy import or_, and_, select, case, nulls_first
from sqlalchemy.orm import Session
from .models import AppSetting

import logging
logger = logging.getLogger(__name__)

from decimal import Decimal
def convert_decimals(obj: Any) -> Any:
    """
    Recursively converts Decimal objects to floats for JSON serialization.
    Handles nested structures and None values.
    """
    if obj is None:
        return None
    if isinstance(obj, list):
        return [convert_decimals(i) for i in obj]
    elif isinstance(obj, dict):
        return {k: convert_decimals(v) for k, v in obj.items()}
    elif isinstance(obj, Decimal):
        return float(obj)
    return obj

def deep_merge(dict1: Dict[str, Any], dict2: Dict[str, Any]) -> Dict[str, Any]:
    """
    Recursively merges dict2 into dict1.
    """
    for key, value in dict2.items():
        if key in dict1 and isinstance(dict1[key], dict) and isinstance(value, dict):
            deep_merge(dict1[key], value)
        else:
            dict1[key] = value
    return dict1

class AppSettingsService:
    """
    Service to manage hierarchical configuration overrides.
    Hierarchy: System (Global) -> Tenant -> Company.
    """

    @staticmethod
    def get_resolved_settings(db: Session, tenant_id: int, company_id: int) -> Dict[str, Any]:
        """
        Retrieves settings from all levels (Global -> Tenant -> Company) and merges them.
        Treats 0 as None for hierarchy resolution.
        """
        # Normalize 0 to None for context-less lookups
        t_id = tenant_id if tenant_id and tenant_id > 0 else None
        c_id = company_id if company_id and company_id > 0 else None

        stmt = (
            select(AppSetting)
            .where(
                or_(
                    and_(AppSetting.tenant_id.is_(None), AppSetting.company_id.is_(None)),
                    and_(AppSetting.tenant_id == t_id, AppSetting.company_id.is_(None)) if t_id else False,
                    and_(AppSetting.tenant_id == t_id, AppSetting.company_id == c_id) if t_id and c_id else False,
                )
            )
            .order_by(
                # Ensure the order is: Global (1) -> Tenant (2) -> Company (3)
                case(
                    (and_(AppSetting.tenant_id.is_(None), AppSetting.company_id.is_(None)), 1),
                    (and_(AppSetting.tenant_id.is_not(None), AppSetting.company_id.is_(None)), 2),
                    (and_(AppSetting.tenant_id.is_not(None), AppSetting.company_id.is_not(None)), 3),
                    else_=4
                ).asc()
            )
        )
        
        results = db.execute(stmt).scalars().all()
        logger.info(f"RESOLVE: Found {len(results)} rows for Hierarchy")
        
        resolved_settings = {}
        for row in results:
            level_name = "GLOBAL" if not row.tenant_id else ("TENANT" if not row.company_id else "COMPANY")
            
            # Use safe data logging to avoid crashes
            settings_data = row.settings if row.settings else {}
            keys = list(settings_data.keys()) if isinstance(settings_data, dict) else "not-a-dict"
            logger.info(f"RESOLVE: Merging {level_name} layer with keys: {keys}")
            
            if isinstance(settings_data, dict):
                deep_merge(resolved_settings, settings_data)
        
        return resolved_settings

    @staticmethod
    def upsert_settings(db: Session, tenant_id: Optional[int], company_id: Optional[int], settings: Dict[str, Any]) -> AppSetting:
        """
        Inserts or updates settings for a specific level.
        Treats 0 as None.
        """
        # Normalize IDs: 0 or None means Global context at that level
        tenant_id = tenant_id if tenant_id and tenant_id > 0 else None
        company_id = company_id if company_id and company_id > 0 else None

        level_label = f"level(tenant={tenant_id}, company={company_id})"
        logger.info(f"UPSERT Settings START: {level_label}, keys_to_update={list(settings.keys())}")
        
        # Ensure all Decimals are converted to floats before deep merge and save
        settings = convert_decimals(settings)
        
        stmt = select(AppSetting).where(
            and_(
                AppSetting.tenant_id == tenant_id if tenant_id is not None else AppSetting.tenant_id.is_(None),
                AppSetting.company_id == company_id if company_id is not None else AppSetting.company_id.is_(None)
            )
        )
        
        existing = db.execute(stmt).scalar_one_or_none()
        
        if existing:
            # Deep merge at the root level (merging categories like ssisgen, ssismex, etc.)
            logger.info(f"UPSERT: Updating existing row ID={existing.id}")
            # Create a shallow copy of the top-level dict to ensure SQLAlchemy sees a new reference
            new_settings = dict(existing.settings) if existing.settings else {}
            
            # Detailed logging of what's changing
            for cat, data in settings.items():
                old_keys = list(new_settings.get(cat, {}).keys())
                new_keys = list(data.keys()) if isinstance(data, dict) else []
                logger.info(f"UPSERT: Merging category [{cat}]. Old keys: {old_keys}, New keys to merge/overwrite: {new_keys}")
            
            logger.info(f"UPSERT: Merging {len(settings)} top-level categories into existing row.")
            deep_merge(new_settings, settings)
            
            # Second pass of conversion (merged results might still have Decimals if original row had them)
            existing.settings = convert_decimals(new_settings)
            
            from sqlalchemy.orm.attributes import flag_modified
            flag_modified(existing, "settings")
            logger.info(f"UPSERT: Row updated and flagged as modified. Fields in ssisgen root: {list(new_settings.get('ssisgen', {}).keys())[:10]}...")
        else:
            logger.info(f"UPSERT: Creating NEW row for {level_label}")
            existing = AppSetting(
                tenant_id=tenant_id,
                company_id=company_id,
                settings=settings
            )
            db.add(existing)
        
        try:
            db.commit()
            db.refresh(existing)
            logger.info(f"UPSERT SUCCESS: Row ID={existing.id}, Final Settings Hash Keys={list(existing.settings.keys())}")
        except Exception as e:
            db.rollback()
            logger.error(f"UPSERT FAILED: {str(e)}")
            raise e
            
        return existing
