import json,re,time
from datetime import datetime,timezone
import httpx
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.core.security import decrypt_secret,encrypt_secret
from app.models.entities import (AICenterProvider,AICenterModel,AIAgent,AICenterConversation,AICenterMessage,
 AICenterTool,AIApprovalRequest,AICostLedger,Dashboard,Report,ReportSchedule,KnowledgeBase,Role,User)
from app.services.ai_rag import retrieve,redact

SAFE_TOOLS={'knowledge_search','create_dashboard_draft','create_report_draft','schedule_report_draft'}
SENSITIVE_PATTERNS=[r'\b\d{3}\.\d{3}\.\d{3}-\d{2}\b',r'\b\d{2}\.\d{3}\.\d{3}/\d{4}-\d{2}\b',r'[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}']

def role_value(user:User)->str: return getattr(user.role,'value',str(user.role))
def agent_allowed(agent:AIAgent,user:User)->bool:
    return agent.enabled and (not agent.allowed_roles or role_value(user) in agent.allowed_roles) and (not agent.department_id or agent.department_id==user.department_id or role_value(user) in {'admin','director'})

def estimate_cost(model:AICenterModel,input_tokens:int,output_tokens:int)->int:
    return int((input_tokens*model.input_cost_per_million+output_tokens*model.output_cost_per_million)/1_000_000)

