from flask import (
    Flask,
    render_template,
    request,
    redirect,
    url_for,
    flash,
    session,
    send_from_directory,
    send_file
)

from models import (
    db,
    User,
    Video,
    Download,
    Activity,
    LoginAttempt,
    PasswordResetToken,
    Playlist,
    PlaylistVideo,
    KioskStatus
)

from flask_login import (
    LoginManager,
    login_user,
    logout_user,
    login_required,
    current_user
)

from werkzeug.security import (
    generate_password_hash,
    check_password_hash
)

from werkzeug.utils import secure_filename

from datetime import datetime, timedelta
from sqlalchemy import func

import os
import secrets

from flask_migrate import Migrate
from flask_mail import Mail, Message
import shutil
import tempfile
import fcntl
from flask_wtf.csrf import CSRFProtect


# =====================================
# APP CONFIG
# =====================================

app = Flask(__name__)
csrf = CSRFProtect(app)


# =====================================
# DATABASE
# =====================================

app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY")
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("SQLALCHEMY_DATABASE_URI") 
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False



# =====================================
# SESSION SECURITY
# =====================================

app.config["SESSION_PERMANENT"] = False

app.config["SESSION_COOKIE_HTTPONLY"] = True

app.config["SESSION_COOKIE_SECURE"] = True

app.config["SESSION_COOKIE_SAMESITE"] = "Lax"

app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(
    hours=2
)


# =====================================
# UPLOADS
# =====================================

app.config["UPLOAD_FOLDER"] = os.path.join(
    app.root_path,
    "uploads",
    "videos"
)

# =====================================
# CENTRAL VIDEO CACHE
# =====================================

app.config["CACHE_FOLDER"] = os.path.join(
    app.root_path,
    "cache",
    "videos"
)

app.config["PLAYLIST_CACHE_FOLDER"] = os.path.join(
    app.root_path,
    "cache",
    "playlists"
)

os.makedirs(
    app.config["CACHE_FOLDER"],
    exist_ok=True
)

os.makedirs(
    app.config["PLAYLIST_CACHE_FOLDER"],
    exist_ok=True
)

# =====================================
# EMAIL CONFIGURATION
# =====================================

app.config["MAIL_SERVER"] = os.environ.get(
    "MAIL_SERVER",
    "smtp.gmail.com"
)

app.config["MAIL_PORT"] = int(
    os.environ.get(
        "MAIL_PORT",
        587
    )
)

app.config["MAIL_USE_TLS"] = True

app.config["MAIL_USERNAME"] = os.environ.get(
    "MAIL_USERNAME"
)

app.config["MAIL_PASSWORD"] = os.environ.get(
    "MAIL_PASSWORD"
)

app.config["MAIL_DEFAULT_SENDER"] = (
    app.config["MAIL_USERNAME"]
)

mail = Mail(app)


# =====================================
# ALLOWED VIDEO FILES
# =====================================

ALLOWED_EXTENSIONS = {
    "mp4",
    "mov",
    "avi",
    "mkv"
}


# =====================================
# DATABASE INITIALIZATION
# =====================================

db.init_app(app)

migrate = Migrate(
    app,
    db
)

os.makedirs(
    app.config["UPLOAD_FOLDER"],
    exist_ok=True
)


# =====================================
# LOGIN SETUP
# =====================================

login_manager = LoginManager()

login_manager.init_app(app)

login_manager.login_view = "login"


@login_manager.user_loader
def load_user(user_id):

    return User.query.get(
        int(user_id)
    )


# =====================================
# HELPERS
# =====================================

def allowed_file(filename):

    return (
        "." in filename
        and filename.rsplit(
            ".",
            1
        )[1].lower()
        in ALLOWED_EXTENSIONS
    )


# =====================================
# ROLE HELPERS
# =====================================

def is_superadmin():

    return (
        current_user.is_authenticated
        and current_user.role == "superadmin"
    )


def is_admin():

    return (
        current_user.is_authenticated
        and current_user.role == "admin"
    )

def is_shop():

    return (
        current_user.is_authenticated
        and current_user.role == "shop"
    )

# =====================================
# CENTRAL CACHE HELPERS
# =====================================

def get_video_cache_path(video):
    """
    Returns the central cached location for a video.
    """

    extension = os.path.splitext(
        video.filename
    )[1].lower()

    return os.path.join(
        app.config["CACHE_FOLDER"],
        f"{video.id}{extension}"
    )


def get_video_lock_path(video):
    """
    Lock file used to prevent multiple workers
    from caching the same video simultaneously.
    """

    return os.path.join(
        app.config["CACHE_FOLDER"],
        f"{video.id}.lock"
    )


def ensure_video_cached(video):
    """
    Make sure a video exists in the central server cache.

    If another shop is already caching this video,
    this process waits for that operation to finish.

    Returns:
        path to the cached video
    """

    source_path = os.path.join(
        app.config["UPLOAD_FOLDER"],
        video.filename
    )

    cache_path = get_video_cache_path(video)
    lock_path = get_video_lock_path(video)

    # =====================================
    # SOURCE MUST EXIST
    # =====================================

    if not os.path.exists(source_path):

        raise FileNotFoundError(
            f"Source video does not exist: {source_path}"
        )

    # =====================================
    # ALREADY CACHED
    # =====================================

    if os.path.exists(cache_path):

        return cache_path

    # =====================================
    # OPEN LOCK FILE
    # =====================================

    with open(lock_path, "w") as lock_file:

        # =================================
        # WAIT FOR OTHER PROCESS
        # =================================

        fcntl.flock(
            lock_file,
            fcntl.LOCK_EX
        )

        try:

            # =============================
            # CHECK AGAIN
            #
            # Another shop may have
            # completed the cache while
            # we were waiting for the lock.
            # =============================

            if os.path.exists(cache_path):

                return cache_path

            # =============================
            # CREATE TEMPORARY FILE
            # =============================

            cache_directory = (
                app.config["CACHE_FOLDER"]
            )

            file_descriptor, temp_path = (
                tempfile.mkstemp(
                    prefix=f".{video.id}_",
                    suffix=".tmp",
                    dir=cache_directory
                )
            )

            os.close(
                file_descriptor
            )

            try:

                # =========================
                # COPY SOURCE TO CACHE
                # =========================

                with open(
                    source_path,
                    "rb"
                ) as source_file:

                    with open(
                        temp_path,
                        "wb"
                    ) as cache_file:

                        shutil.copyfileobj(
                            source_file,
                            cache_file,
                            length=1024 * 1024
                        )

                        cache_file.flush()

                        os.fsync(
                            cache_file.fileno()
                        )

                # =========================
                # ATOMICALLY ACTIVATE CACHE
                # =========================

                os.replace(
                    temp_path,
                    cache_path
                )

            finally:

                # =========================
                # CLEAN TEMP FILE
                # =========================

                if os.path.exists(
                    temp_path
                ):

                    os.remove(
                        temp_path
                    )

        finally:

            # =============================
            # RELEASE LOCK
            # =============================

            fcntl.flock(
                lock_file,
                fcntl.LOCK_UN
            )

    return cache_path

