import base64, hashlib, hmac, json, time
from datetime import datetime, timezone
from typing import Any
import httpx
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 current_user
from app.core.database import get_db
from app.core.security import encrypt_secret, decrypt_secret
from app.models.entities import *
from app.services.workflow import start_instance, advance

router=APIRouter(prefix='/api/v1/v007',tags=['V007 Workflow Alerts API Studio'])

class WorkflowPayload(BaseModel):
    name:str; description:str=''; department_id:str|None=None; definition:dict; status:str='draft'
class StartPayload(BaseModel): context:dict={}
class CompleteTaskPayload(BaseModel): result:dict={}; decision:str|None=None
class AlertPayload(BaseModel):
    name:str; department_id:str|None=None; dataset_id:str; query_definition:dict; operator:str='lt'; threshold:str='0'; severity:str='warning'; channels:list[str]=['notification']; recipients:list[str]=[]; cooldown_minutes:int=60; schedule_cron:str='*/15 * * * *'; enabled:bool=True
class CredentialPayload(BaseModel): name:str; auth_type:str; config:dict
class ApiTestPayload(BaseModel): route:str; method:str='GET'; headers:dict={}; query:dict={}; body:Any=None; credential_id:str|None=None

def editable(obj,u): return obj.owner_id==u.id or u.role in {Role.admin,Role.director} or (u.role==Role.manager and obj.department_id==u.department_id)
def validate_definition(d):
    nodes=d.get('nodes',[]); edges=d.get('edges',[]); ids=[n.get('id') for n in nodes]
    if len(ids)!=len(set(ids)) or None in ids: raise HTTPException(400,'IDs de nós inválidos ou duplicados')
    if sum(1 for n in nodes if n.get('type')=='start')!=1: raise HTTPException(400,'O processo deve possuir exatamente um nó start')
    if not any(n.get('type')=='end' for n in nodes): raise HTTPException(400,'O processo deve possuir pelo menos um nó end')
    allowed={'start','task','approval','decision','timer','service','script','end'}
    if any(n.get('type') not in allowed for n in nodes): raise HTTPException(400,'Tipo de nó não suportado')
    if any(e.get('source') not in ids or e.get('target') not in ids for e in edges): raise HTTPException(400,'Aresta aponta para nó inexistente')

@router.post('/workflows')
def create_workflow(p:WorkflowPayload,db:Session=Depends(get_db),u:User=Depends(current_user)):
    validate_definition(p.definition); row=WorkflowDefinition(**p.model_dump(),owner_id=u.id); db.add(row); db.commit(); db.refresh(row); return {'id':row.id,'version':row.version}
@router.get('/workflows')
def list_workflows(db:Session=Depends(get_db),u:User=Depends(current_user)):
    q=select(WorkflowDefinition).where((WorkflowDefinition.owner_id==u.id)|(WorkflowDefinition.department_id==u.department_id)).order_by(WorkflowDefinition.updated_at.desc())
    return [{'id':x.id,'name':x.name,'description':x.description,'status':x.status,'version':x.version,'definition':x.definition,'department_id':x.department_id} for x in db.scalars(q).all()]
@router.put('/workflows/{wid}')
def update_workflow(wid:str,p:WorkflowPayload,db:Session=Depends(get_db),u:User=Depends(current_user)):
    row=db.get(WorkflowDefinition,wid)
    if not row or not editable(row,u): raise HTTPException(404,'Workflow não encontrado')
    validate_definition(p.definition)
    for k,v in p.model_dump().items(): setattr(row,k,v)
    row.version+=1; db.commit(); return {'id':row.id,'version':row.version}
@router.post('/workflows/{wid}/start')
def start(wid:str,p:StartPayload,db:Session=Depends(get_db),u:User=Depends(current_user)):
    wf=db.get(WorkflowDefinition,wid)
    if not wf or wf.status not in {'published','active'}: raise HTTPException(409,'Workflow não publicado')
    try: inst=start_instance(db,wf,u,p.context)
    except ValueError as e: raise HTTPException(400,str(e))
    return {'id':inst.id,'status':inst.status,'current_node_id':inst.current_node_id}
