from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.api.deps import get_db, current_user, require_roles
from app.models.entities import (User,Role,BusinessTerm,SemanticModel,SemanticMetric,SemanticDimension,
 KnowledgeGraphNode,KnowledgeGraphEdge,SemanticPermission,SemanticQueryAudit)
from app.services.semantic_engine import semantic_plan,audit_query,enterprise_search,impact_analysis,can_read_model

router=APIRouter(prefix='/api/v015',tags=['V015 Enterprise Data Fabric'])
GOV=(Role.admin,Role.director,Role.data_engineer,Role.data_steward)
class TermIn(BaseModel):
    name:str; definition:str; synonyms:list[str]=Field(default_factory=list); domain:str='corporate'; classification:str='internal'; steward_id:str|None=None; examples:list=Field(default_factory=list)
class ModelIn(BaseModel):
    name:str; description:str=''; domain:str='corporate'; dataset_id:str|None=None; business_object:str; source_mapping:dict=Field(default_factory=dict); default_filters:dict=Field(default_factory=dict)
class MetricIn(BaseModel):
    name:str; label:str; description:str=''; expression:str; aggregation:str='sum'; data_type:str='number'; format:str=''
class DimensionIn(BaseModel):
    name:str; label:str; expression:str; data_type:str='string'; hierarchy:list=Field(default_factory=list); hidden:bool=False
class PermissionIn(BaseModel):
    subject_type:str='role'; subject_value:str; object_type:str='model'; object_name:str='*'; action:str='read'; row_filter:dict=Field(default_factory=dict); masking:dict=Field(default_factory=dict); effect:str='allow'
class QueryIn(BaseModel):
    model_id:str; metrics:list[str]=Field(default_factory=list); dimensions:list[str]=Field(default_factory=list); filters:dict=Field(default_factory=dict); limit:int=500
class SearchIn(BaseModel): text:str; limit:int=50
class NodeIn(BaseModel): node_type:str; external_id:str; label:str; properties:dict=Field(default_factory=dict); domain:str='corporate'
class EdgeIn(BaseModel): source_node_id:str; target_node_id:str; relation_type:str; properties:dict=Field(default_factory=dict)

@router.get('/summary')
def summary(db:Session=Depends(get_db),user:User=Depends(current_user)):
    models=[m for m in db.scalars(select(SemanticModel)).all() if can_read_model(db,user,m)]
    return {'terms':len(db.scalars(select(BusinessTerm)).all()),'models':len(models),'certified_metrics':len(db.scalars(select(SemanticMetric).where(SemanticMetric.certified.is_(True))).all()),'graph_nodes':len(db.scalars(select(KnowledgeGraphNode)).all()),'graph_edges':len(db.scalars(select(KnowledgeGraphEdge)).all())}

@router.post('/glossary')
def create_term(p:TermIn,db:Session=Depends(get_db),user:User=Depends(require_roles(*GOV))):
    obj=BusinessTerm(**p.model_dump(),owner_id=user.id); db.add(obj); db.commit(); db.refresh(obj); return obj
@router.get('/glossary')
def list_terms(db:Session=Depends(get_db),user:User=Depends(current_user)):
    return db.scalars(select(BusinessTerm).order_by(BusinessTerm.name)).all()
@router.post('/glossary/{term_id}/approve')
def approve_term(term_id:str,db:Session=Depends(get_db),user:User=Depends(require_roles(Role.admin,Role.director,Role.data_steward))):
    obj=db.get(BusinessTerm,term_id)
    if not obj: raise HTTPException(404,'Termo não encontrado')
    obj.status='approved'; db.commit(); db.refresh(obj); return obj

@router.post('/semantic/models')
def create_model(p:ModelIn,db:Session=Depends(get_db),user:User=Depends(require_roles(*GOV))):
    obj=SemanticModel(**p.model_dump(),owner_id=user.id); db.add(obj); db.commit(); db.refresh(obj); return obj
@router.get('/semantic/models')
def list_models(db:Session=Depends(get_db),user:User=Depends(current_user)):
    return [m for m in db.scalars(select(SemanticModel).order_by(SemanticModel.name)).all() if can_read_model(db,user,m)]
@router.post('/semantic/models/{model_id}/approve')
def approve_model(model_id:str,db:Session=Depends(get_db),user:User=Depends(require_roles(Role.admin,Role.director,Role.data_steward))):
    obj=db.get(SemanticModel,model_id)
    if not obj: raise HTTPException(404,'Modelo não encontrado')
    obj.status='approved'; obj.approved_by=user.id; obj.approved_at=datetime.now(timezone.utc); db.commit(); db.refresh(obj); return obj
