"""Dateifreigaben: Blaettern, Herunterladen, Hochladen, Verwalten."""

from __future__ import annotations

import io

from flask import (
    Blueprint,
    Response,
    abort,
    flash,
    redirect,
    render_template,
    request,
    send_file,
    url_for,
)
from tesm_core import audit
from tesm_core.auth.models import current_user, permission_required
from tesm_core.auth.service import client_ip
from tesm_core.extension import core, db, transaction

from ..services import fileshare as service

bp = Blueprint("fileshare", __name__, url_prefix="/dateifreigaben")


@bp.before_request
def _require_module() -> None:
    manager = core().license
    if manager is not None and not manager.module_active("fileshare"):
        abort(404)


def _audit(action: str, target: str, detail: str = "", severity: str = "info") -> None:
    with transaction() as conn:
        audit.log(
            conn,
            actor=current_user.username,
            actor_id=current_user.id,
            action=action,
            target=target,
            detail=detail,
            ip=client_ip(),
            severity=severity,
        )


def _mount_or_404(token: str) -> service.Mount:
    mounts = service.active_mounts(db(), current_user.id)
    for mount in mounts:
        if mount.token == token:
            return mount
    abort(404)


@bp.get("/")
@permission_required("fileshare.view")
def browse():
    conn = db()
    mounts = service.active_mounts(conn, current_user.id)
    token = request.args.get("freigabe") or (mounts[0].token if mounts else "")
    relative = request.args.get("pfad") or ""
    listing = None
    error = ""
    if token:
        try:
            mount = _mount_or_404(token)
            listing = service.list_directory(mount, relative)
        except service.FileshareError as exc:
            error = str(exc)
    return render_template(
        "tesm/fileshare.html",
        mounts=mounts,
        token=token,
        listing=listing,
        error=error,
        max_upload_mb=int(core().settings.get(conn, "fileshare_upload_max_mb") or 512),
    )


@bp.get("/<token>/herunterladen")
@permission_required("fileshare.view")
def download(token: str):
    mount = _mount_or_404(token)
    relative = request.args.get("pfad") or ""
    try:
        path = service.resolve_path(mount, relative)
    except service.FileshareError as exc:
        abort(400, description=str(exc))
    if not path.is_file():
        abort(404)
    _audit("fileshare.downloaded", f"{mount.label}:{relative}")
    return send_file(
        path,
        as_attachment=True,
        download_name=path.name,
        mimetype=service.download_mime(path.name),
    )


@bp.get("/<token>/vorschau")
@permission_required("fileshare.view")
def preview(token: str):
    mount = _mount_or_404(token)
    relative = request.args.get("pfad") or ""
    try:
        path = service.resolve_path(mount, relative)
        mimetype = service.preview_mime(path.name)
    except service.FileshareError as exc:
        abort(415, description=str(exc))
    if not path.is_file():
        abort(404)
    # ``Content-Disposition: inline`` mit engem MIME-Typ; die CSP der Anwendung
    # verhindert zusaetzlich, dass eingebettetes Skript ausgefuehrt wuerde.
    return Response(
        path.read_bytes(),
        mimetype=mimetype,
        headers={
            "Content-Disposition": f'inline; filename="{path.name}"',
            "X-Content-Type-Options": "nosniff",
        },
    )


@bp.post("/<token>/hochladen")
@permission_required("fileshare.create")
def upload(token: str):
    mount = _mount_or_404(token)
    relative = request.form.get("pfad") or ""
    files = request.files.getlist("files")
    if not files:
        flash("Keine Datei ausgewaehlt.", "danger")
        return redirect(url_for("fileshare.browse", freigabe=token, pfad=relative))

    limit = int(core().settings.get(db(), "fileshare_upload_max_mb") or 512) * 1024 * 1024
    stored = 0
    try:
        target_dir = service.resolve_path(mount, relative)
        for upload_file in files:
            if not upload_file.filename:
                continue
            name = service.safe_filename(upload_file.filename)
            data = upload_file.stream.read(limit + 1)
            if len(data) > limit:
                flash(f"{name}: zu gross (Grenze {limit // 1024 // 1024} MB).", "danger")
                continue
            (target_dir / name).write_bytes(data)
            stored += 1
    except service.FileshareError as exc:
        flash(str(exc), "danger")
        return redirect(url_for("fileshare.browse", freigabe=token, pfad=relative))

    if stored:
        _audit("fileshare.uploaded", f"{mount.label}:{relative}", f"{stored} Datei(en)")
        flash(f"{stored} Datei(en) hochgeladen.", "success")
    return redirect(url_for("fileshare.browse", freigabe=token, pfad=relative))