@router.get('/workflow-instances')
def instances(db:Session=Depends(get_db),u:User=Depends(current_user)):
    q=select(WorkflowInstance).where(WorkflowInstance.started_by==u.id).order_by(WorkflowInstance.started_at.desc())
    return [{'id':x.id,'workflow_id':x.workflow_id,'status':x.status,'current_node_id':x.current_node_id,'context':x.context,'started_at':x.started_at} for x in db.scalars(q).all()]
@router.get('/workflow-tasks')
def tasks(db:Session=Depends(get_db),u:User=Depends(current_user)):
    q=select(WorkflowTask).where(WorkflowTask.status=='open').where((WorkflowTask.assignee_id==u.id)|(WorkflowTask.assignee_role==u.role.value)|(WorkflowTask.assignee_department_id==u.department_id))
    return [{'id':x.id,'instance_id':x.instance_id,'node_id':x.node_id,'title':x.title,'form_schema':x.form_schema,'due_at':x.due_at,'status':x.status} for x in db.scalars(q.order_by(WorkflowTask.due_at.asc())).all()]
@router.post('/workflow-tasks/{tid}/complete')
def complete_task(tid:str,p:CompleteTaskPayload,db:Session=Depends(get_db),u:User=Depends(current_user)):
    task=db.get(WorkflowTask,tid)
    if not task or task.status!='open': raise HTTPException(404,'Tarefa não encontrada')
    if task.assignee_id not in {None,u.id} and task.assignee_role!=u.role.value and task.assignee_department_id!=u.department_id and u.role!=Role.admin: raise HTTPException(403,'Sem acesso')
    inst=db.get(WorkflowInstance,task.instance_id); wf=db.get(WorkflowDefinition,inst.workflow_id); task.status='completed'; task.result=p.result; task.completed_at=datetime.now(timezone.utc); inst.context={**(inst.context or {}),**p.result,'decision':p.decision}
    edges=[e for e in (wf.definition or {}).get('edges',[]) if e.get('source')==task.node_id]
    chosen=next((e for e in edges if p.decision and e.get('label')==p.decision),None) or (edges[0] if edges else None)
    if chosen: inst.current_node_id=chosen['target']
    else: inst.status='completed'; inst.finished_at=datetime.now(timezone.utc)
    db.commit(); advance(db,inst,u.id); return {'instance_id':inst.id,'status':inst.status,'current_node_id':inst.current_node_id}

@router.post('/alerts')
def create_alert(p:AlertPayload,db:Session=Depends(get_db),u:User=Depends(current_user)):
    row=AlertRule(**p.model_dump(),owner_id=u.id); db.add(row); db.commit(); db.refresh(row); return {'id':row.id}
@router.get('/alerts')
def list_alerts(db:Session=Depends(get_db),u:User=Depends(current_user)):
    q=select(AlertRule).where((AlertRule.owner_id==u.id)|(AlertRule.department_id==u.department_id)).order_by(AlertRule.created_at.desc())
    return [{'id':x.id,'name':x.name,'dataset_id':x.dataset_id,'operator':x.operator,'threshold':x.threshold,'severity':x.severity,'channels':x.channels,'last_value':x.last_value,'last_triggered_at':x.last_triggered_at,'enabled':x.enabled} for x in db.scalars(q).all()]
@router.get('/alert-events')
def alert_events(db:Session=Depends(get_db),u:User=Depends(current_user)):
    ids=select(AlertRule.id).where((AlertRule.owner_id==u.id)|(AlertRule.department_id==u.department_id))
    return [{'id':x.id,'rule_id':x.rule_id,'status':x.status,'value':x.value,'message':x.message,'delivery_results':x.delivery_results,'created_at':x.created_at} for x in db.scalars(select(AlertEvent).where(AlertEvent.rule_id.in_(ids)).order_by(AlertEvent.created_at.desc()).limit(200)).all()]

