import os
import sys
from flask import Flask, request, jsonify
from flask_cors import CORS
import jwt
from datetime import datetime, timedelta
import logging
from functools import wraps
from marshmallow import Schema, fields, ValidationError

# Add parent directory to path for imports
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))

from config import get_config
from repositories.license_repo import LicenseRepository
from repositories.cache_repo import CacheRepository
from events.event_bus import EventBus, Event, EventTypes
from models import EventType, ValidationRequest, ValidationResponse

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Initialize Flask app
app = Flask(__name__)
config = get_config()
app.config.from_object(config)
CORS(app)

# Initialize dependencies
license_repo = LicenseRepository(config.DATABASE_URL)
cache_repo = CacheRepository(config.REDIS_URL)
event_bus = EventBus(config.RABBITMQ_URL)

# Validation schemas
class ValidateSchema(Schema):
    license_key = fields.Str(required=True)
    hardware_id = fields.Str(required=True)
    app_version = fields.Str()

class ActivateSchema(Schema):
    license_key = fields.Str(required=True)
    hardware_id = fields.Str(required=True)
    device_name = fields.Str()
    os_info = fields.Dict()

class HeartbeatSchema(Schema):
    session_data = fields.Dict()

class OfflineTokenSchema(Schema):
    duration_hours = fields.Int(missing=24, validate=lambda x: 0 < x <= 72)

def require_api_key(f):
    """Decorator to require API key"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        api_key = request.headers.get('X-API-Key')
        
        if not api_key:
            return jsonify({"error": "Missing API key"}), 401
        
        # For now, accept any API key starting with 'sk_'
        # In production, validate against database
        if not api_key.startswith('sk_'):
            return jsonify({"error": "Invalid API key"}), 401
        
        return f(*args, **kwargs)
    
    return decorated_function

def require_auth_token(f):
    """Decorator to require JWT token"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        auth_header = request.headers.get('Authorization')
        
        if not auth_header or not auth_header.startswith('Bearer '):
            return jsonify({"error": "Missing or invalid authorization header"}), 401
        
        token = auth_header.split(' ')[1]
        
        try:
            payload = jwt.decode(
                token,
                config.JWT_SECRET,
                algorithms=[config.JWT_ALGORITHM]
            )
            request.token_payload = payload
            return f(*args, **kwargs)
        except jwt.ExpiredSignatureError:
            return jsonify({"error": "Token has expired"}), 401
        except jwt.InvalidTokenError:
            return jsonify({"error": "Invalid token"}), 401
    
    return decorated_function

def get_client_ip():
    """Get client IP address"""
    if request.headers.get('X-Forwarded-For'):
        return request.headers.get('X-Forwarded-For').split(',')[0]
    return request.remote_addr

@app.route('/health', methods=['GET'])
def health_check():
    """Health check endpoint"""
    return jsonify({
        "status": "healthy",
        "service": "license-api",
        "timestamp": datetime.utcnow().isoformat()
    })

@app.route('/api/v1/license/validate', methods=['POST'])
@require_api_key
def validate_license():
    """Validate license key with hardware ID"""
    schema = ValidateSchema()
    
    try:
        data = schema.load(request.get_json())
    except ValidationError as e:
        return jsonify({"error": "Invalid request", "details": e.messages}), 400
    
    license_key = data['license_key']
    hardware_id = data['hardware_id']
    app_version = data.get('app_version')
    
    # Check cache first
    cached_result = cache_repo.get_license_validation(license_key, hardware_id)
    if cached_result:
        logger.info(f"Cache hit for license validation: {license_key[:8]}...")
        return jsonify(cached_result)
    
    # Get license from database
    license = license_repo.get_license_by_key(license_key)
    
    if not license:
        event_bus.publish(Event(
            EventTypes.LICENSE_VALIDATION_FAILED,
            {"license_key": license_key, "reason": "not_found"},
            "license-api"
        ))
        return jsonify({
            "valid": False,
            "error": "License not found",
            "error_code": "LICENSE_NOT_FOUND"
        }), 404
    
    # Check if license is active
    if not license['is_active']:
        event_bus.publish(Event(
            EventTypes.LICENSE_VALIDATION_FAILED,
            {"license_id": license['id'], "reason": "inactive"},
            "license-api"
        ))
        return jsonify({
            "valid": False,
            "error": "License is not active",
            "error_code": "LICENSE_INACTIVE"
        }), 403
    
    # Check expiration
    if license['expires_at'] and datetime.utcnow() > license['expires_at']:
        event_bus.publish(Event(
            EventTypes.LICENSE_EXPIRED,
            {"license_id": license['id']},
            "license-api"
        ))
        return jsonify({
            "valid": False,
            "error": "License has expired",
            "error_code": "LICENSE_EXPIRED"
        }), 403
    
    # Check device limit
    device_count = license_repo.get_device_count(license['id'])
    if device_count >= license['max_devices']:
        # Check if this device is already registered
        if not license_repo.check_hardware_id_exists(license['id'], hardware_id):
            return jsonify({
                "valid": False,
                "error": "Device limit exceeded",
                "error_code": "DEVICE_LIMIT_EXCEEDED",
                "current_devices": device_count,
                "max_devices": license['max_devices']
            }), 403
    
    # Record heartbeat
    license_repo.record_heartbeat(
        license_id=license['id'],
        hardware_id=hardware_id,
        ip_address=get_client_ip(),
        user_agent=request.headers.get('User-Agent'),
        app_version=app_version
    )
    
    # Create response
    response = {
        "valid": True,
        "license_id": license['id'],
        "expires_at": license['expires_at'].isoformat() if license['expires_at'] else None,
        "features": license.get('features', []),
        "limits": {
            "max_devices": license['max_devices'],
            "current_devices": device_count
        }
    }
    
    # Cache the result
    cache_repo.set_license_validation(
        license_key, 
        hardware_id, 
        response, 
        config.CACHE_TTL_VALIDATION
    )
    
    # Publish success event
    event_bus.publish(Event(
        EventTypes.LICENSE_VALIDATED,
        {
            "license_id": license['id'],
            "hardware_id": hardware_id,
            "ip_address": get_client_ip()
        },
        "license-api"
    ))
    
    return jsonify(response)

