import io
import os
import uuid
import hashlib
import secrets
import mimetypes
import tempfile
import zipfile
from datetime import datetime, timezone
from pathlib import Path

from flask import request, jsonify, send_file, current_app, Response

from app.api import api_bp
from app.api.auth import token_required
from app.extensions import db, bcrypt
from app.models.file import File, FilePermission, ShareLink
from app.models.file_lock import FileLock


def _user_upload_dir(user_id):
    base = Path(current_app.config['UPLOAD_PATH'])
    user_dir = base / str(user_id)
    user_dir.mkdir(parents=True, exist_ok=True)
    return user_dir


def _check_file_access(file_obj, user, permission='read'):
    """Check if user has access to file. Owner always has full access."""
    if file_obj.owner_id == user.id:
        return True
    perm = FilePermission.query.filter_by(
        file_id=file_obj.id, user_id=user.id
    ).first()
    if not perm:
        return False
    perm_levels = {'read': 0, 'write': 1, 'admin': 2}
    return perm_levels.get(perm.permission, -1) >= perm_levels.get(permission, 0)


def _get_file_or_403(file_id, user, permission='read'):
    f = db.session.get(File, file_id)
    if not f:
        return None, (jsonify({'error': 'Datei nicht gefunden'}), 404)
    if not _check_file_access(f, user, permission):
        return None, (jsonify({'error': 'Zugriff verweigert'}), 403)
    return f, None


def _compute_checksum(filepath):
    h = hashlib.sha256()
    with open(filepath, 'rb') as fh:
        for chunk in iter(lambda: fh.read(8192), b''):
            h.update(chunk)
    return h.hexdigest()


# --- Folder / File listing ---

@api_bp.route('/files', methods=['GET'])
@token_required
def list_files():
    user = request.current_user
    parent_id = request.args.get('parent_id', None, type=int)

    # Own files in this folder (exclude trashed)
    query = File.query.filter_by(owner_id=user.id, parent_id=parent_id, is_trashed=False)
    files = query.order_by(File.is_folder.desc(), File.name).all()

    # Shared files at root level
    shared = []
    if parent_id is None:
        shared_perms = FilePermission.query.filter_by(user_id=user.id).all()
        shared_file_ids = [p.file_id for p in shared_perms]
        if shared_file_ids:
            shared = File.query.filter(
                File.id.in_(shared_file_ids),
                File.parent_id.is_(None)
            ).order_by(File.is_folder.desc(), File.name).all()

    result = []
    for f in files:
        d = f.to_dict()
        d['has_shares'] = ShareLink.query.filter_by(file_id=f.id).count() > 0
        d['has_permissions'] = FilePermission.query.filter_by(file_id=f.id).count() > 0
        lock = FileLock.get_lock(f.id)
        if lock:
            d['locked'] = True
            d['locked_by'] = lock.user.username
            d['locked_at'] = lock.locked_at.isoformat()
        result.append(d)
    for f in shared:
        d = f.to_dict()
        d['shared'] = True
        result.append(d)

    # Build breadcrumb
    breadcrumb = []
    if parent_id:
        current = db.session.get(File, parent_id)
        while current:
            breadcrumb.insert(0, {'id': current.id, 'name': current.name})
            current = current.parent

    return jsonify({'files': result, 'breadcrumb': breadcrumb}), 200


@api_bp.route('/files/folder', methods=['POST'])
@token_required
def create_folder():
    user = request.current_user
    data = request.get_json()
    name = data.get('name', '').strip()
    parent_id = data.get('parent_id', None)

    if not name:
        return jsonify({'error': 'Ordnername erforderlich'}), 400

    if parent_id:
        parent, err = _get_file_or_403(parent_id, user, 'write')
        if err:
            return err
        if not parent.is_folder:
            return jsonify({'error': 'Uebergeordnetes Element ist kein Ordner'}), 400

    existing = File.query.filter_by(
        owner_id=user.id, parent_id=parent_id, name=name, is_folder=True
    ).first()
    if existing:
        return jsonify({'error': 'Ordner existiert bereits'}), 409

    folder = File(
        owner_id=user.id,
        parent_id=parent_id,
        name=name,
        is_folder=True,
    )
    db.session.add(folder)
    db.session.commit()
    return jsonify(folder.to_dict()), 201


def _ensure_folder_path(user_id, parent_id, path_parts):
    """Create nested folder structure. Returns the ID of the deepest folder."""
    current_parent = parent_id
    for part in path_parts:
        part = part.strip()
        if not part:
            continue
        existing = File.query.filter_by(
            owner_id=user_id, parent_id=current_parent, name=part, is_folder=True
        ).first()
        if existing:
            current_parent = existing.id
        else:
            folder = File(owner_id=user_id, parent_id=current_parent, name=part, is_folder=True)
            db.session.add(folder)
            db.session.flush()
            current_parent = folder.id
    return current_parent