@router.post('/apis/{api_id}/credentials')
def create_credential(api_id:str,p:CredentialPayload,db:Session=Depends(get_db),u:User=Depends(current_user)):
    api=db.get(ApiDefinition,api_id)
    if not api or not editable(api,u): raise HTTPException(404,'API não encontrada')
    if p.auth_type not in {'api_key','bearer','basic','oauth2_client_credentials','hmac'}: raise HTTPException(400,'Autenticação não suportada')
    row=ApiCredential(api_definition_id=api_id,name=p.name,auth_type=p.auth_type,encrypted_config=encrypt_secret(json.dumps(p.config)),created_by=u.id); db.add(row); db.commit(); db.refresh(row); return {'id':row.id}

def _auth_headers(cred,method,path,body):
    if not cred: return {}
    cfg=json.loads(decrypt_secret(cred.encrypted_config)); typ=cred.auth_type
    if typ=='api_key': return {cfg.get('header','X-API-Key'):cfg.get('value','')}
    if typ=='bearer': return {'Authorization':'Bearer '+cfg.get('token','')}
    if typ=='basic': return {'Authorization':'Basic '+base64.b64encode(f"{cfg.get('username','')}:{cfg.get('password','')}".encode()).decode()}
    if typ=='oauth2_client_credentials':
        r=httpx.post(cfg['token_url'],data={'grant_type':'client_credentials','client_id':cfg['client_id'],'client_secret':cfg['client_secret'],'scope':cfg.get('scope','')},timeout=20); r.raise_for_status(); return {'Authorization':'Bearer '+r.json()['access_token']}
    if typ=='hmac':
        ts=str(int(time.time())); raw=json.dumps(body or {},separators=(',',':'),sort_keys=True); canonical='\n'.join([method.upper(),path,ts,raw]); sig=hmac.new(cfg['secret'].encode(),canonical.encode(),hashlib.sha256).hexdigest(); return {cfg.get('key_header','X-API-Key'):cfg.get('key',''),cfg.get('timestamp_header','X-Timestamp'):ts,cfg.get('signature_header','X-Signature'):sig}
    return {}

@router.post('/apis/{api_id}/test')
def test_api(api_id:str,p:ApiTestPayload,db:Session=Depends(get_db),u:User=Depends(current_user)):
    api=db.get(ApiDefinition,api_id)
    if not api or not editable(api,u): raise HTTPException(404,'API não encontrada')
    allowed={(r.get('method','GET').upper(),r.get('path')) for r in api.generated_routes}
    if (p.method.upper(),p.route) not in allowed: raise HTTPException(400,'Rota não cadastrada')
    cred=db.get(ApiCredential,p.credential_id) if p.credential_id else None
    if cred and cred.api_definition_id!=api_id: raise HTTPException(400,'Credencial inválida')
    headers={**p.headers,**_auth_headers(cred,p.method,p.route,p.body)}; start=time.monotonic()
    try:
        r=httpx.request(p.method.upper(),api.base_url.rstrip('/')+p.route,headers=headers,params=p.query,json=p.body,timeout=30)
        duration=int((time.monotonic()-start)*1000); content=r.json() if 'json' in r.headers.get('content-type','') else {'text':r.text[:5000]}
        db.add(ApiExecution(api_definition_id=api_id,user_id=u.id,route=p.route,method=p.method.upper(),response_status=r.status_code,duration_ms=duration,success=r.is_success)); db.commit(); return {'status_code':r.status_code,'duration_ms':duration,'headers':dict(r.headers),'data':content}
    except Exception as exc:
        db.add(ApiExecution(api_definition_id=api_id,user_id=u.id,route=p.route,method=p.method.upper(),success=False,error=str(exc))); db.commit(); raise HTTPException(502,str(exc))
