import json
from datetime import datetime
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.core.security import encrypt_text
from app.models.entities import APICatalogV037,APICredentialV037,APIPolicyV037,APISubscriptionV037,WebhookEndpointV037,APICallAuditV037,Tenant,User
from app.services.api_gateway_v037 import API_TYPES,AUTH_TYPES,POLICY_TYPES,generate_consumer_key,hash_secret,normalize_openapi,normalize_wsdl,safe_code,validate_policy,validate_url
from app.services.saas_tenancy import resolve_membership
router=APIRouter(prefix='/api/v037',tags=['V037 API Gateway and Integration 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 técnico ou gestor necessário')
    return c
class APIIn(BaseModel): code:str; name:str; description:str=''; api_type:str='rest'; base_url:str=''; specification:dict=Field(default_factory=dict); version:str='1.0.0'; visibility:str='private'
class ImportIn(BaseModel): format:str='openapi'; document:dict|str
class CredentialIn(BaseModel): name:str; auth_type:str; public_config:dict=Field(default_factory=dict); secret:str=''
class PolicyIn(BaseModel): name:str; policy_type:str; configuration:dict=Field(default_factory=dict); priority:int=100; enabled:bool=True
class SubscriptionIn(BaseModel): consumer_name:str; consumer_type:str='application'; scopes:list[str]=Field(default_factory=list); quota_per_minute:int=60; expires_at:datetime|None=None
class WebhookIn(BaseModel): name:str; target_url:str; event_types:list[str]; secret:str=''; retry_policy:dict=Field(default_factory=lambda:{'max_attempts':3,'backoff_ms':500})
@router.get('/summary')
def summary(c=Depends(ctx),db:Session=Depends(get_db)):
    tid=c[0].id
    return {'version':'V037','api_types':sorted(API_TYPES),'auth_types':sorted(AUTH_TYPES),'policy_types':sorted(POLICY_TYPES),'apis':db.scalar(select(func.count()).select_from(APICatalogV037).where(APICatalogV037.tenant_id==tid)) or 0,'subscriptions':db.scalar(select(func.count()).select_from(APISubscriptionV037).where(APISubscriptionV037.tenant_id==tid)) or 0}
@router.post('/apis',status_code=201)
def create_api(p:APIIn,c=Depends(admin),db:Session=Depends(get_db)):
    if p.api_type not in API_TYPES: raise HTTPException(400,'Tipo de API inválido')
    if p.base_url:
        try: validate_url(p.base_url)
        except ValueError as e: raise HTTPException(400,str(e))
    item=APICatalogV037(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('/apis')
def apis(c=Depends(ctx),db:Session=Depends(get_db)): return db.scalars(select(APICatalogV037).where(APICatalogV037.tenant_id==c[0].id).order_by(APICatalogV037.updated_at.desc())).all()
def owned(db,tid,api_id):
    item=db.get(APICatalogV037,api_id)
    if not item or item.tenant_id!=tid: raise HTTPException(404,'API não encontrada')
    return item
@router.post('/apis/import',status_code=201)
def import_api(p:ImportIn,c=Depends(admin),db:Session=Depends(get_db)):
    try:
        normalized=normalize_openapi(p.document) if p.format in {'openapi','swagger'} and isinstance(p.document,dict) else normalize_wsdl(str(p.document))
    except ValueError as e: raise HTTPException(400,str(e))
    item=APICatalogV037(tenant_id=c[0].id,owner_id=c[1].id,code=safe_code(normalized['title']),name=normalized['title'],api_type='soap' if p.format=='wsdl' else 'rest',base_url=normalized.get('base_url',''),specification=normalized)
    db.add(item); db.commit(); db.refresh(item); return item
@router.post('/apis/{api_id}/credentials',status_code=201)
def credential(api_id:str,p:CredentialIn,c=Depends(admin),db:Session=Depends(get_db)):
    owned(db,c[0].id,api_id)
    if p.auth_type not in AUTH_TYPES: raise HTTPException(400,'Autenticação inválida')
    item=APICredentialV037(tenant_id=c[0].id,api_id=api_id,name=p.name,auth_type=p.auth_type,public_config=p.public_config,encrypted_secret=encrypt_text(p.secret) if p.secret else '',created_by=c[1].id); db.add(item); db.commit(); db.refresh(item); return item
@router.post('/apis/{api_id}/policies',status_code=201)
def policy(api_id:str,p:PolicyIn,c=Depends(admin),db:Session=Depends(get_db)):
    owned(db,c[0].id,api_id)
    try: config=validate_policy(p.policy_type,p.configuration)
    except ValueError as e: raise HTTPException(400,str(e))
    item=APIPolicyV037(tenant_id=c[0].id,api_id=api_id,name=p.name,policy_type=p.policy_type,configuration=config,priority=p.priority,enabled=p.enabled,created_by=c[1].id); db.add(item); db.commit(); db.refresh(item); return item
@router.get('/apis/{api_id}/policies')
def policies(api_id:str,c=Depends(ctx),db:Session=Depends(get_db)):
    owned(db,c[0].id,api_id); return db.scalars(select(APIPolicyV037).where(APIPolicyV037.api_id==api_id).order_by(APIPolicyV037.priority)).all()
@router.post('/apis/{api_id}/publish')
def publish(api_id:str,c=Depends(admin),db:Session=Depends(get_db)):
    item=owned(db,c[0].id,api_id)
    if not item.specification: raise HTTPException(400,'API sem especificação')
    item.status='published'; db.commit(); db.refresh(item); return item
@router.post('/apis/{api_id}/subscriptions',status_code=201)
def subscription(api_id:str,p:SubscriptionIn,c=Depends(admin),db:Session=Depends(get_db)):
    item=owned(db,c[0].id,api_id)
    if item.status!='published': raise HTTPException(409,'Publique a API antes de criar consumidores')
    raw,digest=generate_consumer_key(); sub=APISubscriptionV037(tenant_id=c[0].id,api_id=api_id,key_hash=digest,created_by=c[1].id,**p.model_dump()); db.add(sub); db.commit(); db.refresh(sub); return {'subscription':sub,'api_key':raw}
@router.get('/subscriptions')
def subscriptions(c=Depends(ctx),db:Session=Depends(get_db)): return db.scalars(select(APISubscriptionV037).where(APISubscriptionV037.tenant_id==c[0].id).order_by(APISubscriptionV037.created_at.desc())).all()
@router.post('/webhooks',status_code=201)
def webhook(p:WebhookIn,c=Depends(admin),db:Session=Depends(get_db)):
    try: validate_url(p.target_url)
    except ValueError as e: raise HTTPException(400,str(e))
    retry=validate_policy('retry',p.retry_policy)
    item=WebhookEndpointV037(tenant_id=c[0].id,name=p.name,target_url=p.target_url,event_types=p.event_types,secret_hash=hash_secret(p.secret) if p.secret else '',retry_policy=retry,created_by=c[1].id); db.add(item); db.commit(); db.refresh(item); return item
@router.get('/webhooks')
def webhooks(c=Depends(ctx),db:Session=Depends(get_db)): return db.scalars(select(WebhookEndpointV037).where(WebhookEndpointV037.tenant_id==c[0].id).order_by(WebhookEndpointV037.created_at.desc())).all()
@router.get('/audit')
def audit(c=Depends(ctx),db:Session=Depends(get_db)): return db.scalars(select(APICallAuditV037).where(APICallAuditV037.tenant_id==c[0].id).order_by(APICallAuditV037.created_at.desc()).limit(200)).all()