@api_bp.route('/files/ensure-path', methods=['POST'])
@token_required
def ensure_folder_path():
    """Create nested folder structure from a path string like 'Docs/Work/Project'.
    Returns the ID of the deepest folder."""
    user = request.current_user
    data = request.get_json()
    path = data.get('path', '').strip().strip('/')
    parent_id = data.get('parent_id', None)

    if not path:
        return jsonify({'folder_id': parent_id}), 200

    parts = [p for p in path.split('/') if p.strip()]
    folder_id = _ensure_folder_path(user.id, parent_id, parts)
    db.session.commit()

    return jsonify({'folder_id': folder_id}), 200


# --- Upload ---

@api_bp.route('/files/upload', methods=['POST'])
@token_required
def upload_file():
    user = request.current_user
    parent_id = request.form.get('parent_id', None, type=int)

    if parent_id:
        parent, err = _get_file_or_403(parent_id, user, 'write')
        if err:
            return err

    if 'file' not in request.files:
        return jsonify({'error': 'Keine Datei gesendet'}), 400

    uploaded = request.files['file']
    if not uploaded.filename:
        return jsonify({'error': 'Leerer Dateiname'}), 400

    filename = uploaded.filename
    mime = uploaded.content_type or mimetypes.guess_type(filename)[0] or 'application/octet-stream'

    # Save to disk with UUID name
    storage_name = str(uuid.uuid4())
    user_dir = _user_upload_dir(user.id)
    storage_path = user_dir / storage_name
    uploaded.save(str(storage_path))

    size = os.path.getsize(str(storage_path))
    checksum = _compute_checksum(str(storage_path))

    # Check if file with same name exists -> overwrite
    existing = File.query.filter_by(
        owner_id=user.id, parent_id=parent_id, name=filename, is_folder=False
    ).first()

    if existing:
        # Remove old file from disk
        old_path = Path(current_app.config['UPLOAD_PATH']) / str(user.id) / existing.storage_path
        if old_path.exists():
            old_path.unlink()
        existing.storage_path = storage_name
        existing.size = size
        existing.mime_type = mime
        existing.checksum = checksum
        existing.updated_at = datetime.now(timezone.utc)
        db.session.commit()
        return jsonify(existing.to_dict()), 200

    file_obj = File(
        owner_id=user.id,
        parent_id=parent_id,
        name=filename,
        is_folder=False,
        mime_type=mime,
        size=size,
        storage_path=storage_name,
        checksum=checksum,
    )
    db.session.add(file_obj)
    db.session.commit()
    return jsonify(file_obj.to_dict()), 201


# --- Download ---

@api_bp.route('/files/<int:file_id>/download', methods=['GET'])
@token_required
def download_file(file_id):
    user = request.current_user
    f, err = _get_file_or_403(file_id, user, 'read')
    if err:
        return err

    if f.is_folder:
        return _download_folder_as_zip(f)

    filepath = Path(current_app.config['UPLOAD_PATH']) / str(f.owner_id) / f.storage_path
    if not filepath.exists():
        return jsonify({'error': 'Datei auf Datentraeger nicht gefunden'}), 404

    return send_file(str(filepath), mimetype=f.mime_type, as_attachment=True,
                     download_name=f.name)


def _download_folder_as_zip(folder):
    """Stream a folder as ZIP download."""
    upload_path = Path(current_app.config['UPLOAD_PATH'])

    def _add_folder_to_zip(zf, folder_obj, prefix):
        children = File.query.filter_by(parent_id=folder_obj.id).all()
        for child in children:
            if child.is_folder:
                _add_folder_to_zip(zf, child, f'{prefix}{child.name}/')
            else:
                filepath = upload_path / str(child.owner_id) / child.storage_path
                if filepath.exists():
                    zf.write(str(filepath), f'{prefix}{child.name}')

    tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.zip')
    tmp_path = tmp.name
    tmp.close()

    try:
        with zipfile.ZipFile(tmp_path, 'w', zipfile.ZIP_DEFLATED, allowZip64=True) as zf:
            _add_folder_to_zip(zf, folder, '')

        return send_file(tmp_path, mimetype='application/zip', as_attachment=True,
                         download_name=f'{folder.name}.zip')
    finally:
        # Cleanup after response is sent (Flask handles this)
        import atexit
        atexit.register(lambda: os.path.exists(tmp_path) and os.unlink(tmp_path))


