import os
import random
import string
import uuid
import time
import hashlib
import re
from datetime import datetime, timedelta
from functools import wraps
from urllib.parse import urlparse
from flask import Flask, render_template, request, jsonify, redirect, url_for, send_from_directory, session, Response
from config import config
from storage import StorageManager
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from expiry_db import ExpiryDatabase

# Flask app configuration
app = Flask(__name__)

# Set Flask configuration from config
flask_config = config.get_flask_config()
app.config['MAX_CONTENT_LENGTH'] = config.get('upload.max_file_size_mb', 100) * 1024 * 1024  # Convert MB to bytes
app.secret_key = config.get('flask.secret_key', os.urandom(24))  # For session management

# Admin helper functions
def hash_password(password):
    """Hash a password using SHA-256"""
    return hashlib.sha256(password.encode()).hexdigest()

def verify_password(password, password_hash):
    """Verify a password against its hash"""
    return hash_password(password) == password_hash

def require_admin(f):
    """Decorator to require admin authentication"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        admin_config = config.get('admin', {})
        if not admin_config.get('enabled', False):
            return jsonify({'error': 'Admin panel is disabled'}), 404
            
        if 'admin_logged_in' not in session:
            return redirect(url_for('admin_login'))
            
        # Check session timeout
        timeout_minutes = admin_config.get('session_timeout_minutes', 60)
        if 'admin_login_time' in session:
            login_time = datetime.fromisoformat(session['admin_login_time'])
            if datetime.now() - login_time > timedelta(minutes=timeout_minutes):
                session.clear()
                return redirect(url_for('admin_login'))
                
        return f(*args, **kwargs)
    return decorated_function

# Maintenance mode decorator
def check_maintenance(f):
    """Decorator to check if maintenance mode is enabled"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        maintenance_config = config.get('maintenance', {})
        if maintenance_config.get('enabled', False):
            # Allow health check even in maintenance mode
            if request.endpoint == 'health':
                return f(*args, **kwargs)
            
            # Show maintenance page for all other requests
            return render_template('maintenance.html', 
                                 message=maintenance_config.get('message', 'Sharey is currently under maintenance.'),
                                 estimated_return=maintenance_config.get('estimated_return', '')), 503
        return f(*args, **kwargs)
    return decorated_function

# Storage Configuration and Initialization
try:
    # Validate storage configuration
    if not config.validate_storage_config():
        storage_config = config.get_storage_config()
        backend = storage_config.get('backend', 'unknown')
        
        if backend == 'b2':
            print("❌ Invalid B2 configuration. Please check your config.json or environment variables.")
            print("Required fields: B2_APPLICATION_KEY_ID, B2_APPLICATION_KEY, B2_BUCKET_NAME")
        else:
            print(f"❌ Invalid storage configuration for backend: {backend}")
        
        config.print_config()
        exit(1)

    # Initialize storage manager
    storage = StorageManager(config)
    backend_info = storage.get_backend_info()
    
    if backend_info['type'] == 'b2':
        print(f"✅ Connected to B2 bucket: {backend_info.get('bucket', 'unknown')}")
    elif backend_info['type'] == 'local':
        print(f"✅ Using local storage at: {backend_info.get('path', 'unknown')}")
    
    # Initialize expiry database
    expiry_db = ExpiryDatabase()
    
except Exception as e:
    print(f"❌ Failed to initialize storage: {str(e)}")
    print("Please check your storage configuration.")
    config.print_config()
    exit(1)

# Function to generate a random 6-character string
def generate_short_id(length=6):
    return ''.join(random.choices(string.ascii_letters + string.digits, k=length))

# URL Shortener utility functions
def generate_short_code(length=6):
    """Generate a random short code for URL shortener"""
    characters = string.ascii_letters + string.digits
    while True:
        code = ''.join(random.choices(characters, k=length))
        # Make sure it doesn't conflict with existing content IDs
        content_type, _ = detect_content_type(code)
        if content_type is None and not expiry_db.get_redirect(code):
            return code

def is_valid_url(url):
    """Validate if a URL is properly formatted"""
    try:
        result = urlparse(url)
        return all([result.scheme, result.netloc]) and result.scheme in ['http', 'https']
    except:
        return False

