import marimo

__generated_with = "0.16.5"
app = marimo.App(width="medium")


@app.cell
def _():
    import marimo as mo
    return (mo,)


@app.cell
def _():
    from pif_compiler.functions.db_utils import db_connect
    return (db_connect,)


@app.cell
def _(db_connect):
    col = db_connect(collection_name="orders")
    return (col,)


@app.cell
def _(col):
    input = col.find_one({"client_name": "CSM Srl"})
    return (input,)


@app.cell
def _(input):
    input
    return


@app.cell
def _():
    import json
    from pydantic import BaseModel, Field, field_validator, ConfigDict, model_validator
    from pymongo import MongoClient
    import re
    from typing import List, Optional
    return BaseModel, ConfigDict, Field, List, Optional, model_validator, re


app._unparsable_cell(
    r"""


    class CosmeticIngredient(BaseModel):
        inci_name: str
        cas: str = Field(..., pattern=r'^\d{2,7}-\d{2}-\d$')
        colorant = bool = Field(default=False)
        organic = bool = Field(default=False)

        dap = dict | None = Field(default=None)
        cosing = dict | None = Field(default=None)
        tox_levels = dict | None = Field(default=None)

        @field_validator('inci_name')
        @classmethod
        def make_uppercase(cls, v: str) -> str:
            return v.upper()
    """,
    name="_"
)


@app.cell
def _(CosmeticIngredient, collection, mo):
    mo.stop(True)
    try:
        ingredient = CosmeticIngredient(
            inci_name="Glycerin",
            cas="56-81-5",
            percentage=5.5
        )
        print(f"✅ Object Created: {ingredient}")
    except ValueError as e:
        print(f"❌ Validation Error: {e}")

    document_to_insert = ingredient.model_dump()

    result = collection.insert_one(document_to_insert)
    return


@app.cell
def _(
    BaseModel,
    ConfigDict,
    CosingInfo,
    Field,
    List,
    Optional,
    mo,
    model_validator,
    re,
):
    mo.stop(True)


    class DapInfo(BaseModel):
        """Informazioni dal Dossier (es. origine, purezza)"""
        origin: Optional[str] = None       # es. "Synthetic", "Vegetable"
        purity_percentage: Optional[float] = None
        supplier_code: Optional[str] = None

    class ToxInfo(BaseModel):
        """Dati Tossicologici"""
        noael: Optional[float] = None      # No Observed Adverse Effect Level
        ld50: Optional[float] = None       # Lethal Dose 50
        sed: Optional[float] = None        # Systemic Exposure Dosage
        mos: Optional[float] = None        # Margin of Safety

    # --- 2. Modello Principale ---

    class CosmeticIngredient(BaseModel):
        model_config = ConfigDict(validate_assignment=True) # Valida anche se modifichi i campi dopo

        # Gestione INCI multipli per lo stesso CAS
        inci_names: List[str] = Field(default_factory=list)

        # Il CAS è una stringa obbligatoria, ma il regex dipende dal contesto
        cas: str 

        colorant: bool = Field(default=False)
        organic: bool = Field(default=False)

        # Sotto-oggetti opzionali
        dap: Optional[DapInfo] = None
        cosing: Optional[CosingInfo] = None
        tox_levels: Optional[ToxInfo] = None

        # --- VALIDAZIONE CONDIZIONALE CAS ---
        @model_validator(mode='after')
        def validate_cas_logic(self):
            cas_value = self.cas
            is_exempt = self.colorant or self.organic

            if not cas_value or not cas_value.strip():
                raise ValueError("Il campo CAS non può essere vuoto.")

            if not is_exempt:
                cas_regex = r'^\d{2,7}-\d{2}-\d$'
                if not re.match(cas_regex, cas_value):
                    raise ValueError(f"Formato CAS non valido ('{cas_value}') per ingrediente standard.")

            # Se è colorante/organico, accettiamo qualsiasi stringa (es. 'CI 77891' o codici interni)
            return self

        # --- METODO HELPER PER AGGIUNGERE INCI ---
        def add_inci(self, new_inci: str):
            """Aggiunge un INCI alla lista solo se non è già presente (case insensitive)."""
            new_inci_upper = new_inci.upper()
            # Controlliamo se esiste già (normalizzando a maiuscolo per sicurezza)
            if not any(existing.upper() == new_inci_upper for existing in self.inci_names):
                self.inci_names.append(new_inci_upper)
                print(f"✅ INCI '{new_inci_upper}' aggiunto.")
            else:
                print(f"ℹ️ INCI '{new_inci_upper}' già presente.")
    return CosmeticIngredient, DapInfo


