from datetime import datetime, timezone
from sqlalchemy import select, or_
from sqlalchemy.orm import Session
from app.models.entities import (BusinessTerm, SemanticModel, SemanticMetric, SemanticDimension,
    SemanticPermission, SemanticQueryAudit, KnowledgeGraphNode, KnowledgeGraphEdge, Dataset, Dashboard, Report, ApiDefinition)

PRIVILEGED={'admin','director','data_engineer','data_steward','auditor'}

def role_value(user): return user.role.value if hasattr(user.role,'value') else str(user.role)

def can_read_model(db:Session,user,model:SemanticModel)->bool:
    if role_value(user) in PRIVILEGED or model.owner_id==user.id: return True
    perms=db.scalars(select(SemanticPermission).where(SemanticPermission.model_id==model.id,SemanticPermission.action=='read')).all()
    decision=None
    for p in perms:
        match=(p.subject_type=='user' and p.subject_value==user.id) or (p.subject_type=='role' and p.subject_value==role_value(user)) or (p.subject_type=='department' and p.subject_value==str(user.department_id or ''))
        if match:
            if p.effect=='deny': return False
            decision=True
    return bool(decision or model.status=='approved')

def semantic_plan(db:Session,user,model:SemanticModel,metrics:list[str],dimensions:list[str],filters:dict,limit:int=500):
    if not can_read_model(db,user,model): raise PermissionError('Acesso negado ao modelo semântico')
    metric_rows=db.scalars(select(SemanticMetric).where(SemanticMetric.model_id==model.id,SemanticMetric.name.in_(metrics or ['__none__']))).all() if metrics else []
    dimension_rows=db.scalars(select(SemanticDimension).where(SemanticDimension.model_id==model.id,SemanticDimension.name.in_(dimensions or ['__none__']))).all() if dimensions else []
    missing_m=set(metrics)-{x.name for x in metric_rows}; missing_d=set(dimensions)-{x.name for x in dimension_rows}
    if missing_m or missing_d: raise ValueError(f'Campos semânticos desconhecidos: {sorted(missing_m|missing_d)}')
    mapping=model.source_mapping or {}
    plan={'model':model.name,'business_object':model.business_object,'dataset_id':model.dataset_id,'source_object':mapping.get('source_object',''),'metrics':[{'name':m.name,'expression':m.expression,'aggregation':m.aggregation} for m in metric_rows],'dimensions':[{'name':d.name,'expression':d.expression} for d in dimension_rows],'filters':{**(model.default_filters or {}),**(filters or {})},'limit':min(max(limit,1),5000)}
    return plan

def audit_query(db,user,model,query,plan,status='success',error=''):
    obj=SemanticQueryAudit(user_id=user.id,model_id=model.id if model else None,query=query,resolved_plan=plan,status=status,error=error)
    db.add(obj); db.commit(); db.refresh(obj); return obj

def enterprise_search(db:Session,user,text:str,limit:int=50):
    q=f'%{text.strip()}%'; out=[]
    for row in db.scalars(select(BusinessTerm).where(or_(BusinessTerm.name.ilike(q),BusinessTerm.definition.ilike(q))).limit(limit)).all(): out.append({'type':'business_term','id':row.id,'title':row.name,'description':row.definition,'url':'/business-glossary'})
    for row in db.scalars(select(SemanticModel).where(or_(SemanticModel.name.ilike(q),SemanticModel.description.ilike(q),SemanticModel.business_object.ilike(q))).limit(limit)).all():
        if can_read_model(db,user,row): out.append({'type':'semantic_model','id':row.id,'title':row.name,'description':row.description,'url':'/semantic-hub'})
    for row in db.scalars(select(Dataset).where(or_(Dataset.name.ilike(q),Dataset.source_object.ilike(q))).limit(limit)).all(): out.append({'type':'dataset','id':row.id,'title':row.name,'description':row.source_object,'url':'/enterprise'})
    for cls,typ,url in [(Dashboard,'dashboard','/studio'),(Report,'report','/enterprise'),(ApiDefinition,'api','/integration-studio')]:
        for row in db.scalars(select(cls).where(cls.name.ilike(q)).limit(limit)).all(): out.append({'type':typ,'id':row.id,'title':row.name,'description':'','url':url})
    return out[:limit]

def impact_analysis(db:Session,node_id:str):
    visited={node_id}; frontier=[node_id]; edges=[]
    while frontier and len(visited)<500:
        current=frontier.pop(0)
        found=db.scalars(select(KnowledgeGraphEdge).where(KnowledgeGraphEdge.source_node_id==current)).all()
        for e in found:
            edges.append(e)
            if e.target_node_id not in visited: visited.add(e.target_node_id); frontier.append(e.target_node_id)
    nodes=db.scalars(select(KnowledgeGraphNode).where(KnowledgeGraphNode.id.in_(visited))).all()
    return {'root_node_id':node_id,'nodes':nodes,'edges':edges,'affected_count':max(0,len(nodes)-1)}