def is_safe_url(url):
    """Basic safety check for URLs (can be extended)"""
    # Block localhost, private IPs, and suspicious TLDs
    parsed = urlparse(url)
    hostname = parsed.hostname
    
    if not hostname:
        return False
        
    # Block localhost and private IPs
    if hostname.lower() in ['localhost', '127.0.0.1', '0.0.0.0']:
        return False
        
    # Block private IP ranges (basic check)
    if hostname.startswith('192.168.') or hostname.startswith('10.') or hostname.startswith('172.'):
        return False
    
    # Block suspicious patterns
    suspicious_patterns = [
        r'bit\.ly', r'tinyurl', r'short\.link', r'malware', r'phishing'
    ]
    
    for pattern in suspicious_patterns:
        if re.search(pattern, url, re.IGNORECASE):
            return False
    
    return True

# Helper function to determine if an ID is a file or paste
def detect_content_type(content_id):
    """
    Try to determine if an ID is a file or paste by checking storage
    Returns: ('file', file_id) or ('paste', paste_id) or (None, None)
    """
    print(f"🔍 Detecting content type for ID: {content_id}")
    
    # First try as file (check if it has an extension or exists in files/)
    try:
        # Try direct file access
        storage.download_file(f"files/{content_id}")
        print(f"✅ Found as file: files/{content_id}")
        return 'file', content_id
    except Exception as e:
        print(f"❌ Not found as file: {e}")
    
    # Try as paste
    try:
        storage.download_file(f"pastes/{content_id}.txt")
        print(f"✅ Found as paste: pastes/{content_id}.txt")
        return 'paste', content_id
    except Exception as e:
        print(f"❌ Not found as paste: {e}")
    
    print(f"❌ Content not found: {content_id}")
    return None, None

# Main index route
@app.route('/')
@check_maintenance
def index():
    return render_template('index.html')

# Clean URL route - handles redirects, files, and pastes at /<id>
@app.route('/<content_id>')
@check_maintenance
def get_content(content_id):
    """
    Clean URL handler - checks for URL redirect first, then files/pastes
    Examples: sharey.org/ABC123, sharey.org/XYZ789.png
    """
    print(f"🔍 Processing request for ID: {content_id}")
    
    # First check if it's a URL redirect
    redirect_url = expiry_db.get_redirect(content_id)
    if redirect_url:
        print(f"🔗 Redirecting {content_id} to {redirect_url}")
        return redirect(redirect_url, code=302)
    
    # Not a redirect, try as file/paste
    # Skip certain paths that should not be treated as content IDs
    excluded_paths = ['admin', 'health', 'api', 'static', 'files', 'pastes', 'favicon.ico']
    if content_id in excluded_paths:
        return redirect(url_for('index'))
    
    print(f"🔍 Clean URL accessed: {content_id}")
    content_type, resolved_id = detect_content_type(content_id)
    
    if content_type == 'file':
        print(f"📁 Detected as file: {resolved_id}")
        return get_file(resolved_id)
    elif content_type == 'paste':
        print(f"📝 Detected as paste: {resolved_id}")  
        return view_paste(resolved_id)
    else:
        print(f"❌ Content not found: {content_id}")
        return render_template('404.html', 
                             title="Content Not Found",
                             message=f"The content '{content_id}' could not be found."), 404

# Health check endpoint
@app.route('/health')
def health_check():
    try:
        # Test storage connection by trying to list files
        files = storage.list_files()
        backend_info = storage.get_backend_info()
        return jsonify({
            'status': 'healthy', 
            'storage': f"{backend_info['type']}_connected",
            'backend': backend_info
        }), 200
    except Exception as e:
        return jsonify({'status': 'unhealthy', 'error': str(e)}), 500

# Configuration endpoint
@app.route('/api/config')
@check_maintenance
def get_config():
    """Get public configuration (non-sensitive)"""
    return jsonify({
        'upload': {
            'max_file_size_mb': config.get('upload.max_file_size_mb')
        },
        'paste': {
            'max_length': config.get('paste.max_length')
        }
    })