# =====================================
# VIDEO MIME TYPE
# =====================================

def get_video_mimetype(filename):

    extension = (
        os.path.splitext(
            filename
        )[1]
        .lower()
    )

    mimetypes_map = {

        ".mp4": "video/mp4",

        ".mov": "video/quicktime",

        ".avi": "video/x-msvideo",

        ".mkv": "video/x-matroska"

    }

    return mimetypes_map.get(
        extension,
        "application/octet-stream"
    )



# =====================================
# SUPERADMIN SEED
# =====================================

def seed_superadmin():

    # =====================================
    # SUPERADMIN IS IDENTIFIED BY ROLE
    # =====================================

    existing = User.query.filter_by(
        role="superadmin"
    ).first()

    if existing:
        return

    # =====================================
    # GET INITIAL CREDENTIALS
    # FROM ENVIRONMENT VARIABLES
    # =====================================

    username = os.environ.get(
        "SUPERADMIN_USERNAME"
    )

    password = os.environ.get(
        "SUPERADMIN_PASSWORD"
    )

    # =====================================
    # REQUIRE CREDENTIALS
    # =====================================

    if not username:
        raise RuntimeError(
            "SUPERADMIN_USERNAME environment variable is not configured."
        )

    if not password:
        raise RuntimeError(
            "SUPERADMIN_PASSWORD environment variable is not configured."
        )

    # =====================================
    # CREATE SUPERADMIN
    # =====================================

    admin = User(
        name="System Super Administrator",
        username=username,
        role="superadmin"
    )

    admin.set_password(
        password
    )

    db.session.add(admin)

    db.session.commit()

    print(
        "Initial SuperAdmin created successfully."
    )

    print(
        f"Username: {username}"
    )

    print(
        "Initial SuperAdmin password was loaded from the environment."
    )
# =====================================
# HOME
# =====================================

@app.route("/")
def index():

    if current_user.is_authenticated:

        return redirect(
            url_for("dashboard")
        )

    return render_template(
        "index.html"
    )


# =====================================
# LOGIN
# =====================================

@app.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 check_password_hash(
                user.password,
                password
            )
        ):

            login_user(user)

            attempt = LoginAttempt(
                username=username,
                ip_address=request.remote_addr,
                successful=True
            )

            db.session.add(attempt)

            db.session.commit()


            return redirect(
                url_for("dashboard")
            )

        # =====================================
        # FAILED LOGIN
        # =====================================

        attempt = LoginAttempt(
            username=username,
            ip_address=request.remote_addr,
            successful=False
        )

        db.session.add(attempt)

        db.session.commit()



        return redirect(
            url_for("login")
        )

    return render_template(
        "login.html"
    )


# =====================================
# LOGOUT
# =====================================

@app.route("/logout")
@login_required
def logout():

    logout_user()

    session.clear()


    return redirect(
        url_for("login")
    )

# =====================================================
# DASHBOARD
# =====================================================

@app.route("/dashboard")
@login_required
def dashboard():

    today = datetime.utcnow().date()

    # =================================================
    # LAST 7 DAYS
    # =================================================

    last_7_days = [
        today - timedelta(days=i)
        for i in range(6, -1, -1)
    ]

    labels = [
        d.strftime("%a")
        for d in last_7_days
    ]

    start_date = last_7_days[0]

    end_date = (
        last_7_days[-1]
        + timedelta(days=1)
    )

    # =================================================
    # DOWNLOAD STATISTICS
    # =================================================

    results = (
        db.session.query(
            func.date(
                Download.download_date
            ).label("day"),

            func.count(
                Download.id
            ).label("count")
        )

        .filter(
            Download.download_date >= start_date
        )

        .filter(
            Download.download_date < end_date
        )

        .group_by(
            func.date(
                Download.download_date
            )
        )

        .all()
    )

    downloads_map = {
        row.day.strftime("%a"): row.count
        for row in results
    }

    data = [
        downloads_map.get(day, 0)
        for day in labels
    ]

    # =================================================
    # OTHER STATISTICS
    # =================================================

    total_videos = Video.query.count()

    total_users = User.query.count()

    recent_videos = (
        Video.query

        .order_by(
            Video.upload_date.desc()
        )

        .limit(5)

        .all()
    )

    activities = (
        Activity.query

        .order_by(
            Activity.timestamp.desc()
        )

        .limit(10)

        .all()
    )

    # =================================================
    # KIOSK STATUS
    #
    # ONLY ADMIN + SUPERADMIN CAN SEE SHOP KIOSKS.
    #
    # IMPORTANT:
    # Only users whose role is "shop" are included.
    # =================================================

    kiosks = []

    if current_user.role in [
        "admin",
        "superadmin"
    ]:

        kiosk_records = (

            KioskStatus.query

            .join(
                User,
                KioskStatus.user_id == User.id
            )

            .filter(
                User.role == "shop"
            )

            .order_by(
                KioskStatus.last_seen.desc()
            )

            .all()

        )

        now = datetime.utcnow()

        for kiosk in kiosk_records:

            # -----------------------------------------
            # ONLINE / OFFLINE
            # -----------------------------------------

            if kiosk.last_seen:

                seconds_since_seen = (
                    now - kiosk.last_seen
                ).total_seconds()

            else:

                seconds_since_seen = 999999


            if seconds_since_seen > 60:

                display_status = "offline"

            else:

                display_status = "online"


            # -----------------------------------------
            # SHOP NAME
            # -----------------------------------------

            shop_name = "Unknown Shop"

            if kiosk.user:

                shop_name = (
                    kiosk.user.name
                    or "Unnamed Shop"
                )


            # -----------------------------------------
            # CURRENTLY PLAYING
            #
            # PLAYLIST HAS PRIORITY.
            # -----------------------------------------

            currently_playing = None

            playing_type = None

            if kiosk.playlist:

                currently_playing = (
                    kiosk.playlist.name
                )

                playing_type = "playlist"

            elif kiosk.video:

                currently_playing = (
                    kiosk.video.title
                )

                playing_type = "video"


            # -----------------------------------------
            # ADD TO DASHBOARD LIST
            # -----------------------------------------

            kiosks.append({

                "user_id": kiosk.user_id,

                "shop_name": shop_name,

                "status": display_status,

                "currently_playing": (
                    currently_playing
                ),

                "playing_type": (
                    playing_type
                ),

                "last_seen": (
                    kiosk.last_seen
                )

            })


    # =================================================
    # RENDER DASHBOARD
    # =================================================

    return render_template(

        "dashboard.html",

        user=current_user,

        total_videos=total_videos,

        total_users=total_users,

        recent_videos=recent_videos,

        activities=activities,

        labels=labels,

        downloads_data=data,

        kiosks=kiosks

    )

# =====================================
# USER MANAGEMENT
#
# SUPERADMIN:
#   CREATE SUPERADMIN
#   CREATE ADMIN
#   CREATE SHOP
#   DELETE ADMIN
#   DELETE SHOP
#   CHANGE ANY PASSWORD
#
# ADMIN:
#   CANNOT CREATE USERS
#   CANNOT DELETE USERS
#   CAN CHANGE SHOP PASSWORDS
#
# SHOP:
#   NO USER MANAGEMENT
# =====================================