# --- Rename / Move ---

@api_bp.route('/files/<int:file_id>', methods=['PUT'])
@token_required
def update_file(file_id):
    user = request.current_user
    f, err = _get_file_or_403(file_id, user, 'write')
    if err:
        return err

    data = request.get_json()
    if 'name' in data:
        name = data['name'].strip()
        if name:
            f.name = name

    if 'parent_id' in data:
        new_parent = data['parent_id']
        if new_parent is not None:
            parent, perr = _get_file_or_403(new_parent, user, 'write')
            if perr:
                return perr
            if not parent.is_folder:
                return jsonify({'error': 'Ziel ist kein Ordner'}), 400
            # Prevent moving folder into itself
            if f.is_folder:
                check = parent
                while check:
                    if check.id == f.id:
                        return jsonify({'error': 'Ordner kann nicht in sich selbst verschoben werden'}), 400
                    check = check.parent
        f.parent_id = new_parent

    f.updated_at = datetime.now(timezone.utc)
    db.session.commit()
    return jsonify(f.to_dict()), 200


# --- Delete (soft-delete -> trash) ---

@api_bp.route('/files/<int:file_id>', methods=['DELETE'])
@token_required
def delete_file(file_id):
    user = request.current_user
    f, err = _get_file_or_403(file_id, user, 'admin')
    if err:
        f = db.session.get(File, file_id)
        if not f or f.owner_id != user.id:
            return jsonify({'error': 'Zugriff verweigert'}), 403

    # Soft-delete: move to trash
    _trash_recursive(f)
    db.session.commit()
    return jsonify({'message': 'In Papierkorb verschoben'}), 200


def _trash_recursive(file_obj):
    """Move file/folder to trash (soft-delete)."""
    now = datetime.now(timezone.utc)
    if not file_obj.is_trashed:
        file_obj.original_parent_id = file_obj.parent_id
        file_obj.parent_id = None
        file_obj.is_trashed = True
        file_obj.trashed_at = now
    if file_obj.is_folder:
        children = File.query.filter_by(parent_id=file_obj.id, is_trashed=False).all()
        for child in children:
            child.is_trashed = True
            child.trashed_at = now


def _delete_permanent(file_obj):
    """Permanently delete a file/folder and its disk data."""
    if file_obj.is_folder:
        children = File.query.filter_by(parent_id=file_obj.id).all()
        for child in children:
            _delete_permanent(child)
    else:
        if file_obj.storage_path:
            filepath = Path(current_app.config['UPLOAD_PATH']) / str(file_obj.owner_id) / file_obj.storage_path
            if filepath.exists():
                filepath.unlink()
    db.session.delete(file_obj)


# --- Trash / Papierkorb ---

@api_bp.route('/files/trash', methods=['GET'])
@token_required
def list_trash():
    """List all trashed files for the current user."""
    user = request.current_user
    trashed = File.query.filter_by(owner_id=user.id, is_trashed=True)\
        .filter(File.original_parent_id.isnot(None) | (File.original_parent_id.is_(None)))\
        .order_by(File.trashed_at.desc()).all()

    # Only show top-level trashed items (not children of trashed folders)
    top_level = []
    trashed_folder_ids = {f.id for f in trashed if f.is_folder}
    for f in trashed:
        # Show if parent is not also trashed
        parent_trashed = False
        if f.original_parent_id:
            parent = db.session.get(File, f.original_parent_id)
            if parent and parent.is_trashed:
                parent_trashed = True
        if not parent_trashed:
            top_level.append(f.to_dict())

    return jsonify(top_level), 200


@api_bp.route('/files/trash/<int:file_id>/restore', methods=['POST'])
@token_required
def restore_from_trash(file_id):
    """Restore a file/folder from trash."""
    user = request.current_user
    f = db.session.get(File, file_id)
    if not f or f.owner_id != user.id or not f.is_trashed:
        return jsonify({'error': 'Nicht gefunden'}), 404

    # Restore to original parent (or root if parent no longer exists)
    original_parent = db.session.get(File, f.original_parent_id) if f.original_parent_id else None
    if original_parent and not original_parent.is_trashed:
        f.parent_id = f.original_parent_id
    else:
        f.parent_id = None

    f.is_trashed = False
    f.trashed_at = None
    f.original_parent_id = None

    # Also restore children
    if f.is_folder:
        _restore_children(f)

    db.session.commit()
    return jsonify({'message': 'Wiederhergestellt'}), 200


