import hashlib, secrets
from datetime import datetime, timedelta, timezone
from typing import Any
import httpx
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import StreamingResponse
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 decrypt_secret
from app.models.entities import *
from app.services.storage import ObjectStorage

router=APIRouter(prefix='/api/v1/v006',tags=['V006 Enterprise Experience'])

class LayoutPayload(BaseModel): positions:dict[str,dict]=Field(default_factory=dict)
class CrossFilterPayload(BaseModel): source_widget_id:str; column:str; value:Any
class FlowPayload(BaseModel):
    name:str; api_definition_id:str; department_id:str|None=None; auth_config:dict={}; request_mapping:dict={}; response_mapping:dict={}; transformations:list[dict]=[]; retry_policy:dict={'attempts':3,'backoff_seconds':2}; enabled:bool=True
class DownloadPayload(BaseModel): object_key:str; filename:str; media_type:str='application/octet-stream'; expires_minutes:int=Field(default=15,ge=1,le=1440); max_downloads:int=Field(default=1,ge=1,le=20)

def can_edit(d,u): return d.owner_id==u.id or u.role in {Role.admin,Role.director} or (u.role==Role.manager and d.department_id==u.department_id)

@router.put('/dashboards/{dashboard_id}/layout')
def save_layout(dashboard_id:str,p:LayoutPayload,db:Session=Depends(get_db),u:User=Depends(current_user)):
    d=db.get(Dashboard,dashboard_id)
    if not d or not can_edit(d,u): raise HTTPException(403,'Sem permissão')
    widgets={w.id:w for w in db.scalars(select(DashboardWidget).where(DashboardWidget.dashboard_id==dashboard_id)).all()}
    for wid,pos in p.positions.items():
        if wid in widgets: widgets[wid].position={k:int(pos.get(k,widgets[wid].position.get(k,0))) for k in ('x','y','w','h')}
    db.commit(); return {'updated':len([x for x in p.positions if x in widgets])}

@router.post('/dashboards/{dashboard_id}/cross-filter')
def cross_filter(dashboard_id:str,p:CrossFilterPayload,db:Session=Depends(get_db),u:User=Depends(current_user)):
    d=db.get(Dashboard,dashboard_id)
    if not d or not (d.owner_id==u.id or d.is_shared or d.department_id==u.department_id): raise HTTPException(404,'Dashboard não encontrado')
    widgets=db.scalars(select(DashboardWidget).where(DashboardWidget.dashboard_id==dashboard_id)).all()
    return {'filter':{'column':p.column,'op':'eq','value':p.value},'target_widget_ids':[w.id for w in widgets if w.id!=p.source_widget_id]}

@router.post('/dashboards/{dashboard_id}/restore/{revision_id}')
def restore_revision(dashboard_id:str,revision_id:str,db:Session=Depends(get_db),u:User=Depends(current_user)):
    d=db.get(Dashboard,dashboard_id); rev=db.get(DashboardRevision,revision_id)
    if not d or not rev or rev.dashboard_id!=dashboard_id or not can_edit(d,u): raise HTTPException(404,'Revisão não encontrada')
    snap=rev.snapshot or {}; ds=snap.get('dashboard',{})
    for k in ('name','layout','filters','is_shared'):
        if k in ds: setattr(d,k,ds[k])
    for w in db.scalars(select(DashboardWidget).where(DashboardWidget.dashboard_id==dashboard_id)).all(): db.delete(w)
    for item in snap.get('widgets',[]): db.add(DashboardWidget(dashboard_id=dashboard_id,**item))
    db.commit(); return {'restored':True,'version':rev.version}

@router.get('/report-deliveries')
def deliveries(db:Session=Depends(get_db),u:User=Depends(current_user)):
    schedule_ids=select(ReportSchedule.id).where(ReportSchedule.owner_id==u.id)
    rows=db.scalars(select(ReportDelivery).where(ReportDelivery.schedule_id.in_(schedule_ids)).order_by(ReportDelivery.started_at.desc()).limit(200)).all()
    return [{'id':x.id,'schedule_id':x.schedule_id,'status':x.status,'formats':x.formats,'recipients':x.recipients,'object_keys':x.object_keys,'error':x.error,'started_at':x.started_at,'finished_at':x.finished_at} for x in rows]

