"""Slides API: lobby-display + shopfloor-screensaver slideshows.

Image files live on disk at instance/slides/<surface>/; TvSlide rows hold play
order + per-slide seconds. Feed + image routes are PUBLIC (kiosks/screensaver
have no auth); management routes require the slides.manage permission (admins
have it by default).
"""

import os
import re

from flask import Blueprint, request, current_app, jsonify
from flask_jwt_extended import jwt_required
from werkzeug.utils import secure_filename

from shopdb.api import (db, success_response, error_response, ErrorCodes,
                        require_permission)
from shopdb.api import send_upload

from ..models import TvSlide

slides_bp = Blueprint('slides', __name__)

SURFACES = frozenset({'lobby', 'shopfloor'})
VALID_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'}
DEFAULT_INTERVAL = 10  # seconds per slide when a slide's own seconds is 0


def _surface_dir(surface):
    return os.path.join(current_app.instance_path, 'slides', surface)


def _natkey(name):
    """Natural sort key so Slide1..Slide11 order numerically, not lexically."""
    return [int(t) if t.isdigit() else t.lower() for t in re.split(r'(\d+)', name)]


def _valid_surface(surface):
    return surface in SURFACES


def _is_image(filename):
    return os.path.splitext(filename)[1].lower() in VALID_EXTENSIONS


# =============================================================================
# PUBLIC - consumed by the lobby dashboard + the screensaver
# =============================================================================

@slides_bp.route('/feed', methods=['GET'])
def feed():
    """Flat playlist for a surface. NOT wrapped in the success_response envelope
    so the screensaver's existing parser needs no change."""
    surface = request.args.get('surface', 'lobby')
    if not _valid_surface(surface):
        surface = 'lobby'
    directory = _surface_dir(surface)
    rows = (TvSlide.query.filter_by(surface=surface)
            .order_by(TvSlide.sortorder, TvSlide.slideid).all())
    slides = [
        {'filename': r.filename, 'seconds': r.seconds or DEFAULT_INTERVAL}
        for r in rows
        if os.path.isfile(os.path.join(directory, r.filename))
    ]
    return jsonify({
        'success': True,
        'surface': surface,
        'basepath': f'/api/slides/img/{surface}/',
        'interval': DEFAULT_INTERVAL,
        'slides': slides,
    })


@slides_bp.route('/img/<surface>/<path:filename>', methods=['GET'])
def serve_image(surface, filename):
    """Serve a slide image with a path-traversal guard."""
    if not _valid_surface(surface):
        return error_response(ErrorCodes.NOT_FOUND, 'Unknown surface', http_code=404)
    safe = os.path.basename(filename)
    if safe != filename or not safe:
        return error_response(ErrorCodes.NOT_FOUND, 'Invalid filename', http_code=404)
    directory = _surface_dir(surface)
    if not os.path.isfile(os.path.join(directory, safe)):
        return error_response(ErrorCodes.NOT_FOUND, 'Slide not found', http_code=404)
    return send_upload(directory, safe)


# =============================================================================
# ADMIN - the Vue slide manager
# =============================================================================

@slides_bp.route('/<surface>', methods=['GET'])
@jwt_required()
@require_permission('slides.manage')
def list_slides(surface):
    if not _valid_surface(surface):
        return error_response(ErrorCodes.VALIDATION_ERROR, 'Unknown surface')
    directory = _surface_dir(surface)
    rows = (TvSlide.query.filter_by(surface=surface)
            .order_by(TvSlide.sortorder, TvSlide.slideid).all())
    out = []
    for r in rows:
        if os.path.isfile(os.path.join(directory, r.filename)):
            item = r.to_dict()
            item['url'] = f'/api/slides/img/{surface}/{r.filename}'
            out.append(item)
    return success_response(out)


@slides_bp.route('/<surface>/upload', methods=['POST'])
@jwt_required()
@require_permission('slides.manage')
def upload_slides(surface):
    if not _valid_surface(surface):
        return error_response(ErrorCodes.VALIDATION_ERROR, 'Unknown surface')

    files = request.files.getlist('files')
    if not files and 'file' in request.files:
        files = [request.files['file']]
    if not files:
        return error_response(ErrorCodes.VALIDATION_ERROR, 'No files uploaded')

    directory = _surface_dir(surface)
    os.makedirs(directory, exist_ok=True)

    # Append after the current max order; upload in natural filename order.
    maxorder = db.session.query(db.func.max(TvSlide.sortorder)).filter_by(surface=surface).scalar()
    order = (maxorder or 0)
    existing = {r.filename for r in TvSlide.query.filter_by(surface=surface).all()}

    added = []
    for f in sorted(files, key=lambda x: _natkey(x.filename or '')):
        if not f or not f.filename or not _is_image(f.filename):
            continue
        base = secure_filename(os.path.basename(f.filename))
        if not base:
            continue
        # Unique-rename if the name is taken (on disk or in db).
        stem, ext = os.path.splitext(base)
        name = base
        n = 1
        while name in existing or os.path.exists(os.path.join(directory, name)):
            name = f"{stem}_{n}{ext}"
            n += 1
        f.save(os.path.join(directory, name))
        existing.add(name)
        order += 1
        db.session.add(TvSlide(surface=surface, filename=name, sortorder=order, seconds=0))
        added.append(name)

    db.session.commit()
    return success_response({'added': added}, message=f'{len(added)} slide(s) uploaded')


@slides_bp.route('/<surface>/order', methods=['POST'])
@jwt_required()
@require_permission('slides.manage')
def reorder_slides(surface):
    if not _valid_surface(surface):
        return error_response(ErrorCodes.VALIDATION_ERROR, 'Unknown surface')
    data = request.get_json() or {}
    order = data.get('order') or []
    for i, filename in enumerate(order):
        row = TvSlide.query.filter_by(surface=surface, filename=filename).first()
        if row:
            row.sortorder = i
    db.session.commit()
    return success_response(message='Order saved')


@slides_bp.route('/<surface>/delete', methods=['POST'])
@jwt_required()
@require_permission('slides.manage')
def delete_slides(surface):
    if not _valid_surface(surface):
        return error_response(ErrorCodes.VALIDATION_ERROR, 'Unknown surface')
    data = request.get_json() or {}
    files = data.get('files') or []
    directory = _surface_dir(surface)
    removed = 0
    for filename in files:
        safe = os.path.basename(filename)
        row = TvSlide.query.filter_by(surface=surface, filename=safe).first()
        fpath = os.path.join(directory, safe)
        if os.path.isfile(fpath):
            try:
                os.remove(fpath)
            except OSError:
                pass
        if row:
            db.session.delete(row)
            removed += 1
    db.session.commit()
    return success_response(message=f'{removed} slide(s) deleted')


@slides_bp.route('/<surface>/<int:slideid>', methods=['PATCH'])
@jwt_required()
@require_permission('slides.manage')
def update_slide(surface, slideid):
    if not _valid_surface(surface):
        return error_response(ErrorCodes.VALIDATION_ERROR, 'Unknown surface')
    row = db.session.get(TvSlide, slideid)
    if not row or row.surface != surface:
        return error_response(ErrorCodes.NOT_FOUND, 'Slide not found', http_code=404)
    data = request.get_json() or {}
    if 'seconds' in data:
        try:
            row.seconds = max(0, int(data['seconds']))
        except (TypeError, ValueError):
            return error_response(ErrorCodes.VALIDATION_ERROR, 'seconds must be an integer')
    db.session.commit()
    return success_response(row.to_dict(), message='Slide updated')
