import hashlib
import hmac
import secrets
from datetime import datetime, timedelta, timezone
from sqlalchemy import select, func
from sqlalchemy.orm import Session
from app.core.security import hash_password
from app.models.entities import (SaaSOnboardingApplication,BillingCustomer,BillingInvoice,BillingWebhookEvent,
 TenantRateLimitPolicy,UsageReconciliationRun,TenantQuotaReservation,TenantUsageMeter,Tenant,SaaSPlan,
 SaaSSubscription,TenantMembership,User,Role)

def _hash(token:str)->str: return hashlib.sha256(token.encode()).hexdigest()
def create_onboarding(db:Session,company_name:str,admin_name:str,admin_email:str,tax_id:str='',plan_code:str='trial',ttl_hours:int=24):
    raw=secrets.token_urlsafe(40)
    item=SaaSOnboardingApplication(company_name=company_name.strip(),admin_name=admin_name.strip(),admin_email=admin_email.lower().strip(),tax_id=tax_id.strip(),plan_code=plan_code,activation_token_hash=_hash(raw),activation_expires_at=datetime.now(timezone.utc)+timedelta(hours=ttl_hours))
    db.add(item); db.commit(); db.refresh(item)
    return item,raw

def activate_onboarding(db:Session,application_id:str,token:str,password:str):
    item=db.get(SaaSOnboardingApplication,application_id)
    now=datetime.now(timezone.utc)
    if not item or not hmac.compare_digest(item.activation_token_hash,_hash(token)): raise ValueError('Token de ativação inválido')
    if item.status!='pending_email' or item.activation_expires_at<now: raise ValueError('Ativação expirada ou já utilizada')
    plan=db.scalar(select(SaaSPlan).where(SaaSPlan.code==item.plan_code,SaaSPlan.enabled.is_(True)))
    if not plan: raise ValueError('Plano indisponível')
    slug=''.join(c if c.isalnum() else '-' for c in item.company_name.lower()).strip('-')[:80] or secrets.token_hex(4)
    if db.scalar(select(Tenant).where(Tenant.slug==slug)): slug=f'{slug}-{secrets.token_hex(3)}'
    tenant=Tenant(slug=slug,legal_name=item.company_name,trade_name=item.company_name,tax_id=item.tax_id,status='active',settings_json={'onboarding_application_id':item.id})
    user=db.scalar(select(User).where(User.email==item.admin_email))
    if not user:
        user=User(email=item.admin_email,password_hash=hash_password(password),full_name=item.admin_name,role=Role.user,is_active=True); db.add(user); db.flush()
    db.add(tenant); db.flush()
    db.add(TenantMembership(tenant_id=tenant.id,user_id=user.id,tenant_role='owner',status='active'))
    db.add(SaaSSubscription(tenant_id=tenant.id,plan_id=plan.id,status='trialing',trial_ends_at=now+timedelta(days=14),current_period_start=now,current_period_end=now+timedelta(days=14)))
    db.add(BillingCustomer(tenant_id=tenant.id,billing_email=item.admin_email,tax_id=item.tax_id))
    item.status='activated'; item.tenant_id=tenant.id; item.activated_at=now; db.add(item); db.commit(); db.refresh(tenant)
    return tenant

def expire_quota_reservations(db:Session,tenant_id:str|None=None):
    now=datetime.now(timezone.utc)
    q=select(TenantQuotaReservation).where(TenantQuotaReservation.status=='reserved',TenantQuotaReservation.expires_at<now)
    if tenant_id: q=q.where(TenantQuotaReservation.tenant_id==tenant_id)
    run=UsageReconciliationRun(tenant_id=tenant_id); db.add(run); db.flush()
    rows=list(db.scalars(q)); qty=0
    for row in rows: row.status='released'; row.finalized_at=now; qty+=row.quantity; db.add(row)
    run.expired_reservations=len(rows); run.released_quantity=qty; run.status='success'; run.finished_at=now; db.add(run); db.commit(); db.refresh(run); return run

def create_invoice(db:Session,tenant_id:str,due_days:int=10):
    sub=db.scalar(select(SaaSSubscription).where(SaaSSubscription.tenant_id==tenant_id,SaaSSubscription.status.in_(['trialing','active','past_due'])))
    if not sub: raise ValueError('Assinatura não encontrada')
    plan=db.get(SaaSPlan,sub.plan_id); amount=(plan.monthly_price_cents if plan else 0)
    usage=db.execute(select(TenantUsageMeter.metric_code,func.sum(TenantUsageMeter.quantity)).where(TenantUsageMeter.tenant_id==tenant_id).group_by(TenantUsageMeter.metric_code)).all()
    lines=[{'code':'subscription','quantity':1,'amount_cents':amount}]+[{'code':code,'quantity':int(qty),'amount_cents':0} for code,qty in usage]
    inv=BillingInvoice(tenant_id=tenant_id,subscription_id=sub.id,status='open',currency=plan.currency if plan else 'BRL',subtotal_cents=amount,total_cents=amount,due_at=datetime.now(timezone.utc)+timedelta(days=due_days),line_items=lines)
    db.add(inv); db.commit(); db.refresh(inv); return inv

def verify_webhook(secret:str,body:bytes,signature:str)->bool:
    expected=hmac.new(secret.encode(),body,hashlib.sha256).hexdigest()
    return bool(signature) and hmac.compare_digest(expected,signature)

def register_webhook(db:Session,provider:str,event_id:str,event_type:str,payload:dict,signature_valid:bool):
    # Idempotency is enforced by the unique external event id.
    existing=db.scalar(select(BillingWebhookEvent).where(BillingWebhookEvent.external_event_id==event_id))
    if existing:return existing
    item=BillingWebhookEvent(provider=provider,external_event_id=event_id,event_type=event_type,payload=payload,signature_valid=signature_valid,status='processed' if signature_valid else 'rejected',processed_at=datetime.now(timezone.utc))
    db.add(item); db.commit(); db.refresh(item); return item

def rate_limit_key(tenant_id:str,scope:str,minute_bucket:int)->str:
    return f'tenant:{tenant_id}:ratelimit:{scope}:{minute_bucket}'

# idempotency: external_event_id is unique and duplicate-safe.