# File upload route
@app.route('/api/upload', methods=['POST'])
@check_maintenance
def upload_file():
    print(f"📤 Upload request received")
    
    files = request.files.getlist('files[]')
    expires_at = request.form.get('expires_at')  # Get expiry from form data
    
    if not files or not any(file.filename for file in files):
        return jsonify({'error': 'No files provided'}), 400

    file_urls = []
    
    for file in files:
        if file and file.filename:
            print(f"📁 Processing file: {file.filename}")
            
            # Generate a 6-character alphanumeric string for the file ID
            short_id = generate_short_id()

            # Get the file extension
            file_extension = os.path.splitext(file.filename)[1]

            # Create the file ID with extension
            file_id = f"{short_id}{file_extension}"
            
            # Upload file to storage
            try:
                file_content = file.read()
                file_size = len(file_content)
                print(f"📊 File size: {file_size} bytes ({file_size/1024/1024:.2f} MB)")
                
                if file_size == 0:
                    return jsonify({'error': 'Empty file uploaded'}), 400
                
                backend_info = storage.get_backend_info()
                print(f"⬆️ Starting {backend_info['type']} upload for {file_id}")
                start_time = time.time()
                
                # Prepare metadata
                metadata = {
                    'uploaded_at': datetime.now().isoformat(),
                    'original_name': file.filename,
                    'content_type': file.content_type or 'application/octet-stream'
                }
                
                # Add expiry metadata if provided
                if expires_at:
                    print(f"⏰ File expiry set to: {expires_at}")
                
                success = storage.upload_file(
                    file_content=file_content,
                    file_path=f"files/{file_id}",
                    content_type=file.content_type or 'application/octet-stream'
                )
                
                if not success:
                    return jsonify({'error': 'Upload failed'}), 500
                
                # Add to expiry database if expiry time is provided
                if expires_at:
                    backend_type = backend_info['type']
                    expiry_db.add_file(f"files/{file_id}", expires_at, backend_type)
                
                upload_time = time.time() - start_time
                print(f"✅ {backend_info['type']} upload successful for {file_id} in {upload_time:.2f} seconds")
                
                # Use clean URL format: sharey.org/ABC123.png (new format)
                download_url = url_for('get_content', content_id=file_id, _external=True)
                file_urls.append(download_url)
                print(f"🔗 Generated clean URL: {download_url}")
                
            except Exception as e:
                print(f"❌ Upload failed for {file.filename}: {str(e)}")
                return jsonify({'error': f'Failed to upload file: {str(e)}'}), 500

    print(f"🎉 All uploads completed successfully")
    return jsonify({'urls': file_urls}), 201

# Legacy file route (for backward compatibility)
# Old format: /files/ABC123.png
@app.route('/files/<file_id>', methods=['GET'])
@check_maintenance
def get_file(file_id):
    try:
        # Download file from storage and serve it through Flask
        file_content = storage.download_file(f"files/{file_id}")
        
        # Get the content type from the original upload or guess it
        content_type = 'application/octet-stream'
        file_extension = os.path.splitext(file_id)[1].lower()
        
        # Simple content type mapping
        content_types = {
            '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png',
            '.gif': 'image/gif', '.pdf': 'application/pdf', '.txt': 'text/plain',
            '.mp4': 'video/mp4', '.mp3': 'audio/mpeg', '.zip': 'application/zip'
        }
        content_type = content_types.get(file_extension, 'application/octet-stream')
        
        # Return the file content with proper headers
        return Response(
            file_content,
            mimetype=content_type,
            headers={
                'Content-Disposition': f'inline; filename="{file_id}"',
                'Cache-Control': 'public, max-age=3600'
            }
        )
        
    except Exception as e:
        return jsonify({'error': f'File not found: {str(e)}'}), 404

# Paste creation route
@app.route('/api/paste', methods=['POST'])
@check_maintenance
def create_paste():
    data = request.get_json()
    content = data.get('content')
    expires_at = data.get('expires_at')  # Get expiry from request data
    
    if not content:
        return jsonify({'error': 'Content cannot be empty'}), 400

    # Check paste length limit
    max_length = config.get('paste.max_length', 1000000)
    if len(content) > max_length:
        return jsonify({
            'error': f'Paste too long. Maximum length: {max_length} characters'
        }), 400

    unique_id = str(uuid.uuid4())
    paste_id = unique_id[:6]  # Shorten the UUID for pastes
    
    try:
        # Prepare metadata
        metadata = {
            'uploaded_at': datetime.now().isoformat(),
            'content_type': 'text/plain; charset=utf-8',
            'content_length': len(content)
        }
        
        # Add expiry metadata if provided
        if expires_at:
            metadata['expires_at'] = expires_at
            print(f"⏰ Paste expiry set to: {expires_at}")
        
        # Upload paste to storage
        success = storage.upload_file(
            file_content=content.encode('utf-8'),
            file_path=f"pastes/{paste_id}.txt",
            content_type='text/plain; charset=utf-8',
            metadata=metadata
        )
        
        if not success:
            return jsonify({'error': 'Failed to create paste'}), 500
        
        # Use clean URL format: sharey.org/ABC123 (new format)
        paste_url = url_for('get_content', content_id=paste_id, _external=True)
        return jsonify({'url': paste_url}), 201
        
    except Exception as e:
        return jsonify({'error': f'Failed to create paste: {str(e)}'}), 500

