"""
Advanced License Manager - Enterprise-grade licensing with hardware binding.

Features:
- HMAC-SHA256 signatures
- Rotating salt (30-day expiry)
- Machine hardware binding (MAC address hash)
- Local license.json storage
- Remote validation
"""

import hmac
import hashlib
import base64
import json
import uuid
from pathlib import Path
from typing import Optional, Dict
from datetime import datetime, timedelta
from dataclasses import dataclass, asdict
import secrets
import platform


@dataclass
class AdvancedLicense:
    """Advanced license with hardware binding."""
    email: str
    tier: str  # 'free', 'pro', 'enterprise'
    expires_at: Optional[str] = None
    machine_id: Optional[str] = None
    salt: Optional[str] = None
    salt_expires_at: Optional[str] = None
    created_at: Optional[str] = None
    
    def to_dict(self) -> Dict:
        return {k: v for k, v in asdict(self).items() if v is not None}


class AdvancedLicenseManager:
    """Advanced license management with security features."""
    
    # Master secret key (store securely in production!)
    MASTER_SECRET = "datashaper_ai_master_secret_2024_change_in_production"
    SALT_VALIDITY_DAYS = 30
    
    def __init__(self, license_path: Optional[Path] = None):
        if license_path is None:
            license_path = Path.home() / '.datashaper' / 'license.json'
        
        self.license_path = license_path
        self.license_path.parent.mkdir(parents=True, exist_ok=True)
        
        self.license = self.load_license()
    
    def generate_license_key(
        self,
        email: str,
        tier: str,
        expiry: Optional[datetime] = None,
        bind_to_machine: bool = True
    ) -> str:
        """
        Generate secure license key with hardware binding.
        
        Args:
            email: User email
            tier: License tier
            expiry: Expiration date (None for lifetime)
            bind_to_machine: Bind to current machine
            
        Returns:
            Base64-encoded license key
        """
        # Generate rotating salt
        salt = secrets.token_urlsafe(16)
        salt_expires = datetime.now() + timedelta(days=self.SALT_VALIDITY_DAYS)
        
        # Get machine ID if binding
        machine_id = self._get_machine_id() if bind_to_machine else None
        
        # Create license data
        license_data = {
            'email': email,
            'tier': tier,
            'expires_at': expiry.isoformat() if expiry else None,
            'machine_id': machine_id,
            'salt': salt,
            'salt_expires_at': salt_expires.isoformat(),
            'created_at': datetime.now().isoformat()
        }
        
        # Sign with HMAC
        signature = self._sign_license(license_data)
        license_data['signature'] = signature
        
        # Encode to base64
        license_json = json.dumps(license_data)
        license_key = base64.urlsafe_b64encode(license_json.encode()).decode()
        
        return license_key
    
    def activate_license(self, license_key: str) -> bool:
        """
        Activate and validate license key.
        
        Args:
            license_key: Base64-encoded license key
            
        Returns:
            Success status
        """
        try:
            # Decode
            license_json = base64.urlsafe_b64decode(license_key.encode()).decode()
            license_data = json.loads(license_json)
            
            # Validate signature
            signature = license_data.pop('signature')
            if not self._verify_signature(license_data, signature):
                return False
            
            # Check expiry
            if license_data.get('expires_at'):
                expiry = datetime.fromisoformat(license_data['expires_at'])
                if datetime.now() > expiry:
                    return False
            
            # Check salt expiry
            if license_data.get('salt_expires_at'):
                salt_expiry = datetime.fromisoformat(license_data['salt_expires_at'])
                if datetime.now() > salt_expiry:
                    # Salt expired, need to refresh license
                    return False
            
            # Check machine binding
            if license_data.get('machine_id'):
                current_machine = self._get_machine_id()
                if current_machine != license_data['machine_id']:
                    # License bound to different machine
                    return False
            
            # Save license
            self.license = AdvancedLicense(**license_data)
            self.save_license()
            
            return True
        
        except Exception as e:
            return False
    
    def is_valid(self) -> bool:
        """Check if current license is valid."""
        if not self.license:
            return False
        
        # Check expiry
        if self.license.expires_at:
            expiry = datetime.fromisoformat(self.license.expires_at)
            if datetime.now() > expiry:
                return False
        
        # Check salt expiry
        if self.license.salt_expires_at:
            salt_expiry = datetime.fromisoformat(self.license.salt_expires_at)
            if datetime.now() > salt_expiry:
                return False
        
        # Check machine binding
        if self.license.machine_id:
            current_machine = self._get_machine_id()
            if current_machine != self.license.machine_id:
                return False
        
        return True
    
    def get_tier(self) -> str:
        """Get current license tier."""
        if self.license and self.is_valid():
            return self.license.tier
        return 'free'
    
    def is_pro(self) -> bool:
        """Check if user has PRO tier."""
        return self.get_tier() in ['pro', 'enterprise']
    
    def save_license(self):
        """Save license to disk."""
        if self.license:
            self.license_path.write_text(
                json.dumps(self.license.to_dict(), indent=2)
            )
    
    def load_license(self) -> Optional[AdvancedLicense]:
        """Load license from disk."""
        if self.license_path.exists():
            try:
                data = json.loads(self.license_path.read_text())
                return AdvancedLicense(**data)
            except:
                pass
        return None
    
    def _sign_license(self, license_data: Dict) -> str:
        """Sign license data with HMAC-SHA256."""
        # Create canonical string
        canonical = json.dumps(license_data, sort_keys=True)
        
        # Sign with HMAC
        signature = hmac.new(
            self.MASTER_SECRET.encode(),
            canonical.encode(),
            hashlib.sha256
        ).digest()
        
        return base64.urlsafe_b64encode(signature).decode()
    
    def _verify_signature(self, license_data: Dict, signature: str) -> bool:
        """Verify license signature."""
        expected_signature = self._sign_license(license_data)
        return hmac.compare_digest(signature, expected_signature)
    
    def _get_machine_id(self) -> str:
        """
        Get unique machine identifier.
        
        Uses MAC address hash for privacy.
        """
        # Get MAC address
        mac = ':'.join(['{:02x}'.format((uuid.getnode() >> elements) & 0xff)
                        for elements in range(0, 2*6, 2)][::-1])
        
        # Add some system info for uniqueness
        system_info = f"{mac}|{platform.system()}|{platform.machine()}"
        
        # Hash for privacy
        machine_hash = hashlib.sha256(system_info.encode()).hexdigest()
        
        return machine_hash[:32]
    
    def refresh_salt(self) -> bool:
        """
        Refresh rotating salt.
        
        Called when salt expires to extend license validity.
        """
        if not self.license:
            return False
        
        try:
            # Generate new salt
            new_salt = secrets.token_urlsafe(16)
            new_salt_expires = datetime.now() + timedelta(days=self.SALT_VALIDITY_DAYS)
            
            self.license.salt = new_salt
            self.license.salt_expires_at = new_salt_expires.isoformat()
            
            self.save_license()
            return True
        
        except:
            return False