def _restore_children(folder):
    children = File.query.filter_by(is_trashed=True).all()
    for child in children:
        # Check if this child was inside the restored folder
        if child.original_parent_id == folder.id or child.parent_id == folder.id:
            child.is_trashed = False
            child.trashed_at = None
            child.parent_id = folder.id
            child.original_parent_id = None
            if child.is_folder:
                _restore_children(child)


@api_bp.route('/files/trash/<int:file_id>', methods=['DELETE'])
@token_required
def delete_permanently(file_id):
    """Permanently delete a trashed file."""
    user = request.current_user
    f = db.session.get(File, file_id)
    if not f or f.owner_id != user.id or not f.is_trashed:
        return jsonify({'error': 'Nicht gefunden'}), 404

    _delete_permanent(f)
    db.session.commit()
    return jsonify({'message': 'Endgueltig geloescht'}), 200


@api_bp.route('/files/trash/empty', methods=['POST'])
@token_required
def empty_trash():
    """Permanently delete all trashed files."""
    user = request.current_user
    trashed = File.query.filter_by(owner_id=user.id, is_trashed=True).all()
    for f in trashed:
        _delete_permanent(f)
    db.session.commit()
    return jsonify({'message': 'Papierkorb geleert'}), 200


# --- Permissions ---

@api_bp.route('/files/<int:file_id>/permissions', methods=['GET'])
@token_required
def get_permissions(file_id):
    user = request.current_user
    f, err = _get_file_or_403(file_id, user, 'admin')
    if err:
        if not (f := db.session.get(File, file_id)) or f.owner_id != user.id:
            return jsonify({'error': 'Zugriff verweigert'}), 403

    perms = FilePermission.query.filter_by(file_id=file_id).all()
    from app.models.user import User
    result = []
    for p in perms:
        u = db.session.get(User, p.user_id)
        result.append({
            'id': p.id,
            'user_id': p.user_id,
            'username': u.username if u else None,
            'permission': p.permission,
        })
    return jsonify(result), 200


@api_bp.route('/files/<int:file_id>/permissions', methods=['POST'])
@token_required
def set_permission(file_id):
    user = request.current_user
    f = db.session.get(File, file_id)
    if not f or f.owner_id != user.id:
        return jsonify({'error': 'Nur der Eigentuemer kann Berechtigungen setzen'}), 403

    data = request.get_json()
    target_user_id = data.get('user_id')
    permission = data.get('permission', 'read')

    if permission not in ('read', 'write', 'admin'):
        return jsonify({'error': 'Ungueltige Berechtigung'}), 400

    from app.models.user import User
    target = db.session.get(User, target_user_id)
    if not target:
        return jsonify({'error': 'Benutzer nicht gefunden'}), 404

    existing = FilePermission.query.filter_by(
        file_id=file_id, user_id=target_user_id
    ).first()
    is_new = not existing
    if existing:
        existing.permission = permission
    else:
        perm = FilePermission(file_id=file_id, user_id=target_user_id, permission=permission)
        db.session.add(perm)

    db.session.commit()

    # Notify user via email
    if is_new:
        try:
            from app.services.system_mail import notify_file_shared_with_user
            notify_file_shared_with_user(f.name, user.username, target)
        except Exception:
            pass

    return jsonify({'message': 'Berechtigung gesetzt'}), 200


@api_bp.route('/files/<int:file_id>/permissions/<int:perm_id>', methods=['DELETE'])
@token_required
def remove_permission(file_id, perm_id):
    user = request.current_user
    f = db.session.get(File, file_id)
    if not f or f.owner_id != user.id:
        return jsonify({'error': 'Nur der Eigentuemer kann Berechtigungen entfernen'}), 403

    perm = db.session.get(FilePermission, perm_id)
    if not perm or perm.file_id != file_id:
        return jsonify({'error': 'Berechtigung nicht gefunden'}), 404

    db.session.delete(perm)
    db.session.commit()
    return jsonify({'message': 'Berechtigung entfernt'}), 200


# --- Share Links ---

@api_bp.route('/files/<int:file_id>/share', methods=['POST'])
@token_required
def create_share_link(file_id):
    user = request.current_user
    f, err = _get_file_or_403(file_id, user, 'read')
    if err:
        return err

    data = request.get_json() or {}
    password = data.get('password')
    expires_at = data.get('expires_at')
    max_downloads = data.get('max_downloads')
    permission = data.get('permission', 'read')

    if permission not in ('read', 'write', 'upload_only'):
        return jsonify({'error': 'Berechtigung muss "read", "write" oder "upload_only" sein'}), 400

    token = secrets.token_urlsafe(32)
    password_hash = None
    if password:
        password_hash = bcrypt.generate_password_hash(password).decode('utf-8')

    exp_dt = None
    if expires_at:
        try:
            exp_dt = datetime.fromisoformat(expires_at).replace(tzinfo=timezone.utc)
        except ValueError:
            return jsonify({'error': 'Ungueltiges Datumsformat'}), 400

    link = ShareLink(
        file_id=file_id,
        token=token,
        permission=permission,
        password_hash=password_hash,
        expires_at=exp_dt,
        created_by=user.id,
        max_downloads=max_downloads,
    )
    db.session.add(link)
    db.session.commit()

    return jsonify({
        'token': token,
        'url': f'/share/{token}',
        'permission': permission,
        'expires_at': exp_dt.isoformat() if exp_dt else None,
        'has_password': bool(password),
    }), 201


