
from datetime import datetime, timedelta, timezone
from functools import wraps
import secrets

import requests
from flask import (
    Blueprint, current_app, flash, jsonify, redirect, render_template,
    request, session, url_for
)

from .models import EventLog, Stream, StreamSession, User, db
from .services.mailer import send_reset_email, send_welcome_email
from .utils import ffprobe_stream, generate_password, generate_token, hls_url, ingest_url_with_query

bp = Blueprint('main', __name__)


def current_user():
    uid = session.get('user_id')
    if not uid:
        return None
    return db.session.get(User, uid)


def log_event(stream_id, message, level='info', user_id=None):
    db.session.add(EventLog(stream_id=stream_id, message=message, level=level, user_id=user_id))
    db.session.commit()


def login_required(role=None):
    def deco(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            user = current_user()
            if not user or not user.active:
                return redirect(url_for('main.login'))
            if role and user.role != role:
                return redirect(url_for('main.index'))
            return fn(*args, **kwargs)
        return wrapper
    return deco


def stream_for_current_user_or_403(stream_id):
    user = current_user()
    stream = db.session.get(Stream, stream_id)
    if not stream:
        return None
    if user.role == 'admin':
        return stream
    if stream.user_id != user.id:
        return None
    return stream


def media_api_paths():
    try:
        res = requests.get(f"{current_app.config['MEDIAMTX_API_URL'].rstrip('/')}/v3/paths/list", timeout=2)
        res.raise_for_status()
        return res.json().get('items', [])
    except Exception as exc:
        current_app.logger.warning(f'MediaMTX API unavailable: {exc}')
        return []


def path_info_for(slug):
    for item in media_api_paths():
        if item.get('name') == slug:
            return item
    return None


def extract_slug_token_from_auth(data: dict):
    path = (data.get('path') or '').strip().strip('/')
    query = (data.get('query') or '').strip()
    user = (data.get('user') or '').strip()
    password = (data.get('password') or '').strip()

    slug = path
    token = None
    if '/' in path:
        slug, token = path.split('/', 1)
        slug = slug.strip()
        token = token.strip()

    if not token and query:
        from urllib.parse import parse_qs
        token = parse_qs(query).get('token', [None])[0]

    if not token and password:
        token = password
    if not token and user and user.lower() != 'any':
        token = user
    return slug, token


def update_live_state(stream: Stream):
    info = path_info_for(stream.slug)
    stream.is_live = bool(info and (info.get('ready') or info.get('online')))
    db.session.commit()
    return info


def stats_for(stream: Stream):
    if not stream.is_live:
        return {}
    return ffprobe_stream(hls_url(current_app.config['MEDIA_BASE_URL'], stream.slug))


@bp.app_context_processor
def inject_globals():
    return {'current_user': current_user()}


@bp.get('/')
def index():
    user = current_user()
    if not user:
        return redirect(url_for('main.login'))
    if user.role == 'admin':
        return redirect(url_for('main.admin_dashboard'))
    if user.stream:
        return redirect(url_for('main.streamer_panel', stream_id=user.stream.id))
    flash('Din konto har ingen stream knyttet endnu.', 'error')
    return redirect(url_for('main.logout'))


@bp.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form.get('username', '').strip()
        password = request.form.get('password', '')
        user = User.query.filter_by(username=username).first()
        if user and user.active and user.check_password(password):
            session.clear()
            session['user_id'] = user.id
            if user.force_password_change:
                return redirect(url_for('main.force_password_change'))
            if user.role == 'admin':
                return redirect(url_for('main.admin_dashboard'))
            return redirect(url_for('main.index'))
        flash('Forkert brugernavn eller kodeord.', 'error')
    return render_template('login.html')


@bp.get('/logout')
def logout():
    session.clear()
    return redirect(url_for('main.login'))


@bp.route('/force-password-change', methods=['GET', 'POST'])
@login_required()
def force_password_change():
    user = current_user()
    if request.method == 'POST':
        p1 = request.form.get('password', '')
        p2 = request.form.get('password_confirm', '')
        if len(p1) < 10:
            flash('Kode skal være mindst 10 tegn.', 'error')
        elif p1 != p2:
            flash('Koderne matcher ikke.', 'error')
        else:
            user.set_password(p1)
            user.force_password_change = False
            db.session.commit()
            flash('Din kode er ændret.', 'success')
            return redirect(url_for('main.index'))
    return render_template('force_password_change.html')


@bp.route('/forgot-password', methods=['GET', 'POST'])
def forgot_password():
    if request.method == 'POST':
        identity = request.form.get('identity', '').strip()
        user = User.query.filter((User.username == identity) | (User.email == identity)).first()
        if user:
            token = secrets.token_urlsafe(32)
            user.reset_token = token
            user.reset_token_expires_at = datetime.now(timezone.utc) + timedelta(minutes=current_app.config['RESET_TOKEN_TTL_MINUTES'])
            db.session.commit()
            reset_url = url_for('main.reset_password', token=token, _external=True)
            send_reset_email(user, reset_url)
        flash('Hvis brugeren findes, er der sendt et reset-link.', 'success')
    return render_template('forgot_password.html')


@bp.route('/reset-password/<token>', methods=['GET', 'POST'])
def reset_password(token):
    user = User.query.filter_by(reset_token=token).first()
    if not user or not user.reset_token_expires_at or user.reset_token_expires_at < datetime.now(timezone.utc):
        flash('Reset-link er ugyldigt eller udløbet.', 'error')
        return redirect(url_for('main.login'))

    if request.method == 'POST':
        p1 = request.form.get('password', '')
        p2 = request.form.get('password_confirm', '')
        if len(p1) < 10:
            flash('Kode skal være mindst 10 tegn.', 'error')
        elif p1 != p2:
            flash('Koderne matcher ikke.', 'error')
        else:
            user.set_password(p1)
            user.force_password_change = False
            user.reset_token = None
            user.reset_token_expires_at = None
            db.session.commit()
            flash('Din kode er nulstillet. Log ind nu.', 'success')
            return redirect(url_for('main.login'))
    return render_template('reset_password.html')


@bp.get('/admin/dashboard')
@login_required('admin')
def admin_dashboard():
    users = User.query.order_by(User.created_at.desc()).all()
    streams = Stream.query.order_by(Stream.created_at.desc()).all()
    path_map = {item.get('name'): item for item in media_api_paths()}
    return render_template('admin_dashboard.html', users=users, streams=streams, path_map=path_map)


@bp.route('/admin/streamers/new', methods=['GET', 'POST'])
@login_required('admin')
def create_streamer():
    if request.method == 'POST':
        display_name = request.form.get('display_name', '').strip()
        username = request.form.get('username', '').strip().lower()
        email = request.form.get('email', '').strip().lower()
        stream_name = request.form.get('stream_name', '').strip()
        slug = request.form.get('slug', '').strip().lower().replace(' ', '-').replace('/', '-')
        chat_type = request.form.get('chat_type', 'none')
        chat_url = request.form.get('chat_url', '').strip()
        notes = request.form.get('notes', '').strip()

        if not all([display_name, username, email, stream_name, slug]):
            flash('Alle obligatoriske felter skal udfyldes.', 'error')
            return render_template('create_streamer.html')
        if User.query.filter((User.username == username) | (User.email == email)).first():
            flash('Brugernavn eller e-mail findes allerede.', 'error')
            return render_template('create_streamer.html')
        if Stream.query.filter_by(slug=slug).first():
            flash('Slug findes allerede.', 'error')
            return render_template('create_streamer.html')

        temp_password = generate_password()
        user = User(
            username=username,
            email=email,
            display_name=display_name,
            role='streamer',
            active=True,
            force_password_change=True,
        )
        user.set_password(temp_password)
        db.session.add(user)
        db.session.flush()

        stream = Stream(
            user_id=user.id,
            name=stream_name,
            slug=slug,
            token=generate_token(),
            chat_type=chat_type,
            chat_url=chat_url,
            notes=notes,
            active=True,
        )
        db.session.add(stream)
        db.session.commit()
        send_welcome_email(user, temp_password)
        log_event(stream.id, 'Streamer og stream oprettet', user_id=current_user().id)
        flash('Streamer og stream oprettet. Mail er sendt.', 'success')
        return redirect(url_for('main.stream_detail_admin', stream_id=stream.id))
    return render_template('create_streamer.html')


@bp.get('/admin/streams/<int:stream_id>')
@login_required('admin')
def stream_detail_admin(stream_id):
    stream = db.session.get(Stream, stream_id)
    if not stream:
        return redirect(url_for('main.admin_dashboard'))
    path_info = update_live_state(stream)
    stats = stats_for(stream)
    sessions = StreamSession.query.filter_by(stream_id=stream.id).order_by(StreamSession.started_at.desc()).limit(20).all()
    logs = EventLog.query.filter_by(stream_id=stream.id).order_by(EventLog.created_at.desc()).limit(50).all()
    host = current_app.config['PUBLIC_BASE_URL'].replace('https://', '').replace('http://', '').rstrip('/')
    return render_template(
        'stream_detail_admin.html',
        stream=stream,
        stats=stats,
        sessions=sessions,
        logs=logs,
        path_info=path_info,
        public_hls=hls_url(current_app.config['MEDIA_PUBLIC_HLS_BASE'], stream.slug),
        ingest_full=ingest_url_with_query(host, stream.slug, stream.token),
        ingest_server=f'rtmp://{host}:1935/{stream.slug}',
        ingest_key=stream.token,
        ingest_alt_server=f'rtmp://{host}:1935',
        ingest_alt_key=f'{stream.slug}/{stream.token}',
    )


@bp.post('/admin/streams/<int:stream_id>/rotate-token')
@login_required('admin')
def rotate_stream_token(stream_id):
    stream = db.session.get(Stream, stream_id)
    if not stream:
        return redirect(url_for('main.admin_dashboard'))
    stream.token = generate_token()
    db.session.commit()
    log_event(stream.id, 'Token roteret', 'warning', current_user().id)
    flash('Token er roteret.', 'success')
    return redirect(url_for('main.stream_detail_admin', stream_id=stream.id))


@bp.post('/admin/streams/<int:stream_id>/toggle')
@login_required('admin')
def toggle_stream(stream_id):
    stream = db.session.get(Stream, stream_id)
    if not stream:
        return redirect(url_for('main.admin_dashboard'))
    stream.active = not stream.active
    db.session.commit()
    log_event(stream.id, f"Stream {'aktiveret' if stream.active else 'deaktiveret'}", 'warning', current_user().id)
    flash('Stream-status er ændret.', 'success')
    return redirect(url_for('main.stream_detail_admin', stream_id=stream.id))


@bp.post('/admin/users/<int:user_id>/send-reset')
@login_required('admin')
def admin_send_reset(user_id):
    user = db.session.get(User, user_id)
    if not user:
        return redirect(url_for('main.admin_dashboard'))
    token = secrets.token_urlsafe(32)
    user.reset_token = token
    user.reset_token_expires_at = datetime.now(timezone.utc) + timedelta(minutes=current_app.config['RESET_TOKEN_TTL_MINUTES'])
    db.session.commit()
    reset_url = url_for('main.reset_password', token=token, _external=True)
    send_reset_email(user, reset_url)
    flash('Reset-mail sendt.', 'success')
    return redirect(url_for('main.admin_dashboard'))


@bp.get('/streamer/panel/<int:stream_id>')
@login_required()
def streamer_panel(stream_id):
    stream = stream_for_current_user_or_403(stream_id)
    if not stream:
        return redirect(url_for('main.index'))
    path_info = update_live_state(stream)
    stats = stats_for(stream)
    sessions = StreamSession.query.filter_by(stream_id=stream.id).order_by(StreamSession.started_at.desc()).limit(10).all()
    return render_template(
        'streamer_panel.html',
        stream=stream,
        stats=stats,
        sessions=sessions,
        path_info=path_info,
        public_hls=hls_url(current_app.config['MEDIA_PUBLIC_HLS_BASE'], stream.slug),
    )


@bp.get('/api/streams/<int:stream_id>/status')
@login_required()
def stream_status(stream_id):
    stream = stream_for_current_user_or_403(stream_id)
    if not stream:
        return jsonify({'error': 'not found'}), 404
    path_info = update_live_state(stream)
    stats = stats_for(stream)
    return jsonify({
        'id': stream.id,
        'slug': stream.slug,
        'is_live': stream.is_live,
        'path_info': path_info,
        'stats': stats,
        'preview_url': hls_url(current_app.config['MEDIA_PUBLIC_HLS_BASE'], stream.slug),
        'chat_url': stream.chat_url,
    })


@bp.route('/api/mediamtx/auth', methods=['POST'])
def mediamtx_auth():
    data = request.get_json(silent=True) or request.form.to_dict() or {}
    action = (data.get('action') or '').strip()
    slug, token = extract_slug_token_from_auth(data)

    if action == 'read':
        return 'ok', 200
    if action != 'publish':
        return 'denied', 401
    if not slug or not token:
        return 'denied', 401

    stream = Stream.query.filter_by(slug=slug, active=True).first()
    if not stream or stream.token != token:
        return 'denied', 401

    ip = data.get('ip')
    session_row = StreamSession(stream_id=stream.id, status='live', publisher_ip=ip, source_type=data.get('protocol'), source_id=data.get('id'))
    db.session.add(session_row)
    stream.is_live = True
    db.session.commit()
    return 'ok', 200


@bp.get('/healthz')
def healthz():
    return jsonify({'ok': True})
