import eventlet
eventlet.monkey_patch()

import os
import psutil
import subprocess
from pathlib import Path
from flask import Flask, render_template, jsonify, request, abort, session, redirect, url_for
from flask_socketio import SocketIO, emit
import db
import scheduler as sched
from auth import login_required
from addons.sleep import is_sleep_active as _is_sleep_active
from addons.wifi import wifi_status as _wifi_status
from addons.settings.routes import _apply_internal_display
from addon_loader import discover_and_load, get_loaded_addons
from utils.system import local_ip as _local_ip
from utils.display import (
    sway_env as _sway_env,
    apply_saved_audio as _apply_saved_audio,
    apply_display_mode as _apply_display_mode,
)


# Load kiosk.conf (if present) into DB state so settings survive app restarts
def _load_kiosk_conf():
    conf = Path(os.path.dirname(__file__)) / 'kiosk.conf'
    if not conf.exists():
        return
    try:
        vals = {}
        for line in conf.read_text().splitlines():
            line = line.strip()
            if not line or line.startswith('#') or '=' not in line:
                continue
            k, v = line.split('=', 1)
            vals[k.strip()] = v.strip().strip('"').strip("'")
        if 'KIOSK_DISPLAY_MODE' in vals:
            db.set_state('display_mode', vals['KIOSK_DISPLAY_MODE'])
        if 'KIOSK_DISPLAY_OUTPUT' in vals:
            db.set_state('display_output', vals['KIOSK_DISPLAY_OUTPUT'])
        if 'KIOSK_AUDIO_PROFILE_INDEX' in vals:
            db.set_state('audio_profile_index', vals['KIOSK_AUDIO_PROFILE_INDEX'])
        if 'KIOSK_INTERNAL_DISPLAY' in vals:
            db.set_state('internal_display', vals['KIOSK_INTERNAL_DISPLAY'])
    except Exception:
        pass


# load at import time
_load_kiosk_conf()

app = Flask(__name__)

def _get_secret_key():
    """Hämta en persistent SECRET_KEY från DB eller miljövariabel."""
    env_key = os.environ.get('SECRET_KEY')
    if env_key:
        return env_key
    # Spara i DB så att sessioner överlever omstart
    import db as _db
    _db.init_db()
    key = _db.get_state().get('_secret_key')
    if not key:
        key = os.urandom(32).hex()
        _db.set_state('_secret_key', key)
    return key

app.config['SECRET_KEY'] = _get_secret_key()
socketio = SocketIO(app, async_mode='eventlet', cors_allowed_origins='*')

import extensions as _ext
_ext.socketio = socketio

# Ladda addons och starta scheduler direkt vid import, oavsett startmetod
db.init_db()
discover_and_load(app, socketio)
sched.init_scheduler(socketio)

KIOSK_USER = os.environ.get('KIOSK_USER', 'admin')
KIOSK_PASS = os.environ.get('KIOSK_PASS', 'changeme')


def _check_credentials(username, password):
    """Kontrollera inloggning — DB-hash har prioritet över env/default."""
    from werkzeug.security import check_password_hash as _chk
    state = db.get_state()
    stored_hash = state.get('admin_password_hash')
    stored_user = state.get('admin_username') or KIOSK_USER
    if username != stored_user:
        return False
    if stored_hash:
        return _chk(stored_hash, password)
    return password == KIOSK_PASS


# --- Login ---

@app.route('/login', methods=['GET', 'POST'])
def login():
    error = None
    if request.method == 'POST':
        if _check_credentials(request.form['username'], request.form['password']):
            session['logged_in'] = True
            return redirect(request.args.get('next') or url_for('admin'))
        error = 'Fel användarnamn eller lösenord'
    return render_template('login.html', error=error)


@app.route('/logout')
def logout():
    session.clear()
    return redirect(url_for('login'))


# --- Display page (no auth) ---

@app.route('/display')
def display():
    overlays = [a['name'] for a in get_loaded_addons() if a.get('has_overlay')]
    return render_template('display.html', local_ip=_local_ip(), active_overlays=overlays)


# --- Admin page ---

@app.route('/admin')
@login_required
def admin():
    panels = [a for a in get_loaded_addons() if a.get('has_panel')]
    return render_template('admin.html', addon_panels=panels)


# --- API: sysinfo ---

@app.route('/api/sysinfo')
@login_required
def sysinfo():
    uptime = subprocess.check_output('uptime -p', shell=True).decode().strip()
    state = db.get_state()
    return jsonify({
        'cpu':           psutil.cpu_percent(interval=1),
        'ram':           psutil.virtual_memory().percent,
        'disk':          psutil.disk_usage('/').percent,
        'uptime':        uptime,
        'active_type':   state.get('current_type', '—'),
        'active_source': state.get('current_source', '—'),
    })


# --- API: jobs ---