# Legacy paste routes (for backward compatibility)
# Old format: /pastes/ABC123
@app.route('/pastes/<paste_id>', methods=['GET'])
@check_maintenance
def view_paste(paste_id):
    try:
        # Download paste content from storage
        content_bytes = storage.download_file(f"pastes/{paste_id}.txt")
        content = content_bytes.decode('utf-8')
        return render_template('view_paste.html', content=content, paste_id=paste_id)
    except Exception as e:
        return jsonify({'error': 'Paste not found'}), 404
    
@app.route('/pastes/raw/<paste_id>', methods=['GET'])
@check_maintenance
def view_paste_raw(paste_id):
    try:
        # Download paste content from storage
        content_bytes = storage.download_file(f"pastes/{paste_id}.txt")
        content = content_bytes.decode('utf-8')
        return content, 200, {'Content-Type': 'text/plain; charset=utf-8'}
    except Exception as e:
        return jsonify({'error': 'Paste not found'}), 404

# URL Shortener API Routes
@app.route('/api/shorten', methods=['POST'])
@check_maintenance
def create_redirect():
    """Create a new URL redirect"""
    try:
        data = request.get_json()
        
        if not data or 'url' not in data:
            return jsonify({'error': 'URL is required'}), 400
        
        target_url = data['url'].strip()
        custom_code = data.get('code', '').strip()
        expires_in_hours = data.get('expires_in_hours')
        
        # Validate URL
        if not is_valid_url(target_url):
            return jsonify({'error': 'Invalid URL format'}), 400
            
        if not is_safe_url(target_url):
            return jsonify({'error': 'URL not allowed'}), 400
        
        # Generate or validate short code
        if custom_code:
            # Custom code provided
            if len(custom_code) < 3 or len(custom_code) > 20:
                return jsonify({'error': 'Custom code must be 3-20 characters'}), 400
                
            if not re.match(r'^[a-zA-Z0-9_-]+$', custom_code):
                return jsonify({'error': 'Custom code can only contain letters, numbers, hyphens, and underscores'}), 400
                
            # Check if code already exists
            if expiry_db.get_redirect(custom_code) or detect_content_type(custom_code)[0]:
                return jsonify({'error': 'Code already exists'}), 400
                
            short_code = custom_code
        else:
            # Generate random code
            short_code = generate_short_code()
        
        # Calculate expiry
        expires_at = None
        if expires_in_hours and expires_in_hours > 0:
            expires_at = (datetime.utcnow() + timedelta(hours=expires_in_hours)).isoformat() + 'Z'
        
        # Get client IP for logging
        client_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
        
        # Create redirect
        success = expiry_db.add_redirect(
            short_code=short_code,
            target_url=target_url,
            expires_at=expires_at,
            created_by_ip=client_ip
        )
        
        if not success:
            return jsonify({'error': 'Failed to create redirect'}), 500
        
        # Return short URL
        short_url = url_for('get_content', content_id=short_code, _external=True)
        
        return jsonify({
            'short_url': short_url,
            'short_code': short_code,
            'target_url': target_url,
            'expires_at': expires_at
        }), 201
        
    except Exception as e:
        return jsonify({'error': f'Failed to create redirect: {str(e)}'}), 500

@app.route('/api/redirect/<short_code>/info', methods=['GET'])
@check_maintenance  
def redirect_info(short_code):
    """Get information about a redirect (for preview)"""
    try:
        import sqlite3
        
        # Get redirect info without incrementing click count
        conn = sqlite3.connect(expiry_db.db_path)
        cursor = conn.cursor()
        
        cursor.execute('''
            SELECT target_url, created_at, expires_at, click_count, is_active
            FROM url_redirects 
            WHERE short_code = ?
        ''', (short_code,))
        
        result = cursor.fetchone()
        conn.close()
        
        if not result:
            return jsonify({'error': 'Redirect not found'}), 404
            
        target_url, created_at, expires_at, click_count, is_active = result
        
        # Check if expired
        is_expired = False
        if expires_at:
            current_time = datetime.utcnow().isoformat() + 'Z'
            is_expired = expires_at <= current_time
        
        return jsonify({
            'short_code': short_code,
            'target_url': target_url,
            'created_at': created_at,
            'expires_at': expires_at,
            'click_count': click_count,
            'is_active': bool(is_active),
            'is_expired': is_expired
        })
        
    except Exception as e:
        return jsonify({'error': f'Failed to get redirect info: {str(e)}'}), 500