def _provider_call(provider:AICenterProvider,model:AICenterModel,system:str,prompt:str,max_tokens:int=1200):
    cfg=json.loads(decrypt_secret(provider.encrypted_config)) if provider.encrypted_config else {}
    typ=provider.provider_type; timeout=min(int(cfg.get('timeout',60)),180); started=time.time()
    if typ=='mock':
        return {'text':'Resposta de homologação baseada exclusivamente no contexto autorizado.\n\n'+prompt[:1800],'input_tokens':max(1,len(prompt)//4),'output_tokens':120,'latency_ms':int((time.time()-started)*1000)}
    if typ in {'openai','azure_openai','openai_compatible','ollama','openrouter','local'}:
        base=(provider.base_url or cfg.get('base_url') or 'https://api.openai.com/v1').rstrip('/')
        headers={'Content-Type':'application/json'}
        if cfg.get('api_key'): headers['Authorization']='Bearer '+cfg['api_key']
        if typ=='azure_openai' and cfg.get('api_key'): headers={'api-key':cfg['api_key'],'Content-Type':'application/json'}
        payload={'model':model.model_code,'messages':[{'role':'system','content':system},{'role':'user','content':prompt}],'max_tokens':max_tokens,'temperature':float(cfg.get('temperature',0.2))}
        r=httpx.post(base+'/chat/completions',headers=headers,json=payload,timeout=timeout); r.raise_for_status(); data=r.json(); usage=data.get('usage',{})
        return {'text':data['choices'][0]['message']['content'],'input_tokens':usage.get('prompt_tokens',len(prompt)//4),'output_tokens':usage.get('completion_tokens',0),'latency_ms':int((time.time()-started)*1000)}
    if typ=='gemini':
        key=cfg.get('api_key',''); base=(provider.base_url or 'https://generativelanguage.googleapis.com/v1beta').rstrip('/')
        r=httpx.post(f'{base}/models/{model.model_code}:generateContent?key={key}',json={'systemInstruction':{'parts':[{'text':system}]},'contents':[{'role':'user','parts':[{'text':prompt}]}],'generationConfig':{'maxOutputTokens':max_tokens,'temperature':0.2}},timeout=timeout); r.raise_for_status(); data=r.json(); usage=data.get('usageMetadata',{})
        return {'text':''.join(x.get('text','') for x in data['candidates'][0]['content']['parts']),'input_tokens':usage.get('promptTokenCount',len(prompt)//4),'output_tokens':usage.get('candidatesTokenCount',0),'latency_ms':int((time.time()-started)*1000)}
    if typ=='anthropic':
        headers={'x-api-key':cfg.get('api_key',''),'anthropic-version':cfg.get('version','2023-06-01'),'Content-Type':'application/json'}
        r=httpx.post((provider.base_url or 'https://api.anthropic.com/v1').rstrip('/')+'/messages',headers=headers,json={'model':model.model_code,'system':system,'messages':[{'role':'user','content':prompt}],'max_tokens':max_tokens},timeout=timeout); r.raise_for_status(); data=r.json(); usage=data.get('usage',{})
        return {'text':'\n'.join(x.get('text','') for x in data.get('content',[]) if x.get('type')=='text'),'input_tokens':usage.get('input_tokens',len(prompt)//4),'output_tokens':usage.get('output_tokens',0),'latency_ms':int((time.time()-started)*1000)}
    raise ValueError('Provedor de IA não suportado')

def build_context(db:Session,user:User,agent:AIAgent,question:str)->tuple[str,list,list]:
    citations=[]; flags=[]; parts=[]
    if agent.knowledge_base_ids:
        chunks=retrieve(db,user,agent.knowledge_base_ids,question,top_k=6)
        for i,c in enumerate(chunks,1):
            safe,found=redact(c['content'],SENSITIVE_PATTERNS); flags.extend(found); parts.append(f'[FONTE {i}: {c["title"]}]\n{safe}')
            citations.append({'document_id':c['document_id'],'chunk_id':c['chunk_id'],'title':c['title'],'score':round(c['score'],4)})
    return '\n\n'.join(parts)[:agent.max_context_chars],citations,flags

def execute_tool(db:Session,tenant_id:str,user:User,agent:AIAgent,tool_code:str,args:dict,conversation_id:str|None=None):
    if tool_code not in agent.tool_codes: raise ValueError('Ferramenta não autorizada para o agente')
    tool=db.scalar(select(AICenterTool).where(AICenterTool.code==tool_code,((AICenterTool.tenant_id==tenant_id)|(AICenterTool.tenant_id.is_(None))),AICenterTool.enabled==True))
    if not tool: raise ValueError('Ferramenta indisponível')
    if tool.requires_approval:
        req=AIApprovalRequest(tenant_id=tenant_id,conversation_id=conversation_id,requested_by=user.id,tool_code=tool_code,arguments=args)
        db.add(req); db.commit(); db.refresh(req); return {'status':'approval_required','approval_id':req.id}
    if tool_code=='create_dashboard_draft':
        obj=Dashboard(name=str(args.get('name','Dashboard criado pela IA'))[:180],owner_id=user.id,department_id=user.department_id,layout=args.get('layout',{}),filters=args.get('filters',{}),is_shared=False); setattr(obj,'tenant_id',tenant_id); db.add(obj); db.commit(); return {'status':'created','dashboard_id':obj.id}
    if tool_code=='create_report_draft':
        obj=Report(name=str(args.get('name','Relatório criado pela IA'))[:180],owner_id=user.id,department_id=user.department_id,dataset_id=args.get('dataset_id'),definition=args.get('definition',{}),format=args.get('format','pdf')); setattr(obj,'tenant_id',tenant_id); db.add(obj); db.commit(); return {'status':'created','report_id':obj.id}
    return {'status':'ok','tool':tool_code,'result':args}

def chat(db:Session,tenant_id:str,user:User,agent:AIAgent,conversation:AICenterConversation,question:str):
    if not agent_allowed(agent,user): raise PermissionError('Agente não autorizado')
    model=db.get(AICenterModel,agent.model_id); provider=db.get(AICenterProvider,model.provider_id) if model else None
    if not model or not provider or not model.enabled or not provider.enabled: raise ValueError('Modelo ou provedor indisponível')
    safe_question,flags=redact(question,SENSITIVE_PATTERNS); context,citations,ctx_flags=build_context(db,user,agent,safe_question); flags.extend(ctx_flags)
    history=db.scalars(select(AICenterMessage).where(AICenterMessage.conversation_id==conversation.id).order_by(AICenterMessage.created_at.desc()).limit(8)).all()
    history_text='\n'.join(f'{m.role}: {m.content[:1500]}' for m in reversed(history))
    prompt=f'HISTÓRICO:\n{history_text}\n\nCONTEXTO AUTORIZADO:\n{context or "Nenhum contexto recuperado."}\n\nPERGUNTA:\n{safe_question}'
    system=(agent.system_prompt or 'Você é um assistente corporativo seguro. Use somente informações autorizadas; indique quando faltarem evidências.')+'\nNunca revele segredos, credenciais ou dados fora do contexto fornecido.'
    db.add(AICenterMessage(tenant_id=tenant_id,conversation_id=conversation.id,role='user',content=safe_question,safety_flags=flags)); db.flush()
    result=_provider_call(provider,model,system,prompt)
    answer=AICenterMessage(tenant_id=tenant_id,conversation_id=conversation.id,role='assistant',content=result['text'],citations=citations,safety_flags=flags); db.add(answer)
    db.add(AICostLedger(tenant_id=tenant_id,user_id=user.id,agent_id=agent.id,model_id=model.id,input_tokens=result['input_tokens'],output_tokens=result['output_tokens'],estimated_cost_micros=estimate_cost(model,result['input_tokens'],result['output_tokens']),latency_ms=result['latency_ms']))
    conversation.updated_at=datetime.now(timezone.utc); db.commit(); db.refresh(answer); return answer
