o
    ג[i 7                     @   s^   d Z ddlZddlmZmZmZmZmZ ddl	m
Z
 ddlmZ ddlmZ G dd dZdS )	u  
Data Service Layer

This module handles loading and saving tabular data from MongoDB collections.
It provides the bridge between MongoDB documents and pandas DataFrames for UI editing.

Key responsibilities:
- Load collection documents as normalized DataFrames
- Handle schema normalization (missing fields → None)
- Convert ObjectId to string for UI display
- Track changes between original and edited data
- Apply changes back to MongoDB with user confirmation
    N)DictListAnyTupleOptional)ObjectId)datetime)JSONFlattenerc                   @   s"  e Zd ZdZdd Zdeeef deeef fddZde	eeef  de	e fd	d
Z
		ddedededejfddZ		ddededede	eeef  fddZdejdejdee	e e	e e	e f fddZdejdejdeeef fddZdedejdejdeeef fddZdS ) DataServicez
    Service for loading and saving collection data in tabular format.
    
    This service normalizes MongoDB documents into pandas DataFrames,
    tracks changes, and applies updates back to MongoDB.
    c                 C   s   || _ t | _dS )z
        Initialize data service with MongoDB connection.
        
        Args:
            mongo_service: MongoService instance
        N)mongor	   	flattener)selfmongo_service r   1/var/www/html/IGF-ODF-V3/services/data_service.py__init__   s   zDataService.__init__docreturnc                 C   sz   i }|  D ]4\}}t|trt|||< qt|tr"| ||< qt|tr,|||< qt|tr6|||< q|||< q|S )a   
        Normalize a MongoDB document for DataFrame display.
        
        Converts ObjectId to string and handles nested structures.
        
        Args:
            doc: MongoDB document
            
        Returns:
            Normalized document with ObjectId as string
        )items
isinstancer   strr   	isoformatdictlist)r   r   
normalizedkeyvaluer   r   r   _normalize_document(   s   






zDataService._normalize_document	documentsc                 C   sH   t  }|D ]	}||  qt|}d|v r"|d |dd |S )z
        Get union of all fields across documents.
        
        Args:
            documents: List of MongoDB documents
            
        Returns:
            Sorted list of unique field names
        _idr   )setupdatekeyssortedremoveinsert)r   r   
all_fieldsr   fieldsr   r   r   _get_all_fieldsH   s   

zDataService._get_all_fields  r   collection_namelimitskipc                    sb    j |||}|stjdgdS  fdd|D } |}tj||d}|t|d}|S )a  
        Load MongoDB collection as a normalized pandas DataFrame.
        
        All documents are normalized to have the same columns.
        Missing fields are filled with None.
        ObjectId fields are converted to strings.
        
        Args:
            collection_name: Name of the collection
            limit: Maximum number of documents to load
            skip: Number of documents to skip
            
        Returns:
            DataFrame with normalized documents
        r   )columnsc                    s   g | ]}  |qS r   )r   ).0r   r   r   r   
<listcomp>~   s    z<DataService.load_collection_as_dataframe.<locals>.<listcomp>N)r   sample_documentspd	DataFramer(   wherenotnull)r   r*   r+   r,   r   normalized_docsr&   dfr   r/   r   load_collection_as_dataframea   s   
z(DataService.load_collection_as_dataframe  c           
      C   s   | j |||}|sg S g }|D ]@}d|v r%t|d tr%t|d |d< | j|}| D ]\}}	t|	tr?t|	||< q/t|	trJ|		 ||< q/|
| q|S )a0  
        Load MongoDB records and flatten them for comparison with JSON records.
        
        This method:
        1. Loads documents from MongoDB
        2. Flattens each document using JSONFlattener
        3. Converts ObjectId to string
        4. Returns list of flattened dictionaries
        
        Args:
            collection_name: Name of the collection
            limit: Maximum number of documents to load
            skip: Number of documents to skip
            
        Returns:
            List of flattened document dictionaries
        r   )r   r1   r   r   r   r   flattenr   r   r   append)
r   r*   r+   r,   r   flattened_recordsr   	flattenedr   r   r   r   r   load_flattened_records   s    