@app.route(
    "/create-user",
    methods=["GET", "POST"]
)
@login_required
def create_user():

    # =====================================
    # ALLOW SUPERADMIN AND ADMIN
    # =====================================

    if current_user.role not in ["admin", "superadmin"]:

        flash(
            "You do not have permission to access user management.",
            "danger"
        )

        return redirect(
            url_for("dashboard")
        )

    # =====================================
    # POST = CREATE USER
    #
    # ONLY SUPERADMIN CAN CREATE USERS
    # =====================================

    if request.method == "POST":

        if not is_superadmin():

            flash(
                "Only the SuperAdmin can create users.",
                "danger"
            )

            return redirect(
                url_for("create_user")
            )

        # =====================================
        # FORM DATA
        # =====================================

        name = request.form.get(
            "name",
            ""
        ).strip()

        username = request.form.get(
            "username",
            ""
        ).strip()

        password = request.form.get(
            "password",
            ""
        )

        role = request.form.get(
            "role",
            "shop"
        ).strip().lower()

        # =====================================
        # VALIDATION
        # =====================================

        if (
            not name
            or not username
            or not password
        ):

            flash(
                "Please complete all required fields.",
                "danger"
            )

            return redirect(
                url_for("create_user")
            )

        # =====================================
        # VALID ROLES
        # =====================================

        allowed_roles = {
            "superadmin",
            "admin",
            "shop"
        }

        if role not in allowed_roles:

            flash(
                "Invalid user role.",
                "danger"
            )

            return redirect(
                url_for("create_user")
            )

        # =====================================
        # CHECK DUPLICATE USERNAME
        # =====================================

        existing_user = (
            User.query
            .filter_by(
                username=username
            )
            .first()
        )

        if existing_user:

            flash(
                "Username already exists.",
                "danger"
            )

            return redirect(
                url_for("create_user")
            )

        # =====================================
        # CREATE USER
        # =====================================

        user = User(
            name=name,
            username=username,
            role=role
        )

        user.set_password(
            password
        )

        db.session.add(user)

        # =====================================
        # ACTIVITY LOG
        # =====================================

        activity = Activity(
            message=(
                f"{current_user.username} "
                f"created user '{username}' "
                f"with role '{role}'"
            )
        )

        db.session.add(activity)

        db.session.commit()

        flash(
            f"User '{username}' created successfully.",
            "success"
        )

        return redirect(
            url_for("create_user")
        )

    # =====================================
    # GET = LOAD USERS
    # =====================================

    if is_superadmin():

        # =====================================
        # SUPERADMIN SEES EVERYONE
        # =====================================

        users = (
            User.query
            .order_by(
                User.id.desc()
            )
            .all()
        )

    else:

        # =====================================
        # ADMIN SEES ONLY SHOP USERS
        # =====================================

        users = (
            User.query
            .filter_by(
                role="shop"
            )
            .order_by(
                User.id.desc()
            )
            .all()
        )

    # =====================================
    # PAGE
    # =====================================

    return render_template(
        "create_user.html",
        users=users,
        superadmin=is_superadmin()
    )
# =====================================
# DELETE USER
#
# SUPERADMIN ONLY
# =====================================

@app.route(
    "/delete-user/<int:user_id>",
    methods=["POST"]
)
@login_required
def delete_user(user_id):

    # =====================================
    # SUPERADMIN ONLY
    # =====================================

    if not is_superadmin():

        flash(
            "Only SuperAdmins can delete users.",
            "danger"
        )

        return redirect(
            url_for("create_user")
        )


    user = User.query.get_or_404(
        user_id
    )


    # =====================================
    # PREVENT SELF DELETION
    # =====================================

    if user.id == current_user.id:

        flash(
            "You cannot delete your own account.",
            "danger"
        )

        return redirect(
            url_for("create_user")
        )


    # =====================================
    # PROTECT PRIMARY SUPERADMIN
    #
    # PRIMARY ACCOUNT IS IDENTIFIED BY
    # BOTH USERNAME AND ROLE
    # =====================================

    if (
        user.username == "superadmin"
        and user.role == "superadmin"
    ):

        flash(
            "The primary SuperAdmin account cannot be deleted.",
            "danger"
        )

        return redirect(
            url_for("create_user")
        )


    username = user.username


    # =====================================
    # DELETE USER
    # =====================================

    db.session.delete(user)


    # =====================================
    # ACTIVITY LOG
    # =====================================

    activity = Activity(
        message=(
            f"{current_user.username} "
            f"deleted user '{username}'"
        )
    )

    db.session.add(activity)


    db.session.commit()


    flash(
        f"User '{username}' deleted successfully.",
        "success"
    )


    return redirect(
        url_for("create_user")
    )
# =====================================
# RESET USER PASSWORD
#
# SUPERADMIN:
#   CAN RESET EVERYONE
#
# ADMIN:
#   CAN RESET SHOP USERS ONLY
#
# SHOP:
#   CANNOT RESET OTHER USERS
# =====================================

@app.route(
    "/reset-user-password/<int:user_id>",
    methods=["GET", "POST"]
)
@login_required
def reset_user_password(user_id):

    # =====================================
    # ADMIN OR SUPERADMIN
    # =====================================

    if current_user.role not in ["admin", "superadmin"]:

        flash(
            "You do not have permission to reset passwords.",
            "danger"
        )

        return redirect(
            url_for("dashboard")
        )

    user = User.query.get_or_404(
        user_id
    )

    # =====================================
    # ADMIN CAN ONLY RESET SHOP PASSWORDS
    # =====================================

    if not is_superadmin():

        if user.role != "shop":

            flash(
                "Administrators can only reset Shop user passwords.",
                "danger"
            )

            return redirect(
                url_for("create_user")
            )

    # =====================================
    # ONLY SUPERADMIN CAN RESET
    # SUPERADMIN PASSWORD
    # =====================================

    if (
        user.role == "superadmin"
        and not is_superadmin()
    ):

        flash(
            "Only the SuperAdmin can reset a SuperAdmin password.",
            "danger"
        )

        return redirect(
            url_for("create_user")
        )

    # =====================================
    # POST
    # =====================================

    if request.method == "POST":

        new_password = request.form.get(
            "password",
            ""
        )

        confirm_password = request.form.get(
            "confirm_password",
            ""
        )

        # =====================================
        # CHECK FIELDS
        # =====================================

        if (
            not new_password
            or not confirm_password
        ):

            flash(
                "Please enter and confirm the new password.",
                "danger"
            )

            return redirect(
                url_for(
                    "reset_user_password",
                    user_id=user.id
                )
            )

        # =====================================
        # CHECK MATCH
        # =====================================

        if new_password != confirm_password:

            flash(
                "Passwords do not match.",
                "danger"
            )

            return redirect(
                url_for(
                    "reset_user_password",
                    user_id=user.id
                )
            )

        # =====================================
        # SET PASSWORD
        # =====================================

        user.set_password(
            new_password
        )

        # =====================================
        # ACTIVITY
        # =====================================

        activity = Activity(
            message=(
                f"{current_user.username} "
                f"changed the password for "
                f"'{user.username}'"
            )
        )

        db.session.add(activity)

        db.session.commit()

        flash(
            f"Password for {user.username} "
            f"has been changed successfully.",
            "success"
        )

        return redirect(
            url_for("create_user")
        )

    # =====================================
    # RESET PAGE
    # =====================================

    return render_template(
        "reset_user_password.html",
        user=user
    )