@api_bp.route('/files/<int:file_id>/shares', methods=['GET'])
@token_required
def list_share_links(file_id):
    user = request.current_user
    f, err = _get_file_or_403(file_id, user, 'read')
    if err:
        return err

    links = ShareLink.query.filter_by(file_id=file_id).all()
    return jsonify([{
        'id': l.id,
        'token': l.token,
        'permission': l.permission,
        'has_password': bool(l.password_hash),
        'expires_at': l.expires_at.isoformat() if l.expires_at else None,
        'download_count': l.download_count,
        'max_downloads': l.max_downloads,
        'created_at': l.created_at.isoformat(),
    } for l in links]), 200


@api_bp.route('/share/<token>/info', methods=['GET'])
def share_info(token):
    link = ShareLink.query.filter_by(token=token).first()
    if not link:
        return jsonify({'error': 'Link nicht gefunden'}), 404

    if link.is_expired():
        return jsonify({'error': 'Link abgelaufen'}), 410

    if link.is_download_limit_reached():
        return jsonify({'error': 'Download-Limit erreicht'}), 410

    f = db.session.get(File, link.file_id)
    return jsonify({
        'name': f.name,
        'is_folder': f.is_folder,
        'size': f.size,
        'mime_type': f.mime_type,
        'has_password': bool(link.password_hash),
        'permission': link.permission,
        'upload_allowed': f.is_folder and link.permission in ('write', 'upload_only'),
        'download_allowed': link.permission in ('read', 'write'),
    }), 200


def _is_inside_shared_folder(file_obj, shared_folder_id):
    """Check if a file/folder is a descendant of the shared folder."""
    current = file_obj
    while current:
        if current.id == shared_folder_id:
            return True
        if current.parent_id is None:
            return False
        current = current.parent
    return False


@api_bp.route('/share/<token>/files', methods=['GET'])
def share_list_files(token):
    """List files in a shared folder or subfolder."""
    link = ShareLink.query.filter_by(token=token).first()
    if not link:
        return jsonify({'error': 'Link nicht gefunden'}), 404

    if link.is_expired():
        return jsonify({'error': 'Link abgelaufen'}), 410

    if link.permission == 'upload_only':
        return jsonify({'error': 'Dieser Link erlaubt keinen Einblick'}), 403

    if link.password_hash:
        password = request.args.get('password', '') or request.headers.get('X-Share-Password', '')
        if not bcrypt.check_password_hash(link.password_hash, password):
            return jsonify({'error': 'Passwort erforderlich'}), 401

    shared_folder = db.session.get(File, link.file_id)
    if not shared_folder.is_folder:
        return jsonify({'error': 'Kein Ordner'}), 400

    # Allow browsing subfolders via parent_id parameter
    parent_id = request.args.get('parent_id', None, type=int)
    if parent_id is None:
        parent_id = shared_folder.id
    else:
        # Verify the requested parent is inside the shared folder
        target_parent = db.session.get(File, parent_id)
        if not target_parent or not _is_inside_shared_folder(target_parent, shared_folder.id):
            return jsonify({'error': 'Zugriff verweigert'}), 403

    files = File.query.filter_by(parent_id=parent_id, is_trashed=False)\
        .order_by(File.is_folder.desc(), File.name).all()

    # Build breadcrumb from current parent back to shared root
    breadcrumb = []
    if parent_id != shared_folder.id:
        current = db.session.get(File, parent_id)
        while current and current.id != shared_folder.id:
            breadcrumb.insert(0, {'id': current.id, 'name': current.name})
            current = current.parent

    return jsonify({
        'files': [{
            'id': fi.id,
            'name': fi.name,
            'is_folder': fi.is_folder,
            'size': fi.size,
            'mime_type': fi.mime_type,
            'updated_at': fi.updated_at.isoformat() if fi.updated_at else None,
        } for fi in files],
        'breadcrumb': breadcrumb,
        'current_parent_id': parent_id,
        'shared_root_id': shared_folder.id,
    }), 200


