from __future__ import annotations
from datetime import datetime, timezone
from typing import Any
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from app.models.entities import (AIAgent, AIAgentVersionV032, AIBudgetPolicyV032,
    AICostLedger, AIEvaluationRunV032, AIEvaluationSuiteV032)

BLOCKED_PROMPT_PATTERNS=("ignore previous instructions","reveal system prompt","show hidden prompt","bypass policy")

def validate_agent_definition(definition:dict[str,Any])->list[str]:
    errors=[]
    prompt=str(definition.get('system_prompt','')).strip()
    if len(prompt)<20: errors.append('O prompt do sistema deve possuir pelo menos 20 caracteres')
    lowered=prompt.lower()
    if any(p in lowered for p in BLOCKED_PROMPT_PATTERNS): errors.append('O prompt contém instrução potencialmente insegura')
    if not definition.get('model_id'): errors.append('Selecione um modelo')
    if len(definition.get('tool_codes',[]))>25: errors.append('Limite de 25 ferramentas por agente')
    if int(definition.get('max_context_chars',0) or 0)>100000: errors.append('Contexto máximo acima do permitido')
    return errors

def create_agent_version(db:Session,tenant_id:str,agent:AIAgent,user_id:str,notes:str='')->AIAgentVersionV032:
    current=db.scalar(select(func.max(AIAgentVersionV032.version)).where(AIAgentVersionV032.agent_id==agent.id)) or 0
    definition={'name':agent.name,'description':agent.description,'model_id':agent.model_id,
        'system_prompt':agent.system_prompt,'tool_codes':agent.tool_codes,'knowledge_base_ids':agent.knowledge_base_ids,
        'dataset_ids':agent.dataset_ids,'allowed_roles':agent.allowed_roles,'memory_enabled':agent.memory_enabled,
        'max_context_chars':agent.max_context_chars,'enabled':agent.enabled}
    errors=validate_agent_definition(definition)
    if errors: raise ValueError('; '.join(errors))
    item=AIAgentVersionV032(tenant_id=tenant_id,agent_id=agent.id,version=current+1,definition=definition,change_notes=notes,created_by=user_id)
    db.add(item); db.commit(); db.refresh(item); return item

def approve_version(db:Session,item:AIAgentVersionV032,user_id:str)->AIAgentVersionV032:
    item.status='approved'; item.approved_by=user_id; item.approved_at=datetime.now(timezone.utc)
    db.commit(); db.refresh(item); return item

def evaluate_case(case:dict[str,Any])->dict[str,Any]:
    expected=[str(x).lower() for x in case.get('expected_terms',[])]
    answer=str(case.get('sample_answer',case.get('expected_answer','')))
    low=answer.lower()
    matched=sum(1 for term in expected if term in low)
    score=100 if not expected else round((matched/len(expected))*100)
    forbidden=[str(x).lower() for x in case.get('forbidden_terms',[])]
    violations=[x for x in forbidden if x in low]
    if violations: score=max(0,score-50)
    return {'name':case.get('name','Caso'),'score':score,'matched_terms':matched,'violations':violations}

def run_evaluation(db:Session,tenant_id:str,suite:AIEvaluationSuiteV032,agent:AIAgent,user_id:str,version_id:str|None=None)->AIEvaluationRunV032:
    run=AIEvaluationRunV032(tenant_id=tenant_id,suite_id=suite.id,agent_id=agent.id,agent_version_id=version_id,started_by=user_id)
    db.add(run); db.flush()
    results=[evaluate_case(c) for c in (suite.cases or [])]
    run.results=results; run.score=round(sum(x['score'] for x in results)/len(results)) if results else 0
    minimum=int((suite.thresholds or {}).get('minimum_score',70)); run.status='passed' if run.score>=minimum else 'failed'
    run.finished_at=datetime.now(timezone.utc); db.commit(); db.refresh(run); return run

def budget_status(db:Session,tenant_id:str,policy:AIBudgetPolicyV032)->dict[str,Any]:
    start=datetime.now(timezone.utc).replace(day=1,hour=0,minute=0,second=0,microsecond=0)
    spent=db.scalar(select(func.coalesce(func.sum(AICostLedger.estimated_cost_micros),0)).where(AICostLedger.tenant_id==tenant_id,AICostLedger.created_at>=start)) or 0
    budget=policy.monthly_budget_micros or 0
    percent=round((spent/budget)*100,2) if budget else 0
    return {'spent_micros':int(spent),'budget_micros':int(budget),'percent':percent,'warning':bool(budget and percent>=policy.warning_percent),'blocked':bool(policy.hard_block and budget and spent>=budget)}