# =====================================
# FORGOT PASSWORD
#
# SUPERADMIN RECOVERY
# =====================================

@app.route(
    "/forgot-password",
    methods=["GET", "POST"]
)
def forgot_password():

    if request.method == "POST":

        email = request.form.get(
            "email",
            ""
        ).strip().lower()

        # =====================================
        # FIND SUPERADMIN BY ROLE
        #
        # NOT USERNAME
        # =====================================

        superadmin = (
            User.query
            .filter_by(
                role="superadmin"
            )
            .first()
        )

        # =====================================
        # NO SUPERADMIN
        # =====================================

        if not superadmin:

            flash(
                "Password recovery is currently unavailable.",
                "danger"
            )

            return redirect(
                url_for("forgot_password")
            )

        # =====================================
        # NO EMAIL
        # =====================================

        if not superadmin.email:

            flash(
                "No recovery email has been configured.",
                "danger"
            )

            return redirect(
                url_for("forgot_password")
            )

        # =====================================
        # CHECK EMAIL
        # =====================================

        if (
            not email
            or email != superadmin.email.lower()
        ):

            flash(
                "The recovery email is incorrect.",
                "danger"
            )

            return redirect(
                url_for("forgot_password")
            )

        # =====================================
        # REMOVE OLD TOKENS
        # =====================================

        PasswordResetToken.query.filter_by(
            user_id=superadmin.id
        ).delete()

        # =====================================
        # GENERATE SECURE TOKEN
        # =====================================

        reset_token = secrets.token_urlsafe(
            32
        )

        expires_at = (
            datetime.utcnow()
            + timedelta(
                minutes=30
            )
        )

        reset_request = PasswordResetToken(
            user_id=superadmin.id,
            token=reset_token,
            expires_at=expires_at
        )

        db.session.add(
            reset_request
        )

        db.session.commit()

        # =====================================
        # CREATE LINK
        # =====================================

        reset_link = url_for(
            "reset_password",
            token=reset_token,
            _external=True
        )

        # =====================================
        # SEND EMAIL
        # =====================================

        try:

            message = Message(
                subject=(
                    "Video Distribution Portal "
                    "- Password Reset"
                ),
                sender=app.config[
                    "MAIL_USERNAME"
                ],
                recipients=[
                    superadmin.email
                ]
            )

            message.body = f"""
Hello,

A password reset was requested for the
Video Distribution Portal SuperAdmin account.

Use the link below to create a new password:

{reset_link}

This password reset link will expire in
30 minutes.

If you did not request this password reset,
you can safely ignore this email.

Regards,

Video Distribution Portal
"""

            mail.send(message)

        except Exception as e:

            db.session.delete(
                reset_request
            )

            db.session.commit()

            print(
                "Password reset email failed:",
                e
            )

            flash(
                "The password reset email could not be sent.",
                "danger"
            )

            return redirect(
                url_for("forgot_password")
            )

        flash(
            "A password reset link has been sent to the SuperAdmin recovery email.",
            "success"
        )

        return redirect(
            url_for("login")
        )

    return render_template(
        "forgot_password.html"
    )


# =====================================
# RESET PASSWORD USING TOKEN
#
# SUPERADMIN ONLY
# =====================================

@app.route(
    "/reset-password/<token>",
    methods=["GET", "POST"]
)
def reset_password(token):

    # =====================================
    # FIND TOKEN
    # =====================================

    reset_request = (
        PasswordResetToken.query
        .filter_by(
            token=token
        )
        .first()
    )

    if not reset_request:


        return redirect(
            url_for("login")
        )

    # =====================================
    # CHECK EXPIRATION
    # =====================================

    if (
        reset_request.expires_at
        < datetime.utcnow()
    ):

        db.session.delete(
            reset_request
        )

        db.session.commit()

        flash(
            "This password reset link has expired.",
            "danger"
        )

        return redirect(
            url_for("forgot_password")
        )

    # =====================================
    # FIND USER
    # =====================================

    user = User.query.get(
        reset_request.user_id
    )

    if not user:

        db.session.delete(
            reset_request
        )

        db.session.commit()

        flash(
            "The account associated with this reset link no longer exists.",
            "danger"
        )

        return redirect(
            url_for("login")
        )

    # =====================================
    # VERIFY SUPERADMIN BY ROLE
    #
    # NOT USERNAME
    # =====================================

    if user.role != "superadmin":

        db.session.delete(
            reset_request
        )

        db.session.commit()

        flash(
            "This password reset link is not valid.",
            "danger"
        )

        return redirect(
            url_for("login")
        )

    # =====================================
    # POST
    # =====================================

    if request.method == "POST":

        new_password = request.form.get(
            "password",
            ""
        )

        confirm_password = request.form.get(
            "confirm_password",
            ""
        )

        # =====================================
        # CHECK FIELDS
        # =====================================

        if (
            not new_password
            or not confirm_password
        ):

            flash(
                "Please enter and confirm your new password.",
                "danger"
            )

            return redirect(
                url_for(
                    "reset_password",
                    token=token
                )
            )

        # =====================================
        # CHECK MATCH
        # =====================================

        if new_password != confirm_password:

            flash(
                "Passwords do not match.",
                "danger"
            )

            return redirect(
                url_for(
                    "reset_password",
                    token=token
                )
            )

        # =====================================
        # SET PASSWORD
        # =====================================

        user.set_password(
            new_password
        )

        # =====================================
        # DELETE TOKEN
        # =====================================

        db.session.delete(
            reset_request
        )

        db.session.commit()

        flash(
            "Your password has been reset successfully. You can now log in.",
            "success"
        )

        return redirect(
            url_for("login")
        )

    return render_template(
        "reset_password.html",
        token=token
    )


# =====================================
# SUPERADMIN RECOVERY EMAIL
# =====================================

@app.route(
    "/superadmin/recovery-email",
    methods=["GET", "POST"]
)
@login_required
def recovery_email():

    # =====================================
    # SUPERADMIN ONLY
    #
    # ROLE BASED
    # =====================================

    if not is_superadmin():

        flash(
            "Only the SuperAdmin can manage the recovery email.",
            "danger"
        )

        return redirect(
            url_for("dashboard")
        )

    # =====================================
    # POST
    # =====================================

    if request.method == "POST":

        email = request.form.get(
            "email",
            ""
        ).strip().lower()

        confirm_email = request.form.get(
            "confirm_email",
            ""
        ).strip().lower()

        # =====================================
        # CHECK EMAIL
        # =====================================

        if (
            not email
            or not confirm_email
        ):

            flash(
                "Please enter and confirm the recovery email.",
                "danger"
            )

            return redirect(
                url_for("recovery_email")
            )

        # =====================================
        # MATCH
        # =====================================

        if email != confirm_email:

            flash(
                "The email addresses do not match.",
                "danger"
            )

            return redirect(
                url_for("recovery_email")
            )

        # =====================================
        # CHECK EXISTING USER
        # =====================================

        existing_user = (
            User.query
            .filter(
                User.email == email,
                User.id != current_user.id
            )
            .first()
        )

        if existing_user:

            flash(
                "That email address is already associated with another account.",
                "danger"
            )

            return redirect(
                url_for("recovery_email")
            )

        # =====================================
        # SAVE
        # =====================================

        current_user.email = email

        db.session.commit()

        flash(
            "SuperAdmin recovery email updated successfully.",
            "success"
        )

        return redirect(
            url_for("recovery_email")
        )

    return render_template(
        "recovery_email.html"
    )