@api_bp.route('/share/<token>/files/<int:file_id>/download', methods=['GET'])
def share_download_file(token, file_id):
    """Download a specific file from a shared folder."""
    link = ShareLink.query.filter_by(token=token).first()
    if not link:
        return jsonify({'error': 'Link nicht gefunden'}), 404

    if link.is_expired():
        return jsonify({'error': 'Link abgelaufen'}), 410

    if link.permission not in ('read', 'write'):
        return jsonify({'error': 'Download nicht erlaubt'}), 403

    if link.password_hash:
        password = request.args.get('password', '') or request.headers.get('X-Share-Password', '')
        if not bcrypt.check_password_hash(link.password_hash, password):
            return jsonify({'error': 'Passwort erforderlich'}), 401

    target_file = db.session.get(File, file_id)
    if not target_file:
        return jsonify({'error': 'Datei nicht gefunden'}), 404

    # Check file is inside shared folder (any depth)
    if not _is_inside_shared_folder(target_file, link.file_id):
        return jsonify({'error': 'Datei gehoert nicht zu diesem Ordner'}), 403

    if target_file.is_folder:
        return jsonify({'error': 'Ordner koennen nicht heruntergeladen werden'}), 400

    filepath = Path(current_app.config['UPLOAD_PATH']) / str(target_file.owner_id) / target_file.storage_path
    if not filepath.exists():
        return jsonify({'error': 'Datei nicht gefunden'}), 404

    link.download_count += 1
    db.session.commit()

    return send_file(str(filepath), mimetype=target_file.mime_type, as_attachment=True,
                     download_name=target_file.name)


@api_bp.route('/share/<token>/download-zip', methods=['GET'])
def share_download_zip(token):
    """Download the entire shared folder as ZIP."""
    link = ShareLink.query.filter_by(token=token).first()
    if not link:
        return jsonify({'error': 'Link nicht gefunden'}), 404

    if link.is_expired():
        return jsonify({'error': 'Link abgelaufen'}), 410

    if link.permission not in ('read', 'write'):
        return jsonify({'error': 'Download nicht erlaubt'}), 403

    if link.password_hash:
        password = request.args.get('password', '') or request.headers.get('X-Share-Password', '')
        if not bcrypt.check_password_hash(link.password_hash, password):
            return jsonify({'error': 'Passwort erforderlich'}), 401

    f = db.session.get(File, link.file_id)
    if not f.is_folder:
        return jsonify({'error': 'Kein Ordner'}), 400

    link.download_count += 1
    db.session.commit()

    try:
        from app.services.system_mail import notify_share_link_accessed
        notify_share_link_accessed(link, f.name, request.remote_addr)
    except Exception:
        pass

    return _download_folder_as_zip(f)


@api_bp.route('/share/<token>/files/<int:file_id>', methods=['DELETE'])
def share_delete_file(token, file_id):
    """Delete a file from a shared folder (write permission required)."""
    link = ShareLink.query.filter_by(token=token).first()
    if not link:
        return jsonify({'error': 'Link nicht gefunden'}), 404

    if link.is_expired():
        return jsonify({'error': 'Link abgelaufen'}), 410

    if link.permission != 'write':
        return jsonify({'error': 'Loeschen nicht erlaubt'}), 403

    if link.password_hash:
        password = request.headers.get('X-Share-Password', '')
        if not bcrypt.check_password_hash(link.password_hash, password):
            return jsonify({'error': 'Passwort erforderlich'}), 401

    target_file = db.session.get(File, file_id)
    if not target_file:
        return jsonify({'error': 'Datei nicht gefunden'}), 404

    if not _is_inside_shared_folder(target_file, link.file_id):
        return jsonify({'error': 'Datei gehoert nicht zu diesem Ordner'}), 403

    # Soft-delete: move to trash
    _trash_recursive(target_file)
    db.session.commit()
    return jsonify({'message': 'In Papierkorb verschoben'}), 200


@api_bp.route('/share/<token>/verify', methods=['POST'])
def share_verify(token):
    link = ShareLink.query.filter_by(token=token).first()
    if not link:
        return jsonify({'error': 'Link nicht gefunden'}), 404

    if link.is_expired():
        return jsonify({'error': 'Link abgelaufen'}), 410

    data = request.get_json() or {}
    password = data.get('password', '')

    if link.password_hash:
        if not bcrypt.check_password_hash(link.password_hash, password):
            return jsonify({'error': 'Falsches Passwort'}), 401

    # Generate temporary download token
    download_token = secrets.token_urlsafe(16)
    # Store in link temporarily (simple approach)
    link._download_token = download_token
    return jsonify({'download_token': download_token}), 200