@app.cell
def _():
    from pif_compiler.services.srv_pubchem import pubchem_dap

    dato = pubchem_dap("56-81-5")
    dato
    return (dato,)


app._unparsable_cell(
    r"""
    molecular_weight = dato.get(\"MolecularWeight\")
    log pow = dato.get(\"XLogP\")
    topological_polar_surface_area = dato.get(\"TPSA\")
    melting_point = dato.get(\"MeltingPoint\")
    ionization = dato.get(\"Dissociation Constants\")
    """,
    name="_"
)


@app.cell(hide_code=True)
def _(mo):
    mo.md(
        r"""
    Molecolar Weight >500 Da
    High degree of ionisation
    Log Pow ≤-1 or ≥ 4
    Topological polar surface area >120 Å2
    Melting point > 200°C
    """
    )
    return


@app.cell
def _(BaseModel, Field, Optional, model_validator):
    class DapInfo(BaseModel):
        cas: str

        molecular_weight: Optional[float] = Field(default=None, description="In Daltons (Da)")
        high_ionization: Optional[float] = Field(default=None, description="High degree of ionization")
        log_pow: Optional[float] = Field(default=None, description="Partition coefficient")
        tpsa: Optional[float] = Field(default=None, description="Topological polar surface area")
        melting_point: Optional[float] = Field(default=None, description="In Celsius (°C)")

        # --- Il valore DAP Calcolato ---
        # Lo impostiamo di default a 0.5 (50%), verrà sovrascritto dal validator
        dap_value: float = 0.5 

        @model_validator(mode='after')
        def compute_dap(self):
            # Lista delle condizioni (True se la condizione riduce l'assorbimento)
            conditions = []

            # 1. MW > 500 Da
            if self.molecular_weight is not None:
                conditions.append(self.molecular_weight > 500)

            # 2. High Ionization (Se è True, riduce l'assorbimento)
            if self.high_ionization is not None:
                conditions.append(self.high_ionization is True)

            # 3. Log Pow <= -1 OR >= 4
            if self.log_pow is not None:
                conditions.append(self.log_pow <= -1 or self.log_pow >= 4)

            # 4. TPSA > 120 Å2
            if self.tpsa is not None:
                conditions.append(self.tpsa > 120)

            # 5. Melting Point > 200°C
            if self.melting_point is not None:
                conditions.append(self.melting_point > 200)

            # LOGICA FINALE:
            # Se c'è almeno una condizione "sicura" (True), il DAP è 0.1
            if any(conditions):
                self.dap_value = 0.1
            else:
                self.dap_value = 0.5

            return self
    return (DapInfo,)


@app.cell
def _(DapInfo, dato, re):
    desiderated_keys = ['CAS', 'MolecularWeight', 'XLogP', 'TPSA', 'Melting Point', 'Dissociation Constants']
    actual_keys = [key for key in dato.keys() if key in desiderated_keys]

    dict = {}

    for key in actual_keys:
        if key == 'CAS':
            dict['cas'] = dato[key]
        if key == 'MolecularWeight':
            mw = float(dato[key])
            dict['molecular_weight'] = mw
        if key == 'XLogP':
            log_pow = float(dato[key])
            dict['log_pow'] = log_pow
        if key == 'TPSA':
            tpsa = float(dato[key])
            dict['tpsa'] = tpsa
        if key == 'Melting Point':
            try:
                for item in dato[key]:
                    if '°C' in item['Value']:
                        mp = dato[key]['Value']
                        mp_value = re.findall(r"[-+]?\d*\.\d+|\d+", mp)
                        if mp_value:
                            dict['melting_point'] = float(mp_value[0])
            except:
                continue
        if key == 'Dissociation Constants':
            try:
                for item in dato[key]:
                    if 'pKa' in item['Value']:
                        pk = dato[key]['Value']
                        pk_value = re.findall(r"[-+]?\d*\.\d+|\d+", mp)
                        if pk_value:
                            dict['high_ionization'] = float(mp_value[0])
            except:
                continue

    dap_info = DapInfo(**dict)
    dap_info
    return