# =====================================
# VIDEO UPLOAD
#
# ADMIN + SUPERADMIN
# =====================================

@app.route(
    "/upload",
    methods=["GET", "POST"]
)
@login_required
def upload_video():

    # =====================================
    # ADMIN OR SUPERADMIN
    # =====================================

    if current_user.role not in ["admin", "superadmin"]:


        return redirect(
            url_for("videos")
        )

    # =====================================
    # POST
    # =====================================

    if request.method == "POST":

        # =====================================
        # GET ALL SELECTED FILES
        # =====================================

        files = request.files.getlist(
            "videos"
        )

        # =====================================
        # CHECK FILES
        # =====================================

        if not files:

            return render_template(
                "upload_result.html",
                success=False,
                message="No video files were selected.",
                uploaded_files=[],
                failed_files=[]
            )

        # =====================================
        # PREPARE RESULTS
        # =====================================

        uploaded_files = []
        failed_files = []

        # =====================================
        # PROCESS EACH VIDEO
        # =====================================

        for file in files:

            # =====================================
            # CHECK EMPTY FILE
            # =====================================

            if (
                not file
                or file.filename == ""
            ):

                failed_files.append(
                    "Unnamed file - no file was selected."
                )

                continue

            # =====================================
            # CHECK TYPE
            # =====================================

            if not allowed_file(
                file.filename
            ):

                failed_files.append(
                    f"{file.filename} - invalid video file type."
                )

                continue

            # =====================================
            # SECURE FILENAME
            # =====================================

            filename = secure_filename(
                file.filename
            )

            # =====================================
            # MAKE SURE FILENAME IS NOT EMPTY
            # =====================================

            if not filename:

                failed_files.append(
                    "Invalid filename."
                )

                continue

            # =====================================
            # INITIAL FILEPATH
            # =====================================

            filepath = os.path.join(
                app.config["UPLOAD_FOLDER"],
                filename
            )

            # =====================================
            # PREVENT OVERWRITE
            # =====================================

            if os.path.exists(filepath):

                name, extension = os.path.splitext(
                    filename
                )

                counter = 1

                while os.path.exists(filepath):

                    filename = (
                        f"{name}_{counter}{extension}"
                    )

                    filepath = os.path.join(
                        app.config["UPLOAD_FOLDER"],
                        filename
                    )

                    counter += 1

            # =====================================
            # SAVE FILE
            # =====================================

            try:

                file.save(
                    filepath
                )

            except Exception as e:

                print(
                    "VIDEO SAVE ERROR:",
                    e
                )

                failed_files.append(
                    f"{file.filename} - server could not save the video."
                )

                continue

            # =====================================
            # DATABASE
            # =====================================

            try:

                video = Video(
                    title=filename,
                    filename=filename,
                    uploaded_by=current_user.id
                )

                db.session.add(
                    video
                )

                # =====================================
                # ACTIVITY
                # =====================================

                activity = Activity(
                    message=(
                        f"{current_user.username} "
                        f"uploaded {filename}"
                    )
                )

                db.session.add(
                    activity
                )

                db.session.commit()

                # =====================================
                # SUCCESS
                # =====================================

                uploaded_files.append(
                    filename
                )

            except Exception as e:

                db.session.rollback()

                # =====================================
                # DELETE FILE IF DATABASE FAILED
                # =====================================

                if os.path.exists(filepath):

                    try:

                        os.remove(
                            filepath
                        )

                    except Exception as remove_error:

                        print(
                            "VIDEO CLEANUP ERROR:",
                            remove_error
                        )

                print(
                    "VIDEO DATABASE ERROR:",
                    e
                )

                failed_files.append(
                    f"{filename} - could not be registered in the database."
                )

                continue

        # =====================================
        # BUILD RESULT MESSAGE
        # =====================================

        message_parts = []

        # =====================================
        # SUCCESS MESSAGE
        # =====================================

        if uploaded_files:

            message_parts.append(
                f"{len(uploaded_files)} "
                f"video"
                f"{'s' if len(uploaded_files) != 1 else ''} "
                f"uploaded successfully."
            )

        # =====================================
        # FAILURE MESSAGE
        # =====================================

        if failed_files:

            message_parts.append(
                f"{len(failed_files)} "
                f"video"
                f"{'s' if len(failed_files) != 1 else ''} "
                f"failed."
            )

        # =====================================
        # FINAL RESULT
        # =====================================

        success = (
            len(uploaded_files) > 0
        )

        return render_template(
            "upload_result.html",
            success=success,
            message=" ".join(message_parts),
            uploaded_files=uploaded_files,
            failed_files=failed_files
        )

    # =====================================
    # GET
    # =====================================

    recent_videos = (
        Video.query
        .order_by(
            Video.upload_date.desc()
        )
        .limit(10)
        .all()
    )

    return render_template(
        "upload.html",
        recent_videos=recent_videos
    )

# =====================================
# VIDEO LIST
# =====================================

@app.route("/videos")
@login_required
def videos():

    videos = (
        Video.query
        .order_by(
            Video.upload_date.desc()
        )
        .all()
    )

    return render_template(
        "videos.html",
        videos=videos
    )


# =====================================
# DOWNLOAD / STREAM VIDEO
# =====================================

@app.route(
    "/download/<int:video_id>"
)
@login_required
def download_video(video_id):

    video = Video.query.get_or_404(
        video_id
    )

    response = send_from_directory(
        app.config["UPLOAD_FOLDER"],
        video.filename,
        as_attachment=False
    )

    response.headers[
        "Cache-Control"
    ] = "public, max-age=86400"

    return response


@app.route(
    "/stream/<int:video_id>"
)
@login_required
def stream_video(video_id):

    # =====================================
    # GET VIDEO
    # =====================================

    video = Video.query.get_or_404(
        video_id
    )

    # =====================================
    # ENSURE CENTRAL CACHE EXISTS
    # =====================================

    try:

        cache_path = ensure_video_cached(
            video
        )

    except FileNotFoundError as e:

        print(
            "PLAYBACK CACHE ERROR:",
            e
        )

        return (
            "Video file not found.",
            404
        )

    except Exception as e:

        print(
            "PLAYBACK CACHE ERROR:",
            e
        )

        return (
            "Unable to prepare video for playback.",
            500
        )

    # =====================================
    # SERVE CENTRAL CACHE
    #
    # conditional=True allows:
    #
    # HTTP Range
    # seeking
    # pause/resume
    # browser video buffering
    # =====================================

    response = send_file(

        cache_path,

        mimetype=get_video_mimetype(
            video.filename
        ),

        conditional=True,

        etag=True,

        max_age=86400

    )

    # =====================================
    # RESPONSE CACHE
    # =====================================

    response.headers[
        "Cache-Control"
    ] = "public, max-age=86400"

    return response

