import hashlib, json, re
from datetime import timedelta
from pathlib import Path
from packaging.version import Version, InvalidVersion
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.models.entities import (
    MarketplaceAsset, MarketplaceAssetVersion, MarketplaceLicense,
    MarketplacePublisher, PartnerCommission, TenantEntitlement,
    TenantMarketplaceInstallation, utcnow,
)

ALLOWED_ASSET_TYPES={'connector','dashboard','etl_template','workflow','ai_agent','report','widget','api_template','theme','module'}
CODE_RE=re.compile(r'^[a-z][a-z0-9_.-]{2,139}$')


def sha256_file(path:str)->str:
    h=hashlib.sha256()
    with open(path,'rb') as f:
        for chunk in iter(lambda:f.read(1024*1024),b''): h.update(chunk)
    return h.hexdigest()


def validate_code(code:str)->str:
    value=code.strip().lower()
    if not CODE_RE.match(value): raise ValueError('Código inválido')
    return value


def validate_manifest(manifest:dict)->None:
    if not isinstance(manifest,dict): raise ValueError('Manifesto inválido')
    for key in ('entrypoint','permissions','resources'):
        if key not in manifest: raise ValueError(f'Manifesto sem {key}')
    if not isinstance(manifest.get('permissions'),list): raise ValueError('Permissões inválidas')
    if any(str(p).startswith('platform.superadmin') for p in manifest['permissions']):
        raise ValueError('Permissão proibida no pacote')


def compatible(platform_version:str,min_version:str,max_version:str='')->bool:
    try:
        current=Version(platform_version); minimum=Version(min_version)
        if current<minimum: return False
        return not max_version or current<=Version(max_version)
    except InvalidVersion:
        return False


def publisher_public(p:MarketplacePublisher)->dict:
    return {'id':p.id,'publisher_type':p.publisher_type,'legal_name':p.legal_name,'trade_name':p.trade_name,'website':p.website,'support_email':p.support_email,'status':p.status,'revenue_share_bps':p.revenue_share_bps}


def asset_public(a:MarketplaceAsset,version:MarketplaceAssetVersion|None=None)->dict:
    return {'id':a.id,'code':a.code,'asset_type':a.asset_type,'name':a.name,'short_description':a.short_description,'description':a.description,'category':a.category,'tags':a.tags,'icon_url':a.icon_url,'status':a.status,'visibility':a.visibility,'pricing_model':a.pricing_model,'monthly_price_cents':a.monthly_price_cents,'one_time_price_cents':a.one_time_price_cents,'currency':a.currency,'trial_days':a.trial_days,'required_plan_codes':a.required_plan_codes,'required_module_codes':a.required_module_codes,'latest_version':version.version if version else None}


def resolve_dependencies(db:Session,version:MarketplaceAssetVersion)->list[MarketplaceAssetVersion]:
    resolved=[]
    for dep in version.dependencies or []:
        code=str(dep.get('code','')).strip().lower(); constraint=str(dep.get('min_version','0.0.0'))
        asset=db.scalar(select(MarketplaceAsset).where(MarketplaceAsset.code==code,MarketplaceAsset.status=='published'))
        if not asset: raise ValueError(f'Dependência não publicada: {code}')
        versions=db.scalars(select(MarketplaceAssetVersion).where(MarketplaceAssetVersion.asset_id==asset.id,MarketplaceAssetVersion.status=='published').order_by(MarketplaceAssetVersion.created_at.desc())).all()
        selected=next((v for v in versions if compatible(v.version,constraint)),None)
        if not selected: raise ValueError(f'Versão da dependência indisponível: {code}')
        resolved.append(selected)
    return resolved


def ensure_entitlements(db:Session,tenant_id:str,asset:MarketplaceAsset)->None:
    if not asset.required_module_codes: return
    enabled=set(db.scalars(select(TenantEntitlement.module_code).where(TenantEntitlement.tenant_id==tenant_id,TenantEntitlement.enabled==True)).all())
    missing=[m for m in asset.required_module_codes if m not in enabled]
    if missing: raise ValueError('Módulos não contratados: '+', '.join(missing))


def install_asset(db:Session,tenant_id:str,user_id:str,asset:MarketplaceAsset,version:MarketplaceAssetVersion,platform_version='0.30.0',configuration=None)->TenantMarketplaceInstallation:
    if asset.status!='published' or version.status!='published' or version.security_review_status!='approved':
        raise ValueError('Pacote não está liberado')
    if not compatible(platform_version,version.min_platform_version,version.max_platform_version):
        raise ValueError('Versão incompatível com a plataforma')
    validate_manifest(version.manifest or {})
    ensure_entitlements(db,tenant_id,asset)
    resolve_dependencies(db,version)
    existing=db.scalar(select(TenantMarketplaceInstallation).where(TenantMarketplaceInstallation.tenant_id==tenant_id,TenantMarketplaceInstallation.asset_id==asset.id))
    if existing:
        existing.asset_version_id=version.id; existing.status='installed'; existing.enabled=True; existing.configuration=configuration or existing.configuration; existing.updated_at=utcnow(); item=existing
    else:
        item=TenantMarketplaceInstallation(tenant_id=tenant_id,asset_id=asset.id,asset_version_id=version.id,installed_by=user_id,configuration=configuration or {})
        db.add(item)
    if asset.pricing_model!='free':
        license_item=db.scalar(select(MarketplaceLicense).where(MarketplaceLicense.tenant_id==tenant_id,MarketplaceLicense.asset_id==asset.id))
        if not license_item:
            license_item=MarketplaceLicense(tenant_id=tenant_id,asset_id=asset.id,license_type='trial' if asset.trial_days else 'subscription',status='trialing' if asset.trial_days else 'active',ends_at=utcnow()+timedelta(days=asset.trial_days) if asset.trial_days else None)
            db.add(license_item)
    db.commit(); db.refresh(item); return item


def calculate_commission(db:Session,publisher:MarketplacePublisher,tenant_id:str,asset_id:str|None,reference_type:str,reference_id:str,gross_amount_cents:int,currency='BRL')->PartnerCommission:
    amount=max(0,(gross_amount_cents*publisher.revenue_share_bps)//10000)
    item=PartnerCommission(publisher_id=publisher.id,tenant_id=tenant_id,asset_id=asset_id,reference_type=reference_type,reference_id=reference_id,gross_amount_cents=gross_amount_cents,commission_bps=publisher.revenue_share_bps,commission_amount_cents=amount,currency=currency,status='pending')
    db.add(item); db.commit(); db.refresh(item); return item
