from fastapi import APIRouter, Depends, Header, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy import select, func
from sqlalchemy.orm import Session
from app.api.deps import current_user
from app.core.database import get_db
from app.models.entities import LakehouseCatalogV035,LakehouseTableV035,LakehouseSnapshotV035,LakehouseDataFileV035,LakehouseMaintenanceRunV035,Tenant,User
from app.services.lakehouse_v035 import ALLOWED_CATALOGS,create_table,commit_files,evolve_schema,rollback_to_snapshot,run_maintenance,trino_sql
from app.services.saas_tenancy import resolve_membership
router=APIRouter(prefix='/api/v035',tags=['V035 Enterprise Lakehouse'])
ADMIN={'admin','director','manager','data_steward','data_engineer'}
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 de gestão necessário')
    return c
class CatalogIn(BaseModel): code:str=Field(min_length=2,max_length=80); name:str; catalog_type:str='native'; warehouse_uri:str='storage/lakehouse'; trino_endpoint:str=''; configuration:dict=Field(default_factory=dict)
class TableIn(BaseModel): catalog_id:str; dataset_id:str|None=None; namespace:str='default'; name:str; friendly_name:str=''; zone:str='bronze'; schema:list[dict]; partition_spec:list[dict]=Field(default_factory=list); properties:dict=Field(default_factory=dict)
class SchemaIn(BaseModel): schema:list[dict]
class CommitIn(BaseModel): files:list[dict]; operation:str=Field(default='append',pattern='^(append|overwrite|replace_partitions)$'); summary:dict=Field(default_factory=dict)
class MaintenanceIn(BaseModel): maintenance_type:str; parameters:dict=Field(default_factory=dict)
class QueryPlanIn(BaseModel): columns:list[str]|None=None; limit:int=Field(default=1000,ge=1,le=100000); snapshot_id:str|None=None
@router.get('/summary')
def summary(c=Depends(ctx),db:Session=Depends(get_db)):
    tid=c[0].id
    return {'version':'V035','catalogs':db.scalar(select(func.count()).select_from(LakehouseCatalogV035).where(LakehouseCatalogV035.tenant_id==tid)) or 0,'tables':db.scalar(select(func.count()).select_from(LakehouseTableV035).where(LakehouseTableV035.tenant_id==tid)) or 0,'snapshots':db.scalar(select(func.count()).select_from(LakehouseSnapshotV035).where(LakehouseSnapshotV035.tenant_id==tid)) or 0}
@router.post('/catalogs',status_code=201)
def catalog(p:CatalogIn,c=Depends(admin),db:Session=Depends(get_db)):
    if p.catalog_type not in ALLOWED_CATALOGS: raise HTTPException(400,'Tipo de catálogo inválido')
    item=LakehouseCatalogV035(tenant_id=c[0].id,created_by=c[1].id,**p.model_dump()); db.add(item); db.commit(); db.refresh(item); return item
@router.get('/catalogs')
def catalogs(c=Depends(ctx),db:Session=Depends(get_db)): return db.scalars(select(LakehouseCatalogV035).where(LakehouseCatalogV035.tenant_id==c[0].id).order_by(LakehouseCatalogV035.created_at.desc())).all()
@router.post('/tables',status_code=201)
def table(p:TableIn,c=Depends(admin),db:Session=Depends(get_db)):
    cat=db.get(LakehouseCatalogV035,p.catalog_id)
    if not cat or cat.tenant_id!=c[0].id: raise HTTPException(404,'Catálogo não encontrado')
    try: return create_table(db,cat,c[0].id,c[1].id,**p.model_dump(exclude={'catalog_id'}))
    except ValueError as e: raise HTTPException(400,str(e))
@router.get('/tables')
def tables(c=Depends(ctx),db:Session=Depends(get_db)): return db.scalars(select(LakehouseTableV035).where(LakehouseTableV035.tenant_id==c[0].id).order_by(LakehouseTableV035.updated_at.desc())).all()
def owned_table(db,tid,table_id):
    item=db.get(LakehouseTableV035,table_id)
    if not item or item.tenant_id!=tid: raise HTTPException(404,'Tabela não encontrada')
    return item
@router.put('/tables/{table_id}/schema')
def schema(table_id:str,p:SchemaIn,c=Depends(admin),db:Session=Depends(get_db)):
    item=owned_table(db,c[0].id,table_id)
    try: item.schema_json=evolve_schema(item.schema_json,p.schema); item.schema_version+=1; db.commit(); db.refresh(item); return item
    except ValueError as e: raise HTTPException(400,str(e))
@router.post('/tables/{table_id}/commit',status_code=201)
def commit(table_id:str,p:CommitIn,c=Depends(admin),db:Session=Depends(get_db)):
    item=owned_table(db,c[0].id,table_id); cat=db.get(LakehouseCatalogV035,item.catalog_id)
    try: return commit_files(db,cat,item,c[1].id,p.files,p.operation,p.summary)
    except ValueError as e: raise HTTPException(400,str(e))
@router.get('/tables/{table_id}/snapshots')
def snapshots(table_id:str,c=Depends(ctx),db:Session=Depends(get_db)):
    owned_table(db,c[0].id,table_id); return db.scalars(select(LakehouseSnapshotV035).where(LakehouseSnapshotV035.table_id==table_id).order_by(LakehouseSnapshotV035.committed_at.desc())).all()
@router.post('/tables/{table_id}/rollback/{snapshot_id}',status_code=201)
def rollback(table_id:str,snapshot_id:str,c=Depends(admin),db:Session=Depends(get_db)):
    item=owned_table(db,c[0].id,table_id); snap=db.get(LakehouseSnapshotV035,snapshot_id)
    if not snap: raise HTTPException(404,'Snapshot não encontrado')
    try: return rollback_to_snapshot(db,item,snap,c[1].id)
    except ValueError as e: raise HTTPException(400,str(e))
@router.post('/tables/{table_id}/maintenance',status_code=201)
def maintenance(table_id:str,p:MaintenanceIn,c=Depends(admin),db:Session=Depends(get_db)):
    item=owned_table(db,c[0].id,table_id); cat=db.get(LakehouseCatalogV035,item.catalog_id)
    try: return run_maintenance(db,cat,item,c[1].id,p.maintenance_type,p.parameters)
    except ValueError as e: raise HTTPException(400,str(e))
@router.get('/maintenance-runs')
def runs(c=Depends(ctx),db:Session=Depends(get_db)): return db.scalars(select(LakehouseMaintenanceRunV035).where(LakehouseMaintenanceRunV035.tenant_id==c[0].id).order_by(LakehouseMaintenanceRunV035.started_at.desc()).limit(100)).all()
@router.post('/tables/{table_id}/query-plan')
def query_plan(table_id:str,p:QueryPlanIn,c=Depends(ctx),db:Session=Depends(get_db)):
    item=owned_table(db,c[0].id,table_id)
    try: return {'engine':'trino' if db.get(LakehouseCatalogV035,item.catalog_id).trino_endpoint else 'native','sql':trino_sql(item,p.columns,p.limit,p.snapshot_id),'snapshot_id':p.snapshot_id or item.current_snapshot_id}
    except ValueError as e: raise HTTPException(400,str(e))