@app.route('/api/jobs', methods=['GET'])
@login_required
def get_jobs():
    return jsonify(db.get_all_jobs())


@app.route('/api/jobs', methods=['POST'])
@login_required
def add_job():
    data = request.get_json()
    if not data:
        abort(400)
    required = ('label', 'duration', 'type', 'source')
    if not all(k in data for k in required):
        abort(400)
    job_id = db.add_job(
        data['label'], float(data['duration']), data['type'], data['source'],
        shuffle=bool(data.get('shuffle', False)),
        show_chat=bool(data.get('show_chat', False)),
        transition_type=str(data.get('transition_type', '')),
        transition_duration=int(data.get('transition_duration', 0)),
    )
    sched.reload_jobs()
    return jsonify({'id': job_id}), 201


@app.route('/api/jobs/<int:job_id>', methods=['DELETE'])
@login_required
def delete_job(job_id):
    db.delete_job(job_id)
    sched.reload_jobs()
    return '', 204


@app.route('/api/jobs/<int:job_id>', methods=['PATCH'])
@login_required
def toggle_job(job_id):
    # Allow partial update (edit fields) if JSON body provided, otherwise toggle enabled
    data = request.get_json(silent=True)
    if data:
        allowed = {}
        for k in ('label', 'duration', 'type', 'source', 'enabled', 'shuffle', 'show_chat'):
            if k in data:
                allowed[k] = data[k]
        if 'duration' in allowed:
            allowed['duration'] = float(allowed['duration'])
        if 'shuffle' in allowed:
            allowed['shuffle'] = 1 if allowed['shuffle'] else 0
        if 'show_chat' in allowed:
            allowed['show_chat'] = 1 if allowed['show_chat'] else 0
        if 'transition_type' in allowed:
            allowed['transition_type'] = str(allowed['transition_type'])
        if 'transition_duration' in allowed:
            allowed['transition_duration'] = int(allowed['transition_duration'] or 0)
        db.update_job(job_id, **allowed)
        sched.reload_jobs()
        return '', 204
    else:
        result = db.toggle_job(job_id)
        if result is None:
            abort(404)
        sched.reload_jobs()
        return jsonify(result)


@app.route('/api/jobs/reorder', methods=['POST'])
@login_required
def reorder_jobs():
    data = request.get_json()
    if not data or 'order' not in data or not isinstance(data['order'], list):
        abort(400)
    ids = [int(x) for x in data['order']]
    db.reorder_jobs(ids)
    sched.reload_jobs()
    return '', 204


# --- Kiosk Livechat --- (migrerat till addons/livechat/)


# --- WebSocket ---

# _is_sleep_active importeras från addons.sleep


@socketio.on('connect')
def on_connect():
    state = db.get_state()
    if state.get('current_type'):
        emit('switch', {
            'type':      state['current_type'],
            'source':    state.get('current_source', ''),
            'scheduled': False,
            'shuffle':   False,
            'show_chat': True,
        })
    if state.get('sleep_enabled') == '1':
        active = _is_sleep_active(
            state.get('sleep_start', '22:00'),
            state.get('sleep_end',   '07:00'),
        )
        emit('sleep_toggle', {'active': active})
    # Emittera alla overlay-inställningar direkt via socket
    # så display.js inte behöver lyckas med HTTP-fetches för att initiera.
    emit('ticker', {
        'enabled':      state.get('ticker_enabled', '0') == '1',
        'source':       state.get('ticker_source',  'text'),
        'text':         state.get('ticker_text',    ''),
        'rss_url':      state.get('ticker_rss_url', ''),
        'rss_interval': int(state.get('ticker_rss_interval', '10')),
        'mode':         state.get('ticker_mode',     'sequential'),
        'separator':    state.get('ticker_separator','◆'),
        'position':     state.get('ticker_position', 'bottom'),
        'text_color':   state.get('ticker_text_color','#ffffff'),
        'font_family':  state.get('ticker_font_family','sans-serif'),
        'bar_color':    state.get('ticker_bar_color', '#000000'),
        'pps':          int(state.get('ticker_pps',   '150')),
    })
    emit('chat_settings', {
        'kiosk_enabled':   state.get('kiosk_chat_enabled',   '0') == '1',
        'kiosk_position':  state.get('kiosk_chat_position',  'right'),
        'kiosk_width_pct': int(state.get('kiosk_chat_width', '30')),
        'kiosk_height':    int(state.get('kiosk_chat_height','80')),
    })
    emit('yt_chat_settings', {
        'enabled':    state.get('yt_chat_enabled',    '0') == '1',
        'position':   state.get('yt_chat_position',   'right'),
        'width_pct':  int(state.get('yt_chat_width_pct', '25')),
        'height':     int(state.get('yt_chat_height',    '100')),
        'bg_color':   state.get('yt_chat_bg_color',   '#000000'),
        'bg_opacity': int(state.get('yt_chat_bg_opacity', '70')),
    })
    emit('clock_settings', {
        'enabled':       state.get('clock_enabled',       '0') == '1',
        'style':         state.get('clock_style',         'digital'),
        'show_date':     state.get('clock_show_date',     '0') == '1',
        'position':      state.get('clock_position',      'top-right'),
        'size':          float(state.get('clock_size',    '2')),
        'color':         state.get('clock_color',         '#ffffff'),
        'opacity':       int(state.get('clock_opacity',   '90')),
        'schedule_mode': state.get('clock_schedule_mode', 'always'),
    })
    emit('transition_config', {
        'type':     state.get('transition_type', 'fade'),
        'duration': int(state.get('transition_duration', '600')),
    })