@router.post('/semantic/models/{model_id}/metrics')
def create_metric(model_id:str,p:MetricIn,db:Session=Depends(get_db),user:User=Depends(require_roles(*GOV))):
    if not db.get(SemanticModel,model_id): raise HTTPException(404,'Modelo não encontrado')
    obj=SemanticMetric(model_id=model_id,owner_id=user.id,**p.model_dump()); db.add(obj); db.commit(); db.refresh(obj); return obj
@router.post('/semantic/models/{model_id}/dimensions')
def create_dimension(model_id:str,p:DimensionIn,db:Session=Depends(get_db),user:User=Depends(require_roles(*GOV))):
    if not db.get(SemanticModel,model_id): raise HTTPException(404,'Modelo não encontrado')
    obj=SemanticDimension(model_id=model_id,**p.model_dump()); db.add(obj); db.commit(); db.refresh(obj); return obj
@router.get('/semantic/models/{model_id}')
def model_detail(model_id:str,db:Session=Depends(get_db),user:User=Depends(current_user)):
    model=db.get(SemanticModel,model_id)
    if not model or not can_read_model(db,user,model): raise HTTPException(404,'Modelo não encontrado')
    return {'model':model,'metrics':db.scalars(select(SemanticMetric).where(SemanticMetric.model_id==model_id)).all(),'dimensions':db.scalars(select(SemanticDimension).where(SemanticDimension.model_id==model_id)).all()}
@router.post('/semantic/models/{model_id}/permissions')
def permission(model_id:str,p:PermissionIn,db:Session=Depends(get_db),user:User=Depends(require_roles(Role.admin,Role.director,Role.data_steward))):
    obj=SemanticPermission(model_id=model_id,created_by=user.id,**p.model_dump()); db.add(obj); db.commit(); db.refresh(obj); return obj
@router.post('/semantic/query')
def query(p:QueryIn,db:Session=Depends(get_db),user:User=Depends(current_user)):
    model=db.get(SemanticModel,p.model_id)
    if not model: raise HTTPException(404,'Modelo não encontrado')
    payload=p.model_dump()
    try:
        plan=semantic_plan(db,user,model,p.metrics,p.dimensions,p.filters,p.limit); audit=audit_query(db,user,model,payload,plan)
        return {'status':'planned','plan':plan,'audit_id':audit.id,'execution_note':'A V015 resolve e governa o plano semântico; a execução física usa o conector do dataset em versão posterior.'}
    except PermissionError as e: audit_query(db,user,model,payload,{},'denied',str(e)); raise HTTPException(403,str(e))
    except ValueError as e: audit_query(db,user,model,payload,{},'failed',str(e)); raise HTTPException(422,str(e))

@router.post('/search')
def search(p:SearchIn,db:Session=Depends(get_db),user:User=Depends(current_user)): return enterprise_search(db,user,p.text,p.limit)
@router.post('/knowledge-graph/nodes')
def create_node(p:NodeIn,db:Session=Depends(get_db),user:User=Depends(require_roles(*GOV))):
    obj=KnowledgeGraphNode(**p.model_dump()); db.add(obj); db.commit(); db.refresh(obj); return obj
@router.post('/knowledge-graph/edges')
def create_edge(p:EdgeIn,db:Session=Depends(get_db),user:User=Depends(require_roles(*GOV))):
    if not db.get(KnowledgeGraphNode,p.source_node_id) or not db.get(KnowledgeGraphNode,p.target_node_id): raise HTTPException(404,'Nó não encontrado')
    obj=KnowledgeGraphEdge(**p.model_dump()); db.add(obj); db.commit(); db.refresh(obj); return obj
@router.get('/knowledge-graph')
def graph(db:Session=Depends(get_db),user:User=Depends(current_user)):
    return {'nodes':db.scalars(select(KnowledgeGraphNode).limit(1000)).all(),'edges':db.scalars(select(KnowledgeGraphEdge).limit(3000)).all()}
@router.get('/impact/{node_id}')
def impact(node_id:str,db:Session=Depends(get_db),user:User=Depends(current_user)):
    if not db.get(KnowledgeGraphNode,node_id): raise HTTPException(404,'Nó não encontrado')
    return impact_analysis(db,node_id)
@router.get('/query-audit')
def query_audit(db:Session=Depends(get_db),user:User=Depends(require_roles(Role.admin,Role.director,Role.auditor,Role.data_steward))):
    return db.scalars(select(SemanticQueryAudit).order_by(SemanticQueryAudit.created_at.desc()).limit(500)).all()