z"DataService.load_flattened_recordsoriginal_df	edited_dfc                 C   s  g }g }g }t  }d|jv rt |d  t}t  }d|jv r,t |d  t}t|| }| D ]\}}	|	 }
dd |
 D }
|
	d}|rW|dksWt
|rc|
dd ||
 q6t||vru|
dd ||
 q6||d |k }|js|jd  }dd | D }d}i }|
 D ]#\}}|dkrq|	|}t
|rt
|rq||krd	}|||< q| D ]}|dkr||
vrd	}d||< q|r|||d
 q6|||fS )a  
        Identify new, updated, and deleted documents.
        
        Args:
            original_df: Original DataFrame from MongoDB
            edited_df: Edited DataFrame from UI
            
        Returns:
            Tuple of (new_docs, updated_docs, deleted_ids)
        r   c                 S       i | ]\}}t |r||qS r   r2   notnar.   kvr   r   r   
<dictcomp>        z1DataService._identify_changes.<locals>.<dictcomp> Nr   c                 S   rA   r   rB   rD   r   r   r   rG      rH   FT)r   updates)r    r-   dropnaastyper   r   iterrowsto_dictr   getr2   isnapopr;   emptyilocr"   )r   r?   r@   new_docsupdated_docsdeleted_idsoriginal_ids
edited_idsidxrowrow_dictdoc_idoriginal_roworiginal_dictchangedrJ   r   r   original_valuer   r   r   _identify_changes   s`   




zDataService._identify_changesc                 C   s0   |  ||\}}}t|t|t||||dS )z
        Preview changes between original and edited DataFrames.
        
        Args:
            original_df: Original DataFrame
            edited_df: Edited DataFrame
            
        Returns:
            Dictionary with change summary
        )	new_countupdated_countdeleted_countrT   rU   rV   )ra   len)r   r?   r@   rT   rU   rV   r   r   r   preview_changes  s   
zDataService.preview_changesc                 C   sD  | j |}|du rdddS | ||\}}}ddddg d}z|rPz||}	t|	j|d< W n tyO }
 z|d	 d
t|
  W Y d}
~
nd}
~
ww |D ]h}zE|d }|d }t	|trft
|}dd | D }dd | D }i }|r||d< |r||d< |r|d|i| |d  d7  < W qR ty }
 z|d	 d| dt|
  W Y d}
~
qRd}
~
ww |D ]=}zt	|trt
|}|d|i |d  d7  < W q ty }
 z|d	 d| dt|
  W Y d}
~
qd}
~
ww W |S  ty! }
 zd|d< |d	 dt|
  W Y d}
~
|S d}
~
ww )a   
        Apply changes from edited DataFrame back to MongoDB.
        
        This method:
        1. Identifies new, updated, and deleted documents
        2. Inserts new documents
        3. Updates existing documents (only changed fields)
        4. Deletes removed documents
        
        Args:
            collection_name: Name of the collection
            original_df: Original DataFrame from MongoDB
            edited_df: Edited DataFrame from UI
            
        Returns:
            Dictionary with operation results
        NFz0Collection not found or not connected to MongoDB)successerrorTr   )rg   insertedupdateddeletederrorsri   rl   zInsert error: r   rJ   c                 S   s   i | ]\}}|d ur||qS )Nr   rD   r   r   r   rG   w      z-DataService.apply_changes.<locals>.<dictcomp>c                 S   s   i | ]\}}|d u r|dqS )NrI   r   rD   r   r   r   rG   x  rm   z$setz$unsetrj      zUpdate error for z: rk   zDelete error for rg   zGeneral error: )r   get_collectionra   insert_manyre   inserted_ids	Exceptionr;   r   r   r   r   
update_one
delete_one)r   r*   r?   r@   
collectionrT   rU   rV   resultsinsert_resulte
update_docr\   rJ   set_updatesunset_updates	update_opr   r   r   apply_changes7  s   

$
*
*"zDataService.apply_changesN)r)   r   )r9   r   )__name__
__module____qualname____doc__r   r   r   r   r   r   r(   intr2   r3   r8   r>   r   ra   rf   r}   r   r   r   r   r
      sd    "
" 
.
2
]


r
   )r   pandasr2   typingr   r   r   r   r   bsonr   r   services.json_flattenerr	   r
   r   r   r   r   <module>   s    