@app.cell
def _():
    from pif_compiler.services.srv_cosing import cosing_search, parse_cas_numbers, clean_cosing, identified_ingredients
    return clean_cosing, cosing_search


@app.cell
def _(clean_cosing, cosing_search):
    raw_cosing = cosing_search("72-48-0", 'cas')
    cleaned_cosing = clean_cosing(raw_cosing)
    cleaned_cosing
    return cleaned_cosing, raw_cosing


@app.cell
def _(mo):
    mo.md(
        r"""
    otherRestrictions
    refNo
    annexNo
    casNo
    functionName
    """
    )
    return


@app.cell
def _(BaseModel, Field, List, Optional):
    class CosingInfo(BaseModel):
        cas : List[str] = Field(default_factory=list)
        common_names : List[str] = Field(default_factory=list)
        inci : List[str] = Field(default_factory=list)
        annex : List[str] = Field(default_factory=list)
        functionName : List[str] = Field(default_factory=list)
        otherRestrictions : List[str] = Field(default_factory=list)
        cosmeticRestriction : Optional[str]
    return (CosingInfo,)


@app.cell
def _(CosingInfo):
    def cosing_builder(cleaned_cosing):
        cosing_keys = ['nameOfCommonIngredientsGlossary', 'casNo', 'functionName', 'annexNo', 'refNo', 'otherRestrictions', 'cosmeticRestriction', 'inciName']
        keys = [k for k in cleaned_cosing.keys() if k in cosing_keys]
    
        cosing_dict = {}
    
        for k in keys:
            if k == 'nameOfCommonIngredientsGlossary':
                names = []
                for name in cleaned_cosing[k]:
                    names.append(name)
                cosing_dict['common_names'] = names
            if k == 'inciName':
                inci = []
                for inc in cleaned_cosing[k]:
                    inci.append(inc)
                cosing_dict['inci'] = names
            if k == 'casNo':
                cas_list = []
                for casNo in cleaned_cosing[k]:
                    cas_list.append(casNo)
                cosing_dict['cas'] = cas_list
            if k == 'functionName':
                functions = []
                for func in cleaned_cosing[k]:
                    functions.append(func)
                cosing_dict['functionName'] = functions
            if k == 'annexNo':
                annexes = []
                i = 0
                for ann in cleaned_cosing[k]:
                    restriction = ann + ' / ' + cleaned_cosing['refNo'][i]
                    annexes.append(restriction)
                    i = i+1
                cosing_dict['annex'] = annexes
            if k == 'otherRestrictions':
                other_restrictions = []
                for ores in cleaned_cosing[k]:
                    other_restrictions.append(ores)
                cosing_dict['otherRestrictions'] = other_restrictions
            if k == 'cosmeticRestriction':
                cosing_dict['cosmeticRestriction'] = cleaned_cosing[k]                
    
        test_cosing = CosingInfo(
            **cosing_dict
        )
        return test_cosing
    return (cosing_builder,)


@app.cell
def _(cleaned_cosing, cosing_builder):
    id = cleaned_cosing['identifiedIngredient']
    if id:
        for e in id:
            obj = cosing_builder(e)
    obj
    return


@app.cell
def _(raw_cosing):
    raw_cosing
    return


@app.cell
def _():
    return


if __name__ == "__main__":
    app.run()
