from datetime import datetime, timezone
from fastapi import APIRouter,Depends,Header,HTTPException
from pydantic import BaseModel,Field
from sqlalchemy import func,select
from sqlalchemy.orm import Session
from app.api.deps import current_user
from app.core.database import get_db
from app.models.entities import Tenant,User,LowCodeApplicationV038,LowCodeFormV038,BPMNProcessV038,BPMNProcessVersionV038,ProcessInstanceV038,HumanTaskV038,BusinessRuleV038
from app.services.lowcode_bpmn_v038 import safe_code,validate_form_schema,validate_process,evaluate_conditions,first_nodes,due_date
from app.services.saas_tenancy import resolve_membership
router=APIRouter(prefix='/api/v038',tags=['V038 BPMN and Low-Code Studio'])
ADMIN={'admin','director','manager','data_steward','data_engineer','developer'}
def ctx(x_tenant_id:str|None=Header(None,alias='X-Tenant-ID'),db:Session=Depends(get_db),user:User=Depends(current_user)):
    if not x_tenant_id: raise HTTPException(400,'X-Tenant-ID obrigatório')
    tenant=db.get(Tenant,x_tenant_id)
    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 resolve_membership(db,user,tenant.id): 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 gestor ou técnico necessário')
    return c
class AppIn(BaseModel): code:str; name:str; description:str=''; icon:str='workflow'; theme:dict=Field(default_factory=dict); navigation:list=Field(default_factory=list)
class FormIn(BaseModel): application_id:str|None=None; code:str; name:str; schema_json:list; layout:dict=Field(default_factory=dict); validation_rules:list=Field(default_factory=list); access_policy:dict=Field(default_factory=dict)
class ProcessIn(BaseModel): application_id:str|None=None; code:str; name:str; description:str=''; department_id:str|None=None
class VersionIn(BaseModel): nodes:list; edges:list; variables:dict=Field(default_factory=dict); sla_policy:dict=Field(default_factory=dict); change_notes:str=''
class StartIn(BaseModel): business_key:str=''; variables:dict=Field(default_factory=dict)
class CompleteTaskIn(BaseModel): payload:dict=Field(default_factory=dict); decision:str='approved'
class RuleIn(BaseModel): code:str; name:str; description:str=''; conditions:list; result:dict=Field(default_factory=dict); priority:int=100; active:bool=True
@router.get('/summary')
def summary(c=Depends(ctx),db:Session=Depends(get_db)):
    tid=c[0].id
    count=lambda model: db.scalar(select(func.count()).select_from(model).where(model.tenant_id==tid)) or 0
    return {'version':'V038','applications':count(LowCodeApplicationV038),'forms':count(LowCodeFormV038),'processes':count(BPMNProcessV038),'running_instances':db.scalar(select(func.count()).select_from(ProcessInstanceV038).where(ProcessInstanceV038.tenant_id==tid,ProcessInstanceV038.status=='running')) or 0,'open_tasks':db.scalar(select(func.count()).select_from(HumanTaskV038).where(HumanTaskV038.tenant_id==tid,HumanTaskV038.status=='open')) or 0}
@router.post('/applications',status_code=201)
def create_app(p:AppIn,c=Depends(admin),db:Session=Depends(get_db)):
    item=LowCodeApplicationV038(tenant_id=c[0].id,owner_id=c[1].id,code=safe_code(p.code),**p.model_dump(exclude={'code'})); db.add(item); db.commit(); db.refresh(item); return item
@router.get('/applications')
def applications(c=Depends(ctx),db:Session=Depends(get_db)): return db.scalars(select(LowCodeApplicationV038).where(LowCodeApplicationV038.tenant_id==c[0].id).order_by(LowCodeApplicationV038.updated_at.desc())).all()
@router.post('/forms',status_code=201)
def create_form(p:FormIn,c=Depends(admin),db:Session=Depends(get_db)):
    try: validate_form_schema(p.schema_json)
    except ValueError as e: raise HTTPException(400,str(e))
    item=LowCodeFormV038(tenant_id=c[0].id,created_by=c[1].id,code=safe_code(p.code),**p.model_dump(exclude={'code'})); db.add(item); db.commit(); db.refresh(item); return item
@router.get('/forms')
def forms(c=Depends(ctx),db:Session=Depends(get_db)): return db.scalars(select(LowCodeFormV038).where(LowCodeFormV038.tenant_id==c[0].id).order_by(LowCodeFormV038.created_at.desc())).all()
@router.post('/processes',status_code=201)
def create_process(p:ProcessIn,c=Depends(admin),db:Session=Depends(get_db)):
    item=BPMNProcessV038(tenant_id=c[0].id,owner_id=c[1].id,code=safe_code(p.code),**p.model_dump(exclude={'code'})); db.add(item); db.commit(); db.refresh(item); return item
@router.get('/processes')
def processes(c=Depends(ctx),db:Session=Depends(get_db)): return db.scalars(select(BPMNProcessV038).where(BPMNProcessV038.tenant_id==c[0].id).order_by(BPMNProcessV038.updated_at.desc())).all()
def owned_process(db,tid,pid):
    item=db.get(BPMNProcessV038,pid)
    if not item or item.tenant_id!=tid: raise HTTPException(404,'Processo não encontrado')
    return item
