from datetime import datetime, timedelta, timezone
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.models.entities import WorkflowDefinition, WorkflowEvent, WorkflowInstance, WorkflowTask, User


def _node_map(definition): return {n['id']:n for n in definition.get('nodes',[]) if n.get('id')}
def _outgoing(definition,node_id): return [e for e in definition.get('edges',[]) if e.get('source')==node_id]
def _event(db,instance,node_id,event_type,actor_id=None,payload=None): db.add(WorkflowEvent(instance_id=instance.id,node_id=node_id,event_type=event_type,actor_id=actor_id,payload=payload or {}))
def _resolve_assignee(db,node,instance):
    cfg=node.get('config',{}); uid=cfg.get('assignee_id')
    if uid: return uid
    role=cfg.get('assignee_role'); dept=cfg.get('department_id')
    q=select(User).where(User.is_active.is_(True))
    if role: q=q.where(User.role==role)
    if dept: q=q.where(User.department_id==dept)
    user=db.scalar(q.limit(1)); return user.id if user else None

def _condition_ok(condition,context):
    field=condition.get('field'); op=condition.get('op','eq'); expected=condition.get('value'); actual=context.get(field)
    return {'eq':actual==expected,'ne':actual!=expected,'gt':actual is not None and actual>expected,'gte':actual is not None and actual>=expected,'lt':actual is not None and actual<expected,'lte':actual is not None and actual<=expected,'in':actual in (expected or [])}.get(op,False)

def advance(db:Session,instance:WorkflowInstance,actor_id=None):
    wf=db.get(WorkflowDefinition,instance.workflow_id); definition=wf.definition or {}; nodes=_node_map(definition)
    while instance.status=='running':
        node=nodes.get(instance.current_node_id)
        if not node: instance.status='failed'; _event(db,instance,instance.current_node_id,'invalid_node',actor_id); break
        typ=node.get('type')
        _event(db,instance,node['id'],'node_entered',actor_id)
        if typ in {'start','script','service'}:
            outs=_outgoing(definition,node['id']); instance.current_node_id=outs[0]['target'] if outs else None
        elif typ=='decision':
            chosen=None
            for edge in _outgoing(definition,node['id']):
                if edge.get('default') or _condition_ok(edge.get('condition',{}),instance.context or {}): chosen=edge; break
            if not chosen: instance.status='failed'; _event(db,instance,node['id'],'decision_no_match',actor_id); break
            instance.current_node_id=chosen['target']
        elif typ in {'task','approval'}:
            existing=db.scalar(select(WorkflowTask).where(WorkflowTask.instance_id==instance.id,WorkflowTask.node_id==node['id'],WorkflowTask.status=='open'))
            if not existing:
                sla=int(node.get('config',{}).get('sla_minutes',0) or 0)
                db.add(WorkflowTask(instance_id=instance.id,node_id=node['id'],title=node.get('name') or 'Tarefa',assignee_id=_resolve_assignee(db,node,instance),assignee_role=node.get('config',{}).get('assignee_role'),assignee_department_id=node.get('config',{}).get('department_id'),form_schema=node.get('config',{}).get('form_schema',{}),due_at=datetime.now(timezone.utc)+timedelta(minutes=sla) if sla else None))
                _event(db,instance,node['id'],'task_created',actor_id)
            break
        elif typ=='timer':
            minutes=int(node.get('config',{}).get('minutes',1)); existing=db.scalar(select(WorkflowTask).where(WorkflowTask.instance_id==instance.id,WorkflowTask.node_id==node['id'],WorkflowTask.status=='waiting'))
            if not existing: db.add(WorkflowTask(instance_id=instance.id,node_id=node['id'],title=node.get('name') or 'Timer',status='waiting',due_at=datetime.now(timezone.utc)+timedelta(minutes=minutes)))
            break
        elif typ=='end':
            instance.status='completed'; instance.finished_at=datetime.now(timezone.utc); _event(db,instance,node['id'],'instance_completed',actor_id); break
        else: instance.status='failed'; _event(db,instance,node['id'],'unsupported_node',actor_id); break
    db.commit(); db.refresh(instance); return instance

def start_instance(db,wf,user,context):
    start=next((n for n in (wf.definition or {}).get('nodes',[]) if n.get('type')=='start'),None)
    if not start: raise ValueError('Workflow sem nó inicial')
    instance=WorkflowInstance(workflow_id=wf.id,started_by=user.id,current_node_id=start['id'],context=context or {})
    db.add(instance); db.flush(); _event(db,instance,start['id'],'instance_started',user.id,context or {}); db.commit(); return advance(db,instance,user.id)