@api_bp.route('/share/<token>/download', methods=['GET'])
def share_download(token):
    link = ShareLink.query.filter_by(token=token).first()
    if not link:
        return jsonify({'error': 'Link nicht gefunden'}), 404

    if link.permission == 'upload_only':
        return jsonify({'error': 'Dieser Link erlaubt nur Upload, keinen Download'}), 403

    if link.is_expired():
        return jsonify({'error': 'Link abgelaufen'}), 410

    if link.is_download_limit_reached():
        return jsonify({'error': 'Download-Limit erreicht'}), 410

    # Check password if set
    if link.password_hash:
        # For password-protected links, require the password as query param or header
        password = request.args.get('password', '') or request.headers.get('X-Share-Password', '')
        if not bcrypt.check_password_hash(link.password_hash, password):
            return jsonify({'error': 'Passwort erforderlich'}), 401

    f = db.session.get(File, link.file_id)
    if f.is_folder:
        return jsonify({'error': 'Ordner-Download noch nicht implementiert'}), 501

    filepath = Path(current_app.config['UPLOAD_PATH']) / str(f.owner_id) / f.storage_path
    if not filepath.exists():
        return jsonify({'error': 'Datei nicht gefunden'}), 404

    link.download_count += 1
    db.session.commit()

    # Notify creator about download
    try:
        from app.services.system_mail import notify_share_link_accessed
        notify_share_link_accessed(link, f.name, request.remote_addr)
    except Exception:
        pass

    return send_file(str(filepath), mimetype=f.mime_type, as_attachment=True,
                     download_name=f.name)


@api_bp.route('/share/<token>/upload', methods=['POST'])
def share_upload(token):
    """Upload a file via a share link (only if the shared item is a folder with write permission)."""
    link = ShareLink.query.filter_by(token=token).first()
    if not link:
        return jsonify({'error': 'Link nicht gefunden'}), 404

    if link.is_expired():
        return jsonify({'error': 'Link abgelaufen'}), 410

    # Check write/upload permission
    if link.permission not in ('write', 'upload_only'):
        return jsonify({'error': 'Dieser Link erlaubt keinen Upload'}), 403

    # Check password if set
    if link.password_hash:
        password = request.form.get('password', '') or request.headers.get('X-Share-Password', '')
        if not bcrypt.check_password_hash(link.password_hash, password):
            return jsonify({'error': 'Passwort erforderlich'}), 401

    f = db.session.get(File, link.file_id)
    if not f.is_folder:
        return jsonify({'error': 'Upload nur in freigegebene Ordner moeglich'}), 400

    if 'file' not in request.files:
        return jsonify({'error': 'Keine Datei gesendet'}), 400

    uploaded = request.files['file']
    if not uploaded.filename:
        return jsonify({'error': 'Leerer Dateiname'}), 400

    filename = uploaded.filename
    mime = uploaded.content_type or mimetypes.guess_type(filename)[0] or 'application/octet-stream'

    storage_name = str(uuid.uuid4())
    user_dir = _user_upload_dir(f.owner_id)
    storage_path = user_dir / storage_name
    uploaded.save(str(storage_path))

    size = os.path.getsize(str(storage_path))
    checksum = _compute_checksum(str(storage_path))

    file_obj = File(
        owner_id=f.owner_id,
        parent_id=f.id,
        name=filename,
        is_folder=False,
        mime_type=mime,
        size=size,
        storage_path=storage_name,
        checksum=checksum,
    )
    db.session.add(file_obj)
    db.session.commit()

    # Notify share link creator about the upload
    try:
        from app.services.system_mail import notify_share_link_upload
        notify_share_link_upload(link, f.name, filename, size, request.remote_addr)
    except Exception:
        pass

    return jsonify(file_obj.to_dict()), 201


@api_bp.route('/share/<token>', methods=['DELETE'])
@token_required
def delete_share_link(token):
    user = request.current_user
    link = ShareLink.query.filter_by(token=token).first()
    if not link:
        return jsonify({'error': 'Link nicht gefunden'}), 404

    if link.created_by != user.id:
        return jsonify({'error': 'Nur der Ersteller kann den Link loeschen'}), 403

    db.session.delete(link)
    db.session.commit()
    return jsonify({'message': 'Link geloescht'}), 200


# --- File Locking ---