# =====================================
# DELETE VIDEO
#
# ADMIN + SUPERADMIN
# =====================================

@app.route(
    "/delete-video/<int:video_id>",
    methods=["POST"]
)
@login_required
def delete_video(video_id):

    # =====================================
    # ADMIN / SUPERADMIN ONLY
    # =====================================

    if current_user.role not in ["admin", "superadmin"]:

        flash(
            "Only administrators can delete videos.",
            "danger"
        )

        return redirect(
            url_for("videos")
        )

    # =====================================
    # GET VIDEO
    # =====================================

    video = Video.query.get_or_404(
        video_id
    )

    filename = video.filename

    # =====================================
    # SOURCE FILE
    # =====================================

    file_path = os.path.join(
        app.config["UPLOAD_FOLDER"],
        filename
    )

    # =====================================
    # CENTRAL CACHE
    # =====================================

    cache_path = get_video_cache_path(
        video
    )

    lock_path = get_video_lock_path(
        video
    )

    # =====================================
    # DELETE SOURCE FILE
    # =====================================

    try:

        if os.path.exists(file_path):

            os.remove(file_path)

    except OSError as e:

        print(
            "VIDEO FILE DELETE ERROR:",
            e
        )

        flash(
            "The video could not be deleted.",
            "danger"
        )

        return redirect(
            url_for("videos")
        )

    # =====================================
    # DELETE CENTRAL CACHE
    # =====================================

    try:

        if os.path.exists(cache_path):

            os.remove(cache_path)

    except OSError as e:

        print(
            "VIDEO CACHE DELETE ERROR:",
            e
        )

    # =====================================
    # ACTIVITY
    # =====================================

    activity = Activity(
        message=(
            f"{current_user.username} "
            f"deleted {filename}"
        )
    )

    db.session.add(
        activity
    )

    # =====================================
    # DATABASE
    # =====================================

    try:

        db.session.delete(
            video
        )

        db.session.commit()

    except Exception as e:

        db.session.rollback()

        print(
            "VIDEO DATABASE DELETE ERROR:",
            e
        )

        flash(
            "The video could not be deleted.",
            "danger"
        )

        return redirect(
            url_for("videos")
        )

    # =====================================
    # CLEAN LOCK FILE
    # =====================================

    try:

        if os.path.exists(lock_path):

            os.remove(lock_path)

    except OSError as e:

        print(
            "VIDEO LOCK DELETE ERROR:",
            e
        )

    # =====================================
    # SUCCESS
    # =====================================

    flash(
        "Video deleted successfully.",
        "success"
    )

    return redirect(
        url_for("videos")
    )


# =====================================================
# KIOSK PLAYER
#
# ALL LOGGED-IN USERS MAY TEST THE KIOSK
#
# IMPORTANT:
# Admins and Superadmins may use the kiosk for testing,
# but they are NOT registered as kiosk devices.
# Only users with role="shop" report kiosk status.
# =====================================================

@app.route("/kiosk")
@login_required
def kiosk():

    video_id = request.args.get("video_id", type=int)

    playlist_id = request.args.get("playlist_id", type=int)

    # =================================================
    # ALL VIDEOS
    # =================================================

    videos = (
        Video.query
        .order_by(
            Video.upload_date.desc()
        )
        .all()
    )

    # =================================================
    # DEFAULT VALUES
    # =================================================

    selected_video = None

    selected_video_id = None

    selected_playlist_id = None

    playlist_videos = []

    playlist = None

    # =================================================
    # SINGLE VIDEO
    # =================================================

    if video_id:

        selected_video = (
            Video.query
            .filter_by(
                id=video_id
            )
            .first()
        )

        if selected_video:

            selected_video_id = selected_video.id

    # =================================================
    # PLAYLIST
    # =================================================

    if playlist_id:

        playlist = (
            Playlist.query
            .filter_by(
                id=playlist_id
            )
            .first()
        )

        if playlist:

            selected_playlist_id = playlist.id

            # -----------------------------------------
            # Get playlist videos
            # -----------------------------------------

            for item in playlist.videos:

                if not item.video:
                    continue

                playlist_videos.append({

                    "id": item.video.id,

                    "title": item.video.title,

                    "filename": item.video.filename,

                    "url": url_for(
                        "stream_video",
                        video_id=item.video.id
                    ),

                    "position": item.position

                })

            # -----------------------------------------
            # Make sure playlist order is respected
            # -----------------------------------------

            playlist_videos.sort(
                key=lambda x: x["position"]
            )

    # =================================================
    # RENDER
    # =================================================

    return render_template(
        "kiosk.html",

        videos=videos,

        selected_video=selected_video,

        selected_video_id=selected_video_id,

        selected_playlist_id=selected_playlist_id,

        playlist=playlist,

        playlist_videos=playlist_videos
    )

# =====================================
# KIOSK HEARTBEAT
# =====================================

@app.route(
    "/kiosk/heartbeat",
    methods=["POST"]
)
@login_required
def kiosk_heartbeat():

    kiosk_status = (
        KioskStatus.query
        .filter_by(
            user_id=current_user.id
        )
        .first()
    )

    if not kiosk_status:

        kiosk_status = KioskStatus(
            user_id=current_user.id,
            status="online",
            last_seen=datetime.utcnow()
        )

        db.session.add(
            kiosk_status
        )

    else:

        kiosk_status.status = "online"

        kiosk_status.last_seen = (
            datetime.utcnow()
        )

    db.session.commit()

    return {
        "success": True,
        "status": "online"
    }


# =====================================================
# KIOSK STATUS UPDATE
#
# POST /kiosk/status
#
# ONLY SHOP USERS ARE ALLOWED TO CREATE/UPDATE
# KIOSK STATUS RECORDS.
#
# Admins and Superadmins can still use /kiosk,
# but their testing activity is completely ignored
# by the kiosk monitoring system.
# =====================================================