@app.route('/api/v1/license/activate', methods=['POST'])
@require_api_key
def activate_license():
    """Activate license on a new device"""
    schema = ActivateSchema()
    
    try:
        data = schema.load(request.get_json())
    except ValidationError as e:
        return jsonify({"error": "Invalid request", "details": e.messages}), 400
    
    license_key = data['license_key']
    hardware_id = data['hardware_id']
    device_name = data.get('device_name')
    os_info = data.get('os_info', {})
    
    # Get license
    license = license_repo.get_license_by_key(license_key)
    
    if not license:
        return jsonify({
            "error": "License not found",
            "error_code": "LICENSE_NOT_FOUND"
        }), 404
    
    if not license['is_active']:
        return jsonify({
            "error": "License is not active",
            "error_code": "LICENSE_INACTIVE"
        }), 403
    
    # Check if already activated on this device
    if license_repo.check_hardware_id_exists(license['id'], hardware_id):
        return jsonify({
            "error": "License already activated on this device",
            "error_code": "ALREADY_ACTIVATED"
        }), 400
    
    # Check device limit
    device_count = license_repo.get_device_count(license['id'])
    if device_count >= license['max_devices']:
        return jsonify({
            "error": "Device limit exceeded",
            "error_code": "DEVICE_LIMIT_EXCEEDED",
            "current_devices": device_count,
            "max_devices": license['max_devices']
        }), 403
    
    # Record activation
    license_repo.record_activation_event(
        license_id=license['id'],
        event_type=EventType.ACTIVATION,
        hardware_id=hardware_id,
        ip_address=get_client_ip(),
        user_agent=request.headers.get('User-Agent'),
        success=True,
        metadata={
            "device_name": device_name,
            "os_info": os_info
        }
    )
    
    # Invalidate cache
    cache_repo.invalidate_license_cache(license['id'])
    
    # Publish event
    event_bus.publish(Event(
        EventTypes.LICENSE_ACTIVATED,
        {
            "license_id": license['id'],
            "hardware_id": hardware_id,
            "device_name": device_name
        },
        "license-api"
    ))
    
    return jsonify({
        "success": True,
        "license_id": license['id'],
        "message": "License activated successfully"
    }), 201

@app.route('/api/v1/license/heartbeat', methods=['POST'])
@require_auth_token
def heartbeat():
    """Record license heartbeat"""
    schema = HeartbeatSchema()
    
    try:
        data = schema.load(request.get_json() or {})
    except ValidationError as e:
        return jsonify({"error": "Invalid request", "details": e.messages}), 400
    
    license_id = request.token_payload['sub']
    hardware_id = request.token_payload.get('hwid')
    
    # Record heartbeat
    license_repo.record_heartbeat(
        license_id=license_id,
        hardware_id=hardware_id,
        ip_address=get_client_ip(),
        user_agent=request.headers.get('User-Agent'),
        session_data=data.get('session_data', {})
    )
    
    return jsonify({
        "success": True,
        "timestamp": datetime.utcnow().isoformat()
    })

@app.route('/api/v1/license/offline-token', methods=['POST'])
@require_auth_token
def create_offline_token():
    """Create offline validation token"""
    schema = OfflineTokenSchema()
    
    try:
        data = schema.load(request.get_json() or {})
    except ValidationError as e:
        return jsonify({"error": "Invalid request", "details": e.messages}), 400
    
    license_id = request.token_payload['sub']
    hardware_id = request.token_payload.get('hwid')
    duration_hours = data['duration_hours']
    
    if not hardware_id:
        return jsonify({"error": "Hardware ID required"}), 400
    
    # Create offline token
    token = license_repo.create_license_token(
        license_id=license_id,
        hardware_id=hardware_id,
        valid_hours=duration_hours
    )
    
    if not token:
        return jsonify({"error": "Failed to create token"}), 500
    
    valid_until = datetime.utcnow() + timedelta(hours=duration_hours)
    
    return jsonify({
        "token": token,
        "valid_until": valid_until.isoformat(),
        "duration_hours": duration_hours
    })

@app.route('/api/v1/license/validate-offline', methods=['POST'])
def validate_offline_token():
    """Validate offline token"""
    data = request.get_json()
    
    if not data or 'token' not in data:
        return jsonify({"error": "Missing token"}), 400
    
    # Validate token
    result = license_repo.validate_token(data['token'])
    
    if not result:
        return jsonify({
            "valid": False,
            "error": "Invalid or expired token"
        }), 401
    
    return jsonify({
        "valid": True,
        "license_id": result['license_id'],
        "hardware_id": result['hardware_id'],
        "expires_at": result['valid_until'].isoformat()
    })

@app.errorhandler(404)
def not_found(error):
    return jsonify({"error": "Not found"}), 404

@app.errorhandler(500)
def internal_error(error):
    logger.error(f"Internal error: {error}")
    return jsonify({"error": "Internal server error"}), 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5002, debug=True)