from __future__ import annotations
import hashlib, hmac, json, secrets
from datetime import datetime, timedelta, timezone
from typing import Any
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from app.core.crypto import encrypt_text, decrypt_text
from app.core.security import hash_password
from app.models.entities import (
    BillingInvoice, CheckoutSession, CommercialLead, CommercialOpportunity,
    CustomerHealthSnapshot, CustomerPaymentMethod, CustomerSuccessPlan,
    KnowledgeArticleV029, PaymentGatewayConfig, SaaSOnboardingApplication,
    SaaSPlan, SaaSSubscription, SupportTicket, Tenant, TenantMembership,
    TenantUsageMeter, User, Role, utcnow,
)


def slugify(value:str)->str:
    base=''.join(ch.lower() if ch.isalnum() else '-' for ch in value).strip('-')
    while '--' in base: base=base.replace('--','-')
    return base[:80] or f'empresa-{secrets.token_hex(4)}'


def create_public_lead(db:Session,payload:dict[str,Any])->CommercialLead:
    lead=CommercialLead(company_name=payload['company_name'].strip(),contact_name=payload['contact_name'].strip(),email=payload['email'].lower().strip(),phone=payload.get('phone',''),segment=payload.get('segment',''),source=payload.get('source','website'),notes=payload.get('notes',''))
    db.add(lead); db.commit(); db.refresh(lead); return lead


def start_trial_application(db:Session,payload:dict[str,Any])->tuple[SaaSOnboardingApplication,str]:
    existing=db.scalar(select(SaaSOnboardingApplication).where(SaaSOnboardingApplication.admin_email==payload['admin_email'].lower(),SaaSOnboardingApplication.status.in_(['pending','verified'])))
    if existing: raise ValueError('Já existe uma solicitação de cadastro pendente para este e-mail')
    token=secrets.token_urlsafe(32); digest=hashlib.sha256(token.encode()).hexdigest()
    app=SaaSOnboardingApplication(company_name=payload['company_name'].strip(),admin_name=payload['admin_name'].strip(),admin_email=payload['admin_email'].lower().strip(),tax_id=payload.get('tax_id',''),plan_code=payload['plan_code'],status='pending',activation_token_hash=digest,activation_expires_at=utcnow()+timedelta(hours=24))
    db.add(app); db.commit(); db.refresh(app); return app,token


def activate_trial(db:Session,application:SaaSOnboardingApplication,token:str,password:str,trial_days:int=14)->Tenant:
    if application.status not in ('pending','verified'): raise ValueError('Solicitação já processada')
    if not application.activation_expires_at or application.activation_expires_at<utcnow(): raise ValueError('Token expirado')
    digest=hashlib.sha256(token.encode()).hexdigest()
    if not hmac.compare_digest(digest,application.activation_token_hash): raise ValueError('Token inválido')
    plan=db.scalar(select(SaaSPlan).where(SaaSPlan.code==application.plan_code,SaaSPlan.enabled==True))
    if not plan: raise ValueError('Plano indisponível')
    slug=slugify(application.company_name); base=slug; idx=2
    while db.scalar(select(Tenant).where(Tenant.slug==slug)):
        slug=f'{base}-{idx}'; idx+=1
    tenant=Tenant(slug=slug,legal_name=application.company_name,trade_name=application.company_name,tax_id=application.tax_id,status='active',region='sa-east-1',data_residency='BR',activated_at=utcnow(),isolation_mode='shared_schema')
    user=User(email=application.admin_email,password_hash=hash_password(password),full_name=application.admin_name,role=Role.admin,is_active=True)
    db.add_all([tenant,user]); db.flush()
    db.add(TenantMembership(tenant_id=tenant.id,user_id=user.id,tenant_role='owner',status='active'))
    now=utcnow(); subscription=SaaSSubscription(tenant_id=tenant.id,plan_id=plan.id,status='trialing',billing_cycle='monthly',seats=1,trial_ends_at=now+timedelta(days=trial_days),current_period_start=now,current_period_end=now+timedelta(days=trial_days))
    db.add(subscription)
    application.status='activated'; application.activated_at=now; application.tenant_id=tenant.id
    db.commit(); db.refresh(tenant); return tenant


def save_gateway(db:Session,provider:str,name:str,environment:str,config:dict[str,Any],webhook_secret:str,user_id:str,enabled:bool)->PaymentGatewayConfig:
    item=db.scalar(select(PaymentGatewayConfig).where(PaymentGatewayConfig.provider==provider,PaymentGatewayConfig.environment==environment)) or PaymentGatewayConfig(provider=provider,environment=environment,name=name,created_by=user_id)
    item.name=name; item.encrypted_config=encrypt_text(json.dumps(config)); item.webhook_secret_encrypted=encrypt_text(webhook_secret) if webhook_secret else ''; item.enabled=enabled
    db.add(item); db.commit(); db.refresh(item); return item