@api_bp.route('/files/<int:file_id>/lock', methods=['POST'])
@token_required
def lock_file(file_id):
    """Lock a file (check out). Prevents others from opening/editing."""
    user = request.current_user
    f = db.session.get(File, file_id)
    if not f:
        return jsonify({'error': 'Datei nicht gefunden'}), 404

    # Check existing lock
    existing = FileLock.get_lock(file_id)
    if existing:
        if existing.locked_by == user.id:
            # Already locked by this user - refresh heartbeat
            existing.heartbeat_at = datetime.now(timezone.utc)
            db.session.commit()
            return jsonify(existing.to_dict()), 200
        return jsonify({
            'error': f'Datei wird von {existing.user.username} bearbeitet',
            'locked_by': existing.user.username,
            'locked_at': existing.locked_at.isoformat(),
        }), 423  # 423 Locked

    data = request.get_json(silent=True) or {}
    lock = FileLock(
        file_id=file_id,
        locked_by=user.id,
        client_info=data.get('client_info', ''),
    )
    db.session.add(lock)
    db.session.commit()
    return jsonify(lock.to_dict()), 200


@api_bp.route('/files/<int:file_id>/unlock', methods=['POST'])
@token_required
def unlock_file(file_id):
    """Unlock a file (check in)."""
    user = request.current_user
    lock = FileLock.get_lock(file_id)
    if not lock:
        return jsonify({'message': 'Datei war nicht gesperrt'}), 200

    if lock.locked_by != user.id and user.role != 'admin':
        return jsonify({'error': 'Nur der Sperrer oder ein Admin kann entsperren'}), 403

    db.session.delete(lock)
    db.session.commit()
    return jsonify({'message': 'Datei entsperrt'}), 200


@api_bp.route('/files/<int:file_id>/heartbeat', methods=['POST'])
@token_required
def heartbeat_file(file_id):
    """Heartbeat - signal that the file is still being edited."""
    user = request.current_user
    lock = FileLock.get_lock(file_id)
    if not lock:
        return jsonify({'error': 'Keine Sperre vorhanden'}), 404

    if lock.locked_by != user.id:
        return jsonify({'error': 'Sperre gehoert einem anderen Benutzer'}), 403

    lock.heartbeat_at = datetime.now(timezone.utc)
    db.session.commit()
    return jsonify({'message': 'Heartbeat aktualisiert'}), 200


@api_bp.route('/files/<int:file_id>/lock-status', methods=['GET'])
@token_required
def lock_status(file_id):
    """Check if a file is locked."""
    lock = FileLock.get_lock(file_id)
    if not lock:
        return jsonify({'locked': False}), 200

    return jsonify({
        'locked': True,
        'locked_by': lock.user.username,
        'locked_by_id': lock.locked_by,
        'locked_at': lock.locked_at.isoformat(),
        'client_info': lock.client_info,
    }), 200


@api_bp.route('/files/locks', methods=['GET'])
@token_required
def list_locks():
    """List all active locks (for admin overview or sync clients)."""
    # Cleanup expired first
    FileLock.cleanup_expired()

    locks = FileLock.query.all()
    return jsonify([l.to_dict() for l in locks]), 200


# --- Sync API ---

@api_bp.route('/sync/tree', methods=['GET'])
@token_required
def sync_tree():
    """Returns complete file tree with checksums for sync clients."""
    user = request.current_user

    def _build_tree(parent_id):
        files = File.query.filter_by(owner_id=user.id, parent_id=parent_id)\
            .order_by(File.is_folder.desc(), File.name).all()
        result = []
        for f in files:
            entry = {
                'id': f.id,
                'name': f.name,
                'is_folder': f.is_folder,
                'size': f.size,
                'checksum': f.checksum,
                'updated_at': f.updated_at.isoformat() if f.updated_at else None,
            }
            lock = FileLock.get_lock(f.id)
            if lock:
                entry['locked'] = True
                entry['locked_by'] = lock.user.username
            if f.is_folder:
                entry['children'] = _build_tree(f.id)
            result.append(entry)
        return result

    return jsonify({'tree': _build_tree(None)}), 200


@api_bp.route('/sync/changes', methods=['GET'])
@token_required
def sync_changes():
    """Returns files changed since a given timestamp."""
    user = request.current_user
    since = request.args.get('since')

    if not since:
        return jsonify({'error': 'Parameter "since" erforderlich'}), 400

    try:
        since_dt = datetime.fromisoformat(since).replace(tzinfo=timezone.utc)
    except ValueError:
        return jsonify({'error': 'Ungueltiges Datumsformat'}), 400

    changed = File.query.filter(
        File.owner_id == user.id,
        File.updated_at > since_dt
    ).all()

    return jsonify({
        'changes': [f.to_dict() for f in changed],
        'server_time': datetime.now(timezone.utc).isoformat(),
    }), 200