@app.route(
    "/kiosk/status",
    methods=["POST"]
)
@login_required
def kiosk_status_update():

    # =================================================
    # SECURITY
    #
    # ONLY SHOP USERS CAN REPORT KIOSK STATUS
    # =================================================

    if current_user.role != "shop":

        return {
            "success": False,
            "message": "Only shop kiosks can report kiosk status."
        }, 403

    # =================================================
    # READ JSON
    # =================================================

    data = (
        request.get_json(
            silent=True
        )
        or {}
    )

    status = data.get(
        "status",
        "online"
    )

    video_id = data.get(
        "video_id"
    )

    playlist_id = data.get(
        "playlist_id"
    )

    # =================================================
    # VALIDATE STATUS
    # =================================================

    if status not in [
        "online",
        "offline"
    ]:

        status = "online"

    # =================================================
    # VALIDATE VIDEO
    # =================================================

    if video_id:

        try:

            video_id = int(video_id)

        except (
            TypeError,
            ValueError
        ):

            video_id = None

        if (
            video_id
            and not Video.query.get(video_id)
        ):

            video_id = None

    # =================================================
    # VALIDATE PLAYLIST
    # =================================================

    if playlist_id:

        try:

            playlist_id = int(playlist_id)

        except (
            TypeError,
            ValueError
        ):

            playlist_id = None

        if (
            playlist_id
            and not Playlist.query.get(playlist_id)
        ):

            playlist_id = None

    # =================================================
    # FIND THIS SHOP'S KIOSK RECORD
    # =================================================

    kiosk = (
        KioskStatus.query
        .filter_by(
            user_id=current_user.id
        )
        .first()
    )

    # =================================================
    # CREATE NEW SHOP KIOSK RECORD
    # =================================================

    if not kiosk:

        kiosk = KioskStatus(

            user_id=current_user.id,

            status=status,

            video_id=video_id,

            playlist_id=playlist_id,

            last_seen=datetime.utcnow()

        )

        db.session.add(kiosk)

    # =================================================
    # UPDATE EXISTING SHOP KIOSK
    # =================================================

    else:

        kiosk.status = status

        kiosk.video_id = video_id

        kiosk.playlist_id = playlist_id

        kiosk.last_seen = datetime.utcnow()

        kiosk.updated_at = datetime.utcnow()

    # =================================================
    # SAVE
    # =================================================

    db.session.commit()

    return {

        "success": True,

        "status": kiosk.status,

        "last_seen": (
            kiosk.last_seen.isoformat()
            if kiosk.last_seen
            else None
        )

    }
    

# =====================================================
# KIOSK STATUS - ADMIN DASHBOARD VIEW
#
# GET /kiosk/status
#
# ONLY ADMIN + SUPERADMIN CAN VIEW THIS.
#
# ONLY SHOP USERS ARE INCLUDED.
# =====================================================

@app.route(
    "/kiosk/status",
    methods=["GET"]
)
@login_required
def get_kiosk_status():

    # =================================================
    # SECURITY
    #
    # ONLY ADMIN + SUPERADMIN
    # =================================================

    if current_user.role not in [
        "admin",
        "superadmin"
    ]:

        return {
            "success": False,
            "message": "Not authorized"
        }, 403

    # =================================================
    # GET ONLY SHOP KIOSKS
    #
    # This is important.
    #
    # Even if old admin/superadmin records exist
    # in the KioskStatus table, they will NEVER
    # appear here.
    # =================================================

    kiosks = (
        KioskStatus.query
        .join(
            User,
            KioskStatus.user_id == User.id
        )
        .filter(
            User.role == "shop"
        )
        .order_by(
            KioskStatus.last_seen.desc()
        )
        .all()
    )

    result = []

    # =================================================
    # CURRENT TIME
    # =================================================

    now = datetime.utcnow()

    # =================================================
    # PROCESS KIOSKS
    # =================================================

    for kiosk in kiosks:

        # ---------------------------------------------
        # DETERMINE ONLINE/OFFLINE
        # ---------------------------------------------

        if kiosk.last_seen:

            seconds_since_seen = (
                now - kiosk.last_seen
            ).total_seconds()

        else:

            seconds_since_seen = 999999


        if seconds_since_seen > 60:

            display_status = "offline"

        else:

            display_status = "online"


        # =================================================
        # SHOP NAME
        #
        # We use the shop user's name.
        # NOT username.
        # =================================================

        shop_name = "Unknown Shop"

        if kiosk.user:

            shop_name = (
                kiosk.user.name
                or "Unnamed Shop"
            )


        # =================================================
        # CURRENTLY PLAYING
        #
        # IMPORTANT:
        #
        # Playlist takes priority.
        #
        # If playlist_id exists:
        #     show playlist name
        #
        # Otherwise if video_id exists:
        #     show video name
        #
        # This prevents a playlist from being displayed
        # as the individual video currently playing.
        # =================================================

        currently_playing = None

        playing_type = None

        # ---------------------------------------------
        # PLAYLIST
        # ---------------------------------------------

        if kiosk.playlist:

            currently_playing = (
                kiosk.playlist.name
            )

            playing_type = "playlist"

        # ---------------------------------------------
        # SINGLE VIDEO
        # ---------------------------------------------

        elif kiosk.video:

            currently_playing = (
                kiosk.video.title
            )

            playing_type = "video"


        # =================================================
        # RESULT
        # =================================================

        result.append({

            "user_id": kiosk.user_id,

            "shop_name": shop_name,

            "status": display_status,

            "currently_playing": currently_playing,

            "playing_type": playing_type,

            "last_seen": (
                kiosk.last_seen.isoformat()
                if kiosk.last_seen
                else None
            )

        })


    # =================================================
    # RETURN
    # =================================================

    return {

        "success": True,

        "kiosks": result

    }
# =====================================
# PLAY PLAYLIST IN KIOSK
# =====================================

@app.route(
    "/kiosk/playlist/<int:playlist_id>"
)
@login_required
def kiosk_playlist(playlist_id):

    playlist = Playlist.query.get_or_404(
        playlist_id
    )

    playlist_videos = (
        PlaylistVideo.query
        .filter_by(
            playlist_id=playlist.id
        )
        .order_by(
            PlaylistVideo.position.asc()
        )
        .all()
    )

    videos = []

    for item in playlist_videos:

        if item.video:

            videos.append({
                "id": item.video.id,
                "title": item.video.title,
                "url": url_for(
                    "stream_video",
                    video_id=item.video.id
                )
            })

    return render_template(
        "kiosk.html",
        playlist=playlist,
        playlist_videos=videos
    )


# =====================================
# PLAY VIDEO
# =====================================

@app.route(
    "/video/<int:video_id>"
)
@login_required
def play_video(video_id):

    video = Video.query.get_or_404(
        video_id
    )

    return render_template(
        "play_video.html",
        video=video
    )


# =====================================
# PLAYLISTS
# =====================================

@app.route("/playlists")
@login_required
def playlists():

    playlists = (
        Playlist.query
        .order_by(
            Playlist.created_at.desc()
        )
        .all()
    )

    return render_template(
        "playlists.html",
        playlists=playlists
    )


# =====================================
# CREATE PLAYLIST
#
# ADMIN + SUPERADMIN
# =====================================