@bp.post("/<token>/ordner")
@permission_required("fileshare.create")
def make_directory(token: str):
    mount = _mount_or_404(token)
    relative = request.form.get("pfad") or ""
    try:
        service.make_directory(mount, relative, request.form.get("name", ""))
    except (service.FileshareError, OSError) as exc:
        flash(str(exc), "danger")
        return redirect(url_for("fileshare.browse", freigabe=token, pfad=relative))
    _audit("fileshare.directory_created", f"{mount.label}:{relative}/{request.form.get('name')}")
    flash("Ordner angelegt.", "success")
    return redirect(url_for("fileshare.browse", freigabe=token, pfad=relative))


@bp.post("/<token>/umbenennen")
@permission_required("fileshare.edit")
def rename(token: str):
    mount = _mount_or_404(token)
    relative = request.form.get("pfad") or ""
    parent = request.form.get("parent") or ""
    try:
        service.rename_entry(mount, relative, request.form.get("name", ""))
    except (service.FileshareError, OSError) as exc:
        flash(str(exc), "danger")
        return redirect(url_for("fileshare.browse", freigabe=token, pfad=parent))
    _audit("fileshare.renamed", f"{mount.label}:{relative}")
    flash("Umbenannt.", "success")
    return redirect(url_for("fileshare.browse", freigabe=token, pfad=parent))


@bp.post("/<token>/loeschen")
@permission_required("fileshare.delete")
def delete(token: str):
    mount = _mount_or_404(token)
    parent = request.form.get("parent") or ""
    removed = 0
    for relative in request.form.getlist("pfade"):
        try:
            kind = service.delete_entry(mount, relative)
            _audit("fileshare.deleted", f"{mount.label}:{relative}", kind, severity="warning")
            removed += 1
        except (service.FileshareError, OSError) as exc:
            flash(str(exc), "danger")
    if removed:
        flash(f"{removed} Eintrag/Eintraege geloescht.", "success")
    return redirect(url_for("fileshare.browse", freigabe=token, pfad=parent))


@bp.post("/<token>/freigeben")
@permission_required("fileshare.view")
def release(token: str):
    mount = _mount_or_404(token)
    with transaction() as conn:
        service.release_mount(conn, mount.id)
    _audit("fileshare.released", mount.label)
    flash("Einbindung geloest.", "success")
    return redirect(url_for("fileshare.browse"))


# --------------------------------------------------------------------------- #
# Verwaltung der Zuordnungen (Einstellungen)
# --------------------------------------------------------------------------- #


@bp.get("/zuordnungen")
@permission_required("fileshare.edit")
def mappings():
    from tesm_core.auth import directory

    conn = db()
    return render_template(
        "tesm/fileshare_mappings.html",
        mappings=service.list_mappings(conn),
        directory_enabled=directory.is_enabled(conn),
        mount_root=str(core().settings.get(conn, "fileshare_mount_root")),
    )


@bp.post("/zuordnungen")
@permission_required("fileshare.edit")
def mapping_add():
    try:
        with transaction() as conn:
            service.add_mapping(
                conn,
                ad_group_dn=request.form.get("ad_group_dn", ""),
                ad_group_name=request.form.get("ad_group_name", ""),
                label=request.form.get("label", ""),
                unc=request.form.get("unc", ""),
            )
        _audit("fileshare.mapping_added", request.form.get("label", ""), severity="notice")
        flash("Zuordnung gespeichert.", "success")
    except service.FileshareError as exc:
        flash(str(exc), "danger")
    return redirect(url_for("fileshare.mappings"))


@bp.post("/zuordnungen/<int:mapping_id>/loeschen")
@permission_required("fileshare.edit")
def mapping_delete(mapping_id: int):
    with transaction() as conn:
        service.delete_mapping(conn, mapping_id)
    _audit("fileshare.mapping_deleted", str(mapping_id), severity="notice")
    flash("Zuordnung entfernt.", "success")
    return redirect(url_for("fileshare.mappings"))