@socketio.on('admin_switch')
def handle_switch(data):
    type_ = data.get('type')
    source = data.get('source', '')
    db.set_current(type_, source)
    socketio.emit('switch', {'type': type_, 'source': source, 'scheduled': False})


# --- File browser --- (migrerat till addons/filebrowser/)


# --- Settings --- (migrerat till addons/settings/)


# --- Söv-läge --- (migrerat till addons/sleep/)


# --- Ticker --- (migrerat till addons/ticker/)
# --- Chat overlay --- (migrerat till addons/livechat/)
# --- Clock overlay --- (migrerat till addons/clock/)


# --- Music --- (migrerat till addons/music/)


# --- Wi-Fi --- (migrerat till addons/wifi/)
# --- Bluetooth --- (migrerat till addons/bluetooth/)
# --- System + internal_display --- (migrerat till addons/system/ och addons/settings/)


# --- Startup ---

def _apply_settings_after_cage():
    """Background task: wait for sway's Wayland socket, then apply saved display + audio."""
    xdg_runtime = os.environ.get('XDG_RUNTIME_DIR', f'/run/user/{os.getuid()}')
    # Wait up to 30 s for any wayland socket to appear (sway may not be running yet)
    for _ in range(30):
        eventlet.sleep(1)
        socks = [p for p in Path(xdg_runtime).glob('wayland-[0-9]*') if p.suffix != '.lock']
        if socks:
            break
    state = db.get_state()
    saved_mode = state.get('display_mode', '')
    if saved_mode:
        _apply_display_mode(saved_mode, target_output=state.get('display_output') or None)
    _apply_saved_audio()

    # Vänta ordentligt innan workspace-byte så att display-Chromium hinner
    # ladda klart och WebSocket-anslutningen stabiliseras.
    eventlet.sleep(15)
    env = _sway_env()
    if env:
        internal_pref = state.get('internal_display', 'off')
        wifi = _wifi_status()
        should_enable = (not wifi['connected']) or (internal_pref == 'on')
        if should_enable:
            _apply_internal_display(True, env)


@app.route('/api/addons', methods=['GET'])
@login_required
def list_addons():
    """Lista alla kända addons med aktiverad/inaktiverad status."""
    from addon_loader import get_all_addon_names
    state = db.get_state()
    result = []
    for name in get_all_addon_names():
        db_val = state.get(f'addon_{name}_enabled')
        loaded = any(a['name'] == name for a in get_loaded_addons())
        result.append({
            'name': name,
            'enabled': loaded,
            'db_value': db_val,
        })
    return jsonify(result)


@app.route('/api/addons/<name>/toggle', methods=['POST'])
@login_required
def toggle_addon(name):
    """Aktivera/inaktivera ett addon. Kräver omstart för att träda i kraft."""
    from addon_loader import get_all_addon_names
    if name not in get_all_addon_names():
        return jsonify({'error': 'Okänt addon'}), 404
    state = db.get_state()
    current_val = state.get(f'addon_{name}_enabled', '1')
    new_val = '0' if current_val == '1' else '1'
    db.set_state(f'addon_{name}_enabled', new_val)
    socketio.emit('addons_changed', {'name': name, 'enabled': new_val == '1'})
    return jsonify({'name': name, 'enabled': new_val == '1'})


@app.route('/api/transition', methods=['GET'])
def get_transition():
    state = db.get_state()
    return jsonify({
        'type':     state.get('transition_type', 'fade'),
        'duration': int(state.get('transition_duration', '600')),
    })


@app.route('/api/transition', methods=['POST'])
@login_required
def set_transition():
    data = request.get_json() or {}
    t = data.get('type', 'fade')
    d = int(data.get('duration', 600))
    db.set_state('transition_type', t)
    db.set_state('transition_duration', str(d))
    socketio.emit('transition_config', {'type': t, 'duration': d})
    return jsonify({'type': t, 'duration': d})


if __name__ == '__main__':
    # Apply audio immediately (PipeWire is independent of the Wayland compositor)
    _apply_saved_audio()
    # Apply display settings in background after sway/Wayland is up
    eventlet.spawn(_apply_settings_after_cage)
    socketio.run(app, host='0.0.0.0', port=5000)