@app.route(
    "/playlists/create",
    methods=["GET", "POST"]
)
@login_required
def create_playlist():

    # =====================================
    # ADMIN + SUPERADMIN ONLY
    # =====================================

    if current_user.role not in ["admin", "superadmin"]:


        return redirect(
            url_for("playlists")
        )

    # =====================================
    # AVAILABLE VIDEOS
    # =====================================

    videos = (
        Video.query
        .order_by(
            Video.upload_date.desc()
        )
        .all()
    )

    # =====================================
    # POST
    # =====================================

    if request.method == "POST":

        name = request.form.get(
            "name",
            ""
        ).strip()

        selected_video_ids = (
            request.form.getlist(
                "video_ids"
            )
        )

        # =====================================
        # NAME VALIDATION
        # =====================================

        if not name:

            flash(
                "Please enter a playlist name.",
                "danger"
            )

            return render_template(
                "create_playlist.html",
                videos=videos
            )

        # =====================================
        # VIDEO VALIDATION
        # =====================================

        if not selected_video_ids:

            flash(
                "Please select at least one video.",
                "danger"
            )

            return render_template(
                "create_playlist.html",
                videos=videos
            )

        # =====================================
        # CREATE PLAYLIST
        # =====================================

        playlist = Playlist(
            name=name,
            created_by=current_user.id
        )

        db.session.add(playlist)

        db.session.flush()

        # =====================================
        # ADD VIDEOS
        # =====================================

        position = 1

        for video_id in selected_video_ids:

            try:

                video_id = int(video_id)

            except (
                TypeError,
                ValueError
            ):

                continue

            video = Video.query.get(video_id)

            if not video:
                continue

            playlist_video = PlaylistVideo(
                playlist_id=playlist.id,
                video_id=video.id,
                position=position
            )

            db.session.add(playlist_video)

            position += 1

        # =====================================
        # ACTIVITY
        # =====================================

        activity = Activity(
            message=(
                f"{current_user.username} "
                f"created playlist '{name}'"
            )
        )

        db.session.add(activity)

        # =====================================
        # SAVE
        # =====================================

        db.session.commit()

        flash(
            "Playlist created successfully.",
            "success"
        )

        return redirect(
            url_for("playlists")
        )

    # =====================================
    # GET
    # =====================================

    return render_template(
        "create_playlist.html",
        videos=videos
    )
    
# =========================
# EDIT PLAYLIST
# =========================
@app.route(
    "/playlists/<int:playlist_id>/edit",
    methods=["GET", "POST"]
)
@login_required
def edit_playlist(playlist_id):

    # =====================================
    # ADMIN + SUPERADMIN ONLY
    # =====================================

    if current_user.role not in ["admin", "superadmin"]:

        flash(
            "Only administrators can edit playlists.",
            "danger"
        )

        return redirect(
            url_for("playlists")
        )

    # =====================================
    # GET PLAYLIST
    # =====================================

    playlist = Playlist.query.get_or_404(
        playlist_id
    )

    # =====================================
    # AVAILABLE VIDEOS
    # =====================================

    videos = (
        Video.query
        .order_by(
            Video.upload_date.desc()
        )
        .all()
    )

    # =====================================
    # POST
    # =====================================

    if request.method == "POST":

        name = request.form.get(
            "name",
            ""
        ).strip()

        selected_video_ids = (
            request.form.getlist(
                "video_ids"
            )
        )

        # =====================================
        # NAME VALIDATION
        # =====================================

        if not name:

            flash(
                "Please enter a playlist name.",
                "danger"
            )

            return render_template(
                "edit_playlist.html",
                playlist=playlist,
                videos=videos
            )

        # =====================================
        # VIDEO VALIDATION
        # =====================================

        if not selected_video_ids:

            flash(
                "Please select at least one video.",
                "danger"
            )

            return render_template(
                "edit_playlist.html",
                playlist=playlist,
                videos=videos
            )

        # =====================================
        # REMOVE OLD PLAYLIST VIDEOS
        # =====================================

        PlaylistVideo.query.filter_by(
            playlist_id=playlist.id
        ).delete(
            synchronize_session=False
        )

        # =====================================
        # UPDATE PLAYLIST NAME
        # =====================================

        playlist.name = name

        # =====================================
        # ADD VIDEOS IN NEW ORDER
        # =====================================

        position = 1

        for video_id in selected_video_ids:

            try:

                video_id = int(video_id)

            except (
                TypeError,
                ValueError
            ):

                continue

            video = Video.query.get(
                video_id
            )

            if not video:
                continue

            playlist_video = PlaylistVideo(
                playlist_id=playlist.id,
                video_id=video.id,
                position=position
            )

            db.session.add(
                playlist_video
            )

            position += 1

        # =====================================
        # ACTIVITY LOG
        # =====================================

        activity = Activity(
            message=(
                f"{current_user.username} "
                f"edited playlist '{playlist.name}'"
            )
        )

        db.session.add(activity)

        # =====================================
        # SAVE
        # =====================================

        db.session.commit()

        flash(
            "Playlist updated successfully.",
            "success"
        )

        return redirect(
            url_for("playlists")
        )

    # =====================================
    # GET
    # =====================================

    return render_template(
        "edit_playlist.html",
        playlist=playlist,
        videos=videos
    )

# =====================================
# DELETE PLAYLIST
#
# ADMIN + SUPERADMIN
# =====================================

@app.route(
    "/playlists/delete/<int:playlist_id>",
    methods=["POST"]
)
@login_required
def delete_playlist(playlist_id):

    # =====================================
    # ADMIN + SUPERADMIN
    # =====================================

    if current_user.role not in ["admin", "superadmin"]:

        flash(
            "Only administrators can delete playlists.",
            "danger"
        )

        return redirect(
            url_for("playlists")
        )

    playlist = Playlist.query.get_or_404(
        playlist_id
    )

    playlist_name = playlist.name

    try:

        # =====================================
        # DISCONNECT KIOSKS
        # =====================================

        kiosk_statuses = (
            KioskStatus.query
            .filter_by(
                playlist_id=playlist.id
            )
            .all()
        )

        for kiosk in kiosk_statuses:

            kiosk.playlist_id = None

            kiosk.video_id = None

            kiosk.status = "offline"

            kiosk.updated_at = (
                datetime.utcnow()
            )

        # =====================================
        # DELETE PLAYLIST
        # =====================================

        db.session.delete(
            playlist
        )

        # =====================================
        # ACTIVITY
        # =====================================

        activity = Activity(
            message=(
                f"{current_user.username} "
                f"deleted playlist "
                f"'{playlist_name}'"
            )
        )

        db.session.add(
            activity
        )

        db.session.commit()

        flash(
            "Playlist deleted successfully.",
            "success"
        )

    except Exception as e:

        db.session.rollback()

        print(
            "PLAYLIST DELETE ERROR:",
            e
        )

        flash(
            "The playlist could not be deleted.",
            "danger"
        )

    return redirect(
        url_for("playlists")
    )


# =====================================
# INITIALIZE DATABASE
# =====================================

@app.before_request
def init_once():

    if not hasattr(
        app,
        "db_initialized"
    ):

        with app.app_context():

            seed_superadmin()

        app.db_initialized = True


# =====================================
# SECURITY HEADERS / CACHE
# =====================================

@app.after_request
def add_header(response):

    # Do not override caching headers for video streaming.
    if request.endpoint == "stream_video":
        return response

    response.headers["Cache-Control"] = (
        "no-store, "
        "no-cache, "
        "must-revalidate, "
        "max-age=0"
    )

    response.headers["Pragma"] = "no-cache"
    response.headers["Expires"] = "0"

    return response


# =====================================
# RUN
# =====================================

if __name__ == "__main__":

    app.run(
        debug=True
    )