def gateway_public(item:PaymentGatewayConfig)->dict[str,Any]:
    return {'id':item.id,'provider':item.provider,'name':item.name,'environment':item.environment,'enabled':item.enabled,'configured':bool(item.encrypted_config),'webhook_configured':bool(item.webhook_secret_encrypted)}


def create_checkout(db:Session,application:SaaSOnboardingApplication,provider:str,billing_cycle:str,trial_days:int)->CheckoutSession:
    gateway=db.scalar(select(PaymentGatewayConfig).where(PaymentGatewayConfig.provider==provider,PaymentGatewayConfig.enabled==True))
    session=CheckoutSession(onboarding_application_id=application.id,provider=provider,plan_code=application.plan_code,billing_cycle=billing_cycle,customer_email=application.admin_email,trial_days=trial_days,status='created',expires_at=utcnow()+timedelta(minutes=30))
    if gateway:
        # Adapter boundary: real card data is always collected by the provider-hosted page.
        session.external_session_id=f'{provider}_{secrets.token_hex(12)}'
        session.checkout_url=f'/commercial/checkout-simulator?session={session.external_session_id}'
        session.status='pending_payment_method'
    else:
        session.external_session_id=f'mock_{secrets.token_hex(12)}'; session.checkout_url=f'/commercial/checkout-simulator?session={session.external_session_id}'; session.status='sandbox'
    db.add(session); db.commit(); db.refresh(session); return session


def calculate_health(db:Session,tenant_id:str)->CustomerHealthSnapshot:
    users=db.scalar(select(func.count()).select_from(TenantMembership).where(TenantMembership.tenant_id==tenant_id,TenantMembership.status=='active')) or 0
    tickets_open=db.scalar(select(func.count()).select_from(SupportTicket).where(SupportTicket.tenant_id==tenant_id,SupportTicket.status.in_(['open','pending','in_progress']))) or 0
    tickets_critical=db.scalar(select(func.count()).select_from(SupportTicket).where(SupportTicket.tenant_id==tenant_id,SupportTicket.priority=='critical',SupportTicket.status!='resolved')) or 0
    usage=db.scalar(select(func.coalesce(func.sum(TenantUsageMeter.quantity),0)).where(TenantUsageMeter.tenant_id==tenant_id)) or 0
    overdue=db.scalar(select(func.count()).select_from(BillingInvoice).where(BillingInvoice.tenant_id==tenant_id,BillingInvoice.status.in_(['past_due','unpaid']))) or 0
    adoption=min(100,int(users*8 + min(float(usage),60)))
    support=max(0,100-tickets_open*5-tickets_critical*20)
    billing=max(0,100-overdue*35)
    engagement=min(100,int(min(float(usage),100)))
    score=round(adoption*.35+support*.25+billing*.25+engagement*.15)
    risk='low' if score>=75 else 'medium' if score>=50 else 'high'
    snap=CustomerHealthSnapshot(tenant_id=tenant_id,score=score,adoption_score=adoption,support_score=support,billing_score=billing,engagement_score=engagement,risk_level=risk,signals={'active_users':users,'open_tickets':tickets_open,'critical_tickets':tickets_critical,'usage_quantity':float(usage),'overdue_invoices':overdue})
    db.add(snap); db.commit(); db.refresh(snap); return snap


def sla_due(priority:str)->datetime:
    hours={'low':72,'normal':24,'high':8,'critical':2}.get(priority,24)
    return utcnow()+timedelta(hours=hours)

def ensure_default_plans(db:Session)->None:
    defaults=[
        ('starter','Starter',49000,490000,{'users':5,'storage_gb':20,'ai_tokens':100000,'modules':['data_lake','catalog','dashboards']}),
        ('business','Business',149000,1490000,{'users':25,'storage_gb':100,'ai_tokens':1000000,'modules':['data_lake','catalog','dashboards','reports','etl','ai','support']}),
        ('enterprise','Enterprise',399000,3990000,{'users':100,'storage_gb':500,'ai_tokens':5000000,'modules':['all']}),
    ]
    changed=False
    for code,name,monthly,annual,limits in defaults:
        if not db.scalar(select(SaaSPlan).where(SaaSPlan.code==code)):
            db.add(SaaSPlan(code=code,name=name,currency='BRL',monthly_price_cents=monthly,annual_price_cents=annual,limits_json=limits,enabled=True)); changed=True
    if changed: db.commit()