@router.post('/processes/{process_id}/versions',status_code=201)
def version(process_id:str,p:VersionIn,c=Depends(admin),db:Session=Depends(get_db)):
    proc=owned_process(db,c[0].id,process_id)
    try: validation=validate_process(p.nodes,p.edges)
    except ValueError as e: raise HTTPException(400,str(e))
    proc.current_version+=1
    item=BPMNProcessVersionV038(tenant_id=c[0].id,process_id=proc.id,version=proc.current_version,created_by=c[1].id,validation_result=validation,**p.model_dump()); db.add(item); db.commit(); db.refresh(item); return item
@router.get('/processes/{process_id}/versions')
def versions(process_id:str,c=Depends(ctx),db:Session=Depends(get_db)):
    owned_process(db,c[0].id,process_id); return db.scalars(select(BPMNProcessVersionV038).where(BPMNProcessVersionV038.process_id==process_id).order_by(BPMNProcessVersionV038.version.desc())).all()
@router.post('/processes/{process_id}/versions/{version_id}/publish')
def publish(process_id:str,version_id:str,c=Depends(admin),db:Session=Depends(get_db)):
    proc=owned_process(db,c[0].id,process_id); ver=db.get(BPMNProcessVersionV038,version_id)
    if not ver or ver.process_id!=proc.id: raise HTTPException(404,'Versão não encontrada')
    ver.status='published'; ver.approved_by=c[1].id; ver.published_at=datetime.now(timezone.utc); proc.published_version=ver.version; proc.status='published'; db.commit(); db.refresh(ver); return ver
@router.post('/processes/{process_id}/start',status_code=201)
def start(process_id:str,p:StartIn,c=Depends(ctx),db:Session=Depends(get_db)):
    proc=owned_process(db,c[0].id,process_id)
    if proc.status!='published' or not proc.published_version: raise HTTPException(409,'Processo não publicado')
    ver=db.scalar(select(BPMNProcessVersionV038).where(BPMNProcessVersionV038.process_id==proc.id,BPMNProcessVersionV038.version==proc.published_version))
    current=first_nodes(ver); inst=ProcessInstanceV038(tenant_id=c[0].id,process_id=proc.id,process_version_id=ver.id,business_key=p.business_key,variables=p.variables,current_nodes=current,started_by=c[1].id); db.add(inst); db.flush()
    by_id={n['id']:n for n in ver.nodes}
    for node_id in current:
        node=by_id[node_id]
        if node.get('type')=='user_task': db.add(HumanTaskV038(tenant_id=c[0].id,instance_id=inst.id,node_id=node_id,name=node.get('name','Tarefa'),assignee_id=node.get('assignee_id'),candidate_roles=node.get('candidate_roles',[]),candidate_departments=node.get('candidate_departments',[]),form_id=node.get('form_id'),priority=node.get('priority','normal'),due_at=due_date(node.get('config')),payload={}))
    db.commit(); db.refresh(inst); return inst
@router.get('/instances')
def instances(c=Depends(ctx),db:Session=Depends(get_db)): return db.scalars(select(ProcessInstanceV038).where(ProcessInstanceV038.tenant_id==c[0].id).order_by(ProcessInstanceV038.started_at.desc()).limit(200)).all()
@router.get('/tasks')
def tasks(c=Depends(ctx),db:Session=Depends(get_db)):
    user=c[1]; role=getattr(user.role,'value',user.role)
    q=select(HumanTaskV038).where(HumanTaskV038.tenant_id==c[0].id,HumanTaskV038.status=='open')
    if role not in {'admin','director','manager'}: q=q.where((HumanTaskV038.assignee_id==user.id)|(HumanTaskV038.candidate_roles.contains([role])))
    return db.scalars(q.order_by(HumanTaskV038.due_at.asc())).all()
@router.post('/tasks/{task_id}/complete')
def complete(task_id:str,p:CompleteTaskIn,c=Depends(ctx),db:Session=Depends(get_db)):
    task=db.get(HumanTaskV038,task_id)
    if not task or task.tenant_id!=c[0].id: raise HTTPException(404,'Tarefa não encontrada')
    if task.status!='open': raise HTTPException(409,'Tarefa já concluída')
    if task.assignee_id and task.assignee_id!=c[1].id and getattr(c[1].role,'value',c[1].role) not in {'admin','director','manager'}: raise HTTPException(403,'Tarefa atribuída a outro usuário')
    task.status='completed'; task.payload={**p.payload,'decision':p.decision}; task.completed_by=c[1].id; task.completed_at=datetime.now(timezone.utc); db.commit(); db.refresh(task); return task
@router.post('/rules',status_code=201)
def rule(p:RuleIn,c=Depends(admin),db:Session=Depends(get_db)):
    try: evaluate_conditions(p.conditions,{})
    except (ValueError,TypeError): pass
    item=BusinessRuleV038(tenant_id=c[0].id,created_by=c[1].id,code=safe_code(p.code),**p.model_dump(exclude={'code'})); db.add(item); db.commit(); db.refresh(item); return item
@router.post('/rules/{rule_id}/evaluate')
def evaluate(rule_id:str,variables:dict,c=Depends(ctx),db:Session=Depends(get_db)):
    item=db.get(BusinessRuleV038,rule_id)
    if not item or item.tenant_id!=c[0].id: raise HTTPException(404,'Regra não encontrada')
    try: matched=evaluate_conditions(item.conditions,variables)
    except ValueError as e: raise HTTPException(400,str(e))
    return {'matched':matched,'result':item.result if matched else {}}
