from datetime import datetime,timezone
from pydantic import BaseModel,Field
from fastapi import APIRouter,Depends,Header,HTTPException
from sqlalchemy import select
from sqlalchemy.orm import Session
from croniter import croniter
from app.core.database import get_db
from app.api.deps import current_user
from app.models.entities import User,Tenant,ETLPipeline,ETLPipelineVersion,ETLPipelineRun,ETLNodeRun,ETLTemplate
from app.services.saas_tenancy import resolve_membership
from app.services.etl_studio import validate_pipeline,execute_pipeline
router=APIRouter(prefix='/api/v026',tags=['V026 Visual ETL Studio'])
ADMIN={'admin','director','data_steward','data_engineer'}

def ctx(x_tenant_id:str=Header(...,alias='X-Tenant-ID'),db:Session=Depends(get_db),user:User=Depends(current_user)):
    tenant=db.get(Tenant,x_tenant_id); member=resolve_membership(db,user,x_tenant_id) if tenant else None
    if not tenant or tenant.status!='active': raise HTTPException(404,'Tenant ativo não encontrado')
    if getattr(user.role,'value',user.role) not in {'admin','director'} and not member: raise HTTPException(403,'Usuário não pertence ao tenant')
    return tenant,user

def admin(c=Depends(ctx)):
    if getattr(c[1].role,'value',c[1].role) not in ADMIN: raise HTTPException(403,'Perfil técnico necessário')
    return c
class PipelineIn(BaseModel):
    name:str; description:str=''; nodes:list=Field(default_factory=list); edges:list=Field(default_factory=list); variables:dict=Field(default_factory=dict); schedule_cron:str|None=None; timezone:str='America/Sao_Paulo'; enabled:bool=True
class TemplateIn(BaseModel): name:str; category:str='general'; description:str=''; nodes:list=Field(default_factory=list); edges:list=Field(default_factory=list); public:bool=False

@router.get('/summary')
def summary(c=Depends(ctx),db:Session=Depends(get_db)):
    t,_=c
    return {'version':'V026','pipelines':len(db.scalars(select(ETLPipeline).where(ETLPipeline.tenant_id==t.id)).all()),'runs':len(db.scalars(select(ETLPipelineRun).where(ETLPipelineRun.tenant_id==t.id)).all()),'templates':len(db.scalars(select(ETLTemplate).where((ETLTemplate.tenant_id==t.id)|(ETLTemplate.public==True))).all())}
@router.post('/pipelines')
def create(p:PipelineIn,c=Depends(admin),db:Session=Depends(get_db)):
    t,u=c
    try: validate_pipeline(p.nodes,p.edges)
    except ValueError as e: raise HTTPException(400,str(e))
    next_run=croniter(p.schedule_cron,datetime.now(timezone.utc)).get_next(datetime) if p.schedule_cron else None
    x=ETLPipeline(tenant_id=t.id,owner_id=u.id,next_run_at=next_run,**p.model_dump()); db.add(x); db.flush(); db.add(ETLPipelineVersion(tenant_id=t.id,pipeline_id=x.id,version=1,snapshot=p.model_dump(),created_by=u.id)); db.commit(); db.refresh(x); return x
@router.get('/pipelines')
def list_pipelines(c=Depends(ctx),db:Session=Depends(get_db)):
    t,_=c; return db.scalars(select(ETLPipeline).where(ETLPipeline.tenant_id==t.id).order_by(ETLPipeline.updated_at.desc())).all()
@router.get('/pipelines/{pipeline_id}')
def get_pipeline(pipeline_id:str,c=Depends(ctx),db:Session=Depends(get_db)):
    t,_=c; x=db.get(ETLPipeline,pipeline_id)
    if not x or x.tenant_id!=t.id: raise HTTPException(404,'Pipeline não encontrado')
    return x
@router.put('/pipelines/{pipeline_id}')
def update(pipeline_id:str,p:PipelineIn,c=Depends(admin),db:Session=Depends(get_db)):
    t,u=c; x=db.get(ETLPipeline,pipeline_id)
    if not x or x.tenant_id!=t.id: raise HTTPException(404,'Pipeline não encontrado')
    try: validate_pipeline(p.nodes,p.edges)
    except ValueError as e: raise HTTPException(400,str(e))
    x.version+=1
    for k,v in p.model_dump().items(): setattr(x,k,v)
    x.next_run_at=croniter(p.schedule_cron,datetime.now(timezone.utc)).get_next(datetime) if p.schedule_cron else None
    db.add(ETLPipelineVersion(tenant_id=t.id,pipeline_id=x.id,version=x.version,snapshot=p.model_dump(),created_by=u.id)); db.commit(); db.refresh(x); return x
@router.post('/pipelines/{pipeline_id}/validate')
def validate(pipeline_id:str,c=Depends(ctx),db:Session=Depends(get_db)):
    t,_=c; x=db.get(ETLPipeline,pipeline_id)
    if not x or x.tenant_id!=t.id: raise HTTPException(404,'Pipeline não encontrado')
    try: order=validate_pipeline(x.nodes,x.edges); return {'valid':True,'execution_order':order}
    except ValueError as e: return {'valid':False,'error':str(e)}
@router.post('/pipelines/{pipeline_id}/run')
def run(pipeline_id:str,debug:bool=False,c=Depends(admin),db:Session=Depends(get_db)):
    t,u=c; x=db.get(ETLPipeline,pipeline_id)
    if not x or x.tenant_id!=t.id: raise HTTPException(404,'Pipeline não encontrado')
    r=ETLPipelineRun(tenant_id=t.id,pipeline_id=x.id,requested_by=u.id,mode='debug' if debug else 'full'); db.add(r); db.commit(); db.refresh(r)
    try: return execute_pipeline(db,x,r,debug)
    except Exception as e: raise HTTPException(400,f'Falha no pipeline: {str(e)[:500]}')
@router.get('/runs')
def runs(c=Depends(ctx),db:Session=Depends(get_db)):
    t,_=c; return db.scalars(select(ETLPipelineRun).where(ETLPipelineRun.tenant_id==t.id).order_by(ETLPipelineRun.created_at.desc()).limit(200)).all()
@router.get('/runs/{run_id}/nodes')
def node_runs(run_id:str,c=Depends(ctx),db:Session=Depends(get_db)):
    t,_=c; r=db.get(ETLPipelineRun,run_id)
    if not r or r.tenant_id!=t.id: raise HTTPException(404,'Execução não encontrada')
    return db.scalars(select(ETLNodeRun).where(ETLNodeRun.pipeline_run_id==run_id).order_by(ETLNodeRun.started_at)).all()
@router.post('/templates')
def template(p:TemplateIn,c=Depends(admin),db:Session=Depends(get_db)):
    t,u=c; validate_pipeline(p.nodes,p.edges); x=ETLTemplate(tenant_id=None if p.public and getattr(u.role,'value',u.role)=='admin' else t.id,created_by=u.id,**p.model_dump()); db.add(x); db.commit(); db.refresh(x); return x
@router.get('/templates')
def templates(c=Depends(ctx),db:Session=Depends(get_db)):
    t,_=c; return db.scalars(select(ETLTemplate).where((ETLTemplate.tenant_id==t.id)|(ETLTemplate.public==True)).order_by(ETLTemplate.created_at.desc())).all()