# Admin Panel Routes
@app.route('/admin')
@require_admin
def admin_panel():
    """Admin panel dashboard"""
    admin_config = config.get('admin', {})
    maintenance_config = config.get('maintenance', {})
    
    return render_template('admin.html',
        maintenance_enabled=maintenance_config.get('enabled', False),
        maintenance_message=maintenance_config.get('message', ''),
        estimated_return=maintenance_config.get('estimated_return', ''),
        config=config.config
    )

@app.route('/admin/login', methods=['GET', 'POST'])
def admin_login():
    """Admin login page"""
    admin_config = config.get('admin', {})
    
    if not admin_config.get('enabled', False):
        return jsonify({'error': 'Admin panel is disabled'}), 404
        
    if not admin_config.get('password_hash'):
        return render_template('admin_login.html', 
                             error='Admin password not configured. Please set admin.password_hash in config.json')
    
    if request.method == 'POST':
        password = request.form.get('password')
        if password and verify_password(password, admin_config['password_hash']):
            session['admin_logged_in'] = True
            session['admin_login_time'] = datetime.now().isoformat()
            return redirect(url_for('admin_panel'))
        else:
            return render_template('admin_login.html', error='Invalid password')
    
    return render_template('admin_login.html')

@app.route('/admin/logout')
def admin_logout():
    """Admin logout"""
    session.clear()
    return redirect(url_for('admin_login'))

@app.route('/admin/maintenance', methods=['POST'])
@require_admin
def admin_maintenance():
    """Toggle maintenance mode"""
    action = request.form.get('action')
    message = request.form.get('maintenance_message', '')
    estimated_return = request.form.get('estimated_return', '')
    
    current_config = config.config.copy()
    
    if action == 'enable':
        current_config['maintenance']['enabled'] = True
        current_config['maintenance']['message'] = message
        current_config['maintenance']['estimated_return'] = estimated_return
    elif action == 'disable':
        current_config['maintenance']['enabled'] = False
    
    # Save the updated configuration
    if config.save_config(current_config):
        # Reload config to reflect changes
        config.config = config._load_config()
    
    return redirect(url_for('admin_panel'))

@app.route('/admin/config', methods=['POST'])
@require_admin
def admin_config():
    """Update configuration"""
    max_file_size = request.form.get('max_file_size')
    max_paste_length = request.form.get('max_paste_length')
    
    current_config = config.config.copy()
    
    if max_file_size:
        try:
            current_config['upload']['max_file_size_mb'] = int(max_file_size)
        except ValueError:
            pass
    
    if max_paste_length:
        try:
            current_config['paste']['max_length'] = int(max_paste_length)
        except ValueError:
            pass
    
    # Save the updated configuration
    if config.save_config(current_config):
        # Reload config to reflect changes
        config.config = config._load_config()
        # Update Flask config
        app.config['MAX_CONTENT_LENGTH'] = config.get('upload.max_file_size_mb', 100) * 1024 * 1024
    
    return redirect(url_for('admin_panel'))

@app.route('/admin/urls')
@require_admin
def admin_urls():
    """Admin page for managing URL redirects"""
    redirects = expiry_db.list_redirects(limit=100)
    return render_template('admin.html', 
                         tab='urls',
                         redirects=redirects)

@app.route('/admin/urls/disable/<short_code>', methods=['POST'])
@require_admin
def admin_disable_redirect(short_code):
    """Disable a URL redirect"""
    success = expiry_db.disable_redirect(short_code)
    if success:
        return jsonify({'success': True, 'message': f'Redirect {short_code} disabled'})
    else:
        return jsonify({'success': False, 'message': 'Failed to disable redirect'}), 404

# Error handling for 404 routes
@app.errorhandler(404)
def page_not_found(e):
    return jsonify({'error': 'Page not found'}), 404

# Run the Flask app
if __name__ == '__main__':
    flask_config = config.get_flask_config()
    app.run(
        host=flask_config['host'], 
        port=flask_config['port'], 
        debug=flask_config['debug']
    )