@router.post('/secure-downloads')
def create_download(p:DownloadPayload,db:Session=Depends(get_db),u:User=Depends(current_user)):
    token=secrets.token_urlsafe(32); row=SecureDownload(token_hash=hashlib.sha256(token.encode()).hexdigest(),object_key=p.object_key,filename=p.filename,media_type=p.media_type,created_by=u.id,expires_at=datetime.now(timezone.utc)+timedelta(minutes=p.expires_minutes),max_downloads=p.max_downloads)
    db.add(row); db.commit(); return {'url':f'/api/v1/v006/download/{token}','expires_at':row.expires_at,'max_downloads':row.max_downloads}

@router.get('/download/{token}')
def download(token:str,db:Session=Depends(get_db)):
    row=db.scalar(select(SecureDownload).where(SecureDownload.token_hash==hashlib.sha256(token.encode()).hexdigest()))
    if not row or row.revoked or row.expires_at<datetime.now(timezone.utc) or row.download_count>=row.max_downloads: raise HTTPException(410,'Link inválido ou expirado')
    storage=ObjectStorage(); obj=storage.client.get_object(Bucket=storage.bucket,Key=row.object_key); row.download_count+=1; db.commit()
    return StreamingResponse(obj['Body'],media_type=row.media_type,headers={'Content-Disposition':f'attachment; filename="{row.filename}"','Cache-Control':'no-store'})

@router.post('/integration-flows')
def create_flow(p:FlowPayload,db:Session=Depends(get_db),u:User=Depends(current_user)):
    api=db.get(ApiDefinition,p.api_definition_id)
    if not api: raise HTTPException(404,'API não encontrada')
    flow=IntegrationFlow(**p.model_dump(),owner_id=u.id); db.add(flow); db.commit(); db.refresh(flow); return {'id':flow.id}

@router.get('/integration-flows')
def list_flows(db:Session=Depends(get_db),u:User=Depends(current_user)):
    q=select(IntegrationFlow).where((IntegrationFlow.owner_id==u.id)|(IntegrationFlow.department_id==u.department_id)).order_by(IntegrationFlow.created_at.desc())
    return [{'id':x.id,'name':x.name,'api_definition_id':x.api_definition_id,'auth_config':x.auth_config,'request_mapping':x.request_mapping,'response_mapping':x.response_mapping,'transformations':x.transformations,'retry_policy':x.retry_policy,'enabled':x.enabled} for x in db.scalars(q).all()]

def _map(payload,mapping): return {target:payload.get(source) for target,source in (mapping or {}).items()}
def _transform(payload,steps):
    out=dict(payload)
    for step in steps or []:
        op=step.get('op'); field=step.get('field')
        if op=='rename' and field in out: out[step.get('to')]=out.pop(field)
        elif op=='default' and field not in out: out[field]=step.get('value')
        elif op=='remove': out.pop(field,None)
        elif op=='string' and field in out: out[field]=str(out[field])
    return out

@router.post('/integration-flows/{flow_id}/execute')
def execute_flow(flow_id:str,payload:dict,db:Session=Depends(get_db),u:User=Depends(current_user)):
    flow=db.get(IntegrationFlow,flow_id); api=db.get(ApiDefinition,flow.api_definition_id) if flow else None
    if not flow or not api or not flow.enabled: raise HTTPException(404,'Fluxo não encontrado')
    if flow.owner_id!=u.id and flow.department_id!=u.department_id and u.role not in {Role.admin,Role.data_engineer}: raise HTTPException(403,'Sem acesso')
    route=payload.get('route'); method=payload.get('method','POST').upper(); allowed={(r.get('method','GET').upper(),r.get('path')) for r in api.generated_routes}
    if (method,route) not in allowed: raise HTTPException(400,'Rota não autorizada')
    body=_transform(_map(payload.get('data',{}),flow.request_mapping) if flow.request_mapping else payload.get('data',{}),flow.transformations)
    headers={'Accept':'application/json'}; secret=decrypt_secret(api.encrypted_auth) if api.encrypted_auth else ''
    if api.auth_type=='bearer': headers['Authorization']='Bearer '+secret
    elif api.auth_type=='api_key': headers[(flow.auth_config or {}).get('header','X-API-Key')]=secret
    attempts=max(1,min(int((flow.retry_policy or {}).get('attempts',3)),5)); last=None
    for _ in range(attempts):
        try:
            res=httpx.request(method,api.base_url.rstrip('/')+route,headers=headers,json=body,timeout=30); res.raise_for_status(); result=res.json() if 'json' in res.headers.get('content-type','') else {'text':res.text}; return {'status_code':res.status_code,'data':_map(result,flow.response_mapping) if flow.response_mapping else result}
        except Exception as exc: last=exc
    raise HTTPException(502,f'Falha após {attempts} tentativas: {last}')
