from datetime import datetime, timezone
from sqlalchemy import select, func
from sqlalchemy.orm import Session
from app.models.entities import (
    User, Tenant, TenantMembership, TenantResourceScope, TenantIsolationViolation,
    TenantMigrationBatch, TenantIsolationPolicy,
)

PLATFORM_ROLES={'admin','director'}
LEGACY_RESOURCE_MAP={
    'data_source':'data_sources','dataset':'datasets','dashboard':'dashboards','report':'reports',
    'automation':'automations','message':'messages','api_definition':'api_definitions',
    'ai_conversation':'ai_conversations','workflow_definition':'workflow_definitions_v007',
    'knowledge_base':'knowledge_bases_v008','communication_channel':'communication_channels_v009',
    'semantic_model':'semantic_models_v015','semantic_execution':'semantic_executions_v016',
    'federated_job':'federated_query_jobs_v017','query_export':'query_exports_v018',
    'object_artifact':'object_storage_artifacts_v019',
}

def get_policy(db:Session,tenant_id:str)->TenantIsolationPolicy:
    policy=db.scalar(select(TenantIsolationPolicy).where(TenantIsolationPolicy.tenant_id==tenant_id))
    if not policy:
        policy=TenantIsolationPolicy(tenant_id=tenant_id)
        db.add(policy); db.commit(); db.refresh(policy)
    return policy

def user_has_tenant_access(db:Session,user:User,tenant_id:str)->bool:
    role=getattr(user.role,'value',user.role)
    if role in PLATFORM_ROLES:
        return True
    return bool(db.scalar(select(TenantMembership.id).where(
        TenantMembership.tenant_id==tenant_id,TenantMembership.user_id==user.id,
        TenantMembership.status=='active')))

def record_violation(db:Session,tenant_id:str|None,user_id:str|None,resource_type:str,
                     resource_id:str='',action:str='read',reason:str='tenant_mismatch',
                     request_id:str='',details:dict|None=None)->TenantIsolationViolation:
    item=TenantIsolationViolation(tenant_id=tenant_id,user_id=user_id,resource_type=resource_type,
        resource_id=resource_id,action=action,reason=reason,request_id=request_id,details=details or {})
    db.add(item); db.commit(); db.refresh(item); return item

def bind_resource(db:Session,tenant_id:str,resource_type:str,resource_id:str,actor_id:str|None=None,
                  source_table:str='',isolation_mode:str='physical')->TenantResourceScope:
    if resource_type not in LEGACY_RESOURCE_MAP:
        raise ValueError('Tipo de recurso não governado')
    existing=db.scalar(select(TenantResourceScope).where(
        TenantResourceScope.resource_type==resource_type,TenantResourceScope.resource_id==resource_id))
    if existing:
        if existing.tenant_id!=tenant_id: raise ValueError('Recurso já pertence a outro tenant')
        return existing
    item=TenantResourceScope(tenant_id=tenant_id,resource_type=resource_type,resource_id=resource_id,
        source_table=source_table or LEGACY_RESOURCE_MAP[resource_type],isolation_mode=isolation_mode,
        assigned_by=actor_id)
    db.add(item); db.commit(); db.refresh(item); return item

def assert_resource_access(db:Session,user:User,tenant_id:str,resource_type:str,resource_id:str,
                           action:str='read',request_id:str='')->TenantResourceScope:
    if not user_has_tenant_access(db,user,tenant_id):
        record_violation(db,tenant_id,user.id,resource_type,resource_id,action,'membership_denied',request_id)
        raise PermissionError('Usuário não pertence ao tenant')
    policy=get_policy(db,tenant_id)
    scope=db.scalar(select(TenantResourceScope).where(
        TenantResourceScope.resource_type==resource_type,TenantResourceScope.resource_id==resource_id))
    if not scope:
        if policy.block_unscoped_resources:
            if policy.audit_denied_access:
                record_violation(db,tenant_id,user.id,resource_type,resource_id,action,'unscoped_resource',request_id)
            raise PermissionError('Recurso legado ainda não foi atribuído a um tenant')
        return None
    if scope.tenant_id!=tenant_id:
        if policy.audit_denied_access:
            record_violation(db,tenant_id,user.id,resource_type,resource_id,action,'cross_tenant_access',request_id,
                {'owner_tenant_id':scope.tenant_id})
        raise PermissionError('Acesso cruzado entre tenants bloqueado')
    return scope

def start_migration(db:Session,tenant_id:str,resource_type:str,user_id:str,dry_run:bool=True,strategy:str='manual')->TenantMigrationBatch:
    if resource_type not in LEGACY_RESOURCE_MAP: raise ValueError('Tipo de recurso não governado')
    batch=TenantMigrationBatch(tenant_id=tenant_id,resource_type=resource_type,status='running',
        strategy=strategy,dry_run=dry_run,requested_by=user_id,started_at=datetime.now(timezone.utc))
    db.add(batch); db.commit(); db.refresh(batch); return batch

def finish_migration(db:Session,batch:TenantMigrationBatch,resource_ids:list[str])->TenantMigrationBatch:
    batch.scanned_count=len(resource_ids)
    assigned=skipped=errors=0
    for rid in resource_ids:
        try:
            existing=db.scalar(select(TenantResourceScope).where(
                TenantResourceScope.resource_type==batch.resource_type,TenantResourceScope.resource_id==rid))
            if existing: skipped+=1
            elif not batch.dry_run:
                db.add(TenantResourceScope(tenant_id=batch.tenant_id,resource_type=batch.resource_type,
                    resource_id=rid,source_table=LEGACY_RESOURCE_MAP[batch.resource_type],
                    isolation_mode='registry',assigned_by=batch.requested_by)); assigned+=1
            else: assigned+=1
        except Exception: errors+=1
    batch.assigned_count=assigned; batch.skipped_count=skipped; batch.error_count=errors
    batch.status='success' if errors==0 else 'partial'; batch.checkpoint={'last_resource_id':resource_ids[-1] if resource_ids else ''}
    batch.finished_at=datetime.now(timezone.utc); db.add(batch); db.commit(); db.refresh(batch); return batch

def isolation_summary(db:Session,tenant_id:str)->dict:
    return {
        'scoped_resources':db.scalar(select(func.count(TenantResourceScope.id)).where(TenantResourceScope.tenant_id==tenant_id)) or 0,
        'violations':db.scalar(select(func.count(TenantIsolationViolation.id)).where(TenantIsolationViolation.tenant_id==tenant_id)) or 0,
        'migration_batches':db.scalar(select(func.count(TenantMigrationBatch.id)).where(TenantMigrationBatch.tenant_id==tenant_id)) or 0,
        'policy':get_policy(db,tenant_id),
        'governed_resource_types':sorted(LEGACY_RESOURCE_MAP),
    }
