import os
import socket
from datetime import datetime

import zipstream
from flask import (
    Response,
    abort,
    redirect,
    render_template,
    request,
    send_from_directory,
    url_for,
)
from werkzeug.utils import secure_filename

from pyzbrowser import app
from pyzbrowser.utils import (
    get_file_icon,
    get_root_dir,
    get_root_name_from_path,
    load_root_dirs,
    remove_root_from_path,
)

ROOT_DIRS = load_root_dirs()


@app.route("/")
@app.route("/browse/<path:subpath>")
def browse(subpath=""):
    hostname = socket.gethostname()

    # Se não há subpath, mostrar as pastas raiz disponíveis
    if not subpath:
        items = []
        for root_name, root_path in ROOT_DIRS:
            if os.path.exists(root_path):
                try:
                    created_time = os.path.getctime(root_path)
                    created_date = datetime.fromtimestamp(created_time).strftime(
                        "%d/%m/%Y %H:%M"
                    )
                except Exception:
                    created_time = 0
                    created_date = "Desconhecida"

                items.append(
                    {
                        "name": root_name,
                        "path": root_name,
                        "is_dir": True,
                        "icon": "📁",
                        "created": created_time,
                        "created_date": created_date,
                        "ext": "",
                        "is_viewable": False,
                    }
                )

        return render_template(
            "index.html",
            items=items,
            current_path="",
            parent_path="",
            hostname=hostname,
            search_query="",
            sort_by="name",
        )

    # Extrair o nome da raiz do path
    root_name = get_root_name_from_path(subpath)
    if not root_name:
        abort(404)

    ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
    if not ROOT_DIR:
        abort(404)

    # Remover o nome da raiz do subpath para obter o caminho relativo
    relative_path = remove_root_from_path(subpath)

    # Segurança: Evita que o usuário suba níveis com ".."
    full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
    if not full_path.startswith(os.path.abspath(ROOT_DIR)):
        abort(403)

    if not os.path.exists(full_path):
        abort(404)

    # Se for arquivo, faz o download direto
    if os.path.isfile(full_path):
        return send_from_directory(
            os.path.dirname(full_path), os.path.basename(full_path)
        )

    # Obter parâmetros de busca e ordenação
    search_query = request.args.get("search", "").lower().strip()
    sort_by = request.args.get("sort", "name")  # name, date, type

    # Listagem de diretório
    items = []
    try:
        for name in os.listdir(full_path):
            # Filtrar por busca
            if search_query and search_query not in name.lower():
                continue

            item_full_path = os.path.join(full_path, name)
            # Incluir o nome da raiz no path
            if relative_path:
                rel_path = f"{root_name}/{relative_path}/{name}".replace("\\", "/")
            else:
                rel_path = f"{root_name}/{name}".replace("\\", "/")
            is_dir = os.path.isdir(item_full_path)

            # Obter data de criação
            try:
                created_time = os.path.getctime(item_full_path)
                created_date = datetime.fromtimestamp(created_time).strftime(
                    "%d/%m/%Y %H:%M"
                )
            except Exception:
                created_time = 0
                created_date = "Desconhecida"

            ext = os.path.splitext(name)[1].lower() if not is_dir else ""

            # Verificar se é um arquivo visualizável
            viewable_extensions = [
                # Imagens
                ".jpg",
                ".jpeg",
                ".png",
                ".gif",
                ".bmp",
                ".svg",
                ".webp",
                ".ico",
                # Vídeos
                ".mp4",
                ".webm",
                ".ogg",
                # Áudio
                ".mp3",
                ".wav",
                ".ogg",
                ".m4a",
                # Documentos
                ".pdf",
                # Texto/Código
                ".txt",
                ".md",
                ".log",
                ".py",
                ".js",
                ".jsx",
                ".ts",
                ".tsx",
                ".ini",
                ".html",
                ".htm",
                ".css",
                ".scss",
                ".sass",
                ".json",
                ".xml",
                ".yaml",
                ".yml",
                ".c",
                ".cpp",
                ".h",
                ".hpp",
                ".java",
                ".php",
                ".rb",
                ".go",
                ".rs",
                ".sh",
                ".bat",
            ]
            is_viewable = ext in viewable_extensions

            items.append(
                {
                    "name": name,
                    "path": rel_path,
                    "is_dir": is_dir,
                    "icon": get_file_icon(name, is_dir),
                    "created": created_time,
                    "created_date": created_date,
                    "ext": ext,
                    "is_viewable": is_viewable,
                }
            )
    except PermissionError:
        abort(403)

    # Ordenar itens
    if sort_by == "name":
        items.sort(key=lambda x: (not x["is_dir"], x["name"].lower()))
    elif sort_by == "date":
        items.sort(key=lambda x: (not x["is_dir"], -x["created"]))
    elif sort_by == "type":
        items.sort(key=lambda x: (not x["is_dir"], x["ext"], x["name"].lower()))

    # Calcular o caminho pai para o botão "Voltar"
    parent_path = ""
    if subpath:
        parts = subpath.split("/")
        if len(parts) > 1:
            parent_path = "/".join(parts[:-1])
        # Se só tem o nome da raiz, parent_path fica vazio (volta para home)

    return render_template(
        "index.html",
        items=items,
        current_path=subpath,
        parent_path=parent_path,
        hostname=hostname,
        search_query=search_query,
        sort_by=sort_by,
    )


@app.route("/create_folder", methods=["POST"])
def create_folder():
    current_path = request.form.get("path", "")
    folder_name = request.form.get("folder_name", "").strip()

    if not folder_name or not current_path:
        abort(400)

    # Segurança: limpar o nome da pasta
    folder_name = secure_filename(folder_name)

    # Extrair o nome da raiz
    root_name = get_root_name_from_path(current_path)
    if not root_name:
        abort(404)

    ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
    if not ROOT_DIR:
        abort(404)

    relative_path = remove_root_from_path(current_path)
    full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
    if not full_path.startswith(os.path.abspath(ROOT_DIR)):
        abort(403)

    new_folder_path = os.path.join(full_path, folder_name)

    try:
        os.makedirs(new_folder_path, exist_ok=True)
    except Exception:
        abort(500)

    # Redirecionar de volta para o diretório atual
    if current_path:
        return redirect(url_for("browse", subpath=current_path))
    else:
        return redirect(url_for("browse"))


@app.route("/upload", methods=["POST"])
def upload_files():
    current_path = request.form.get("path", "")

    if not current_path:
        abort(400)

    # Extrair o nome da raiz
    root_name = get_root_name_from_path(current_path)
    if not root_name:
        abort(404)

    ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
    if not ROOT_DIR:
        abort(404)

    relative_path = remove_root_from_path(current_path)

    # Validar diretório de destino
    full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
    if not full_path.startswith(os.path.abspath(ROOT_DIR)):
        abort(403)

    if not os.path.exists(full_path) or not os.path.isdir(full_path):
        abort(404)

    # Verificar se há arquivos no request
    if "files" not in request.files:
        abort(400)

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

    if not files:
        abort(400)

    # Salvar cada arquivo
    for file in files:
        if file and file.filename:
            # Para uploads de pastas, preservar a estrutura de diretórios
            filename = file.filename

            # Normalizar o caminho (trocar barras e remover ../.. )
            filename = filename.replace("\\", "/")

            # Separar diretório e nome do arquivo
            if "/" in filename:
                # É um arquivo dentro de uma estrutura de pastas
                parts = filename.split("/")
                # Criar diretórios necessários
                dir_path = os.path.join(full_path, *parts[:-1])
                try:
                    os.makedirs(dir_path, exist_ok=True)
                except Exception:
                    continue

                # Nome do arquivo final
                final_filename = secure_filename(parts[-1])
                file_path = os.path.join(dir_path, final_filename)
            else:
                # Arquivo único
                final_filename = secure_filename(filename)
                file_path = os.path.join(full_path, final_filename)

            try:
                file.save(file_path)
            except Exception:
                # Continuar com os próximos arquivos mesmo se um falhar
                continue

    # Redirecionar de volta para o diretório atual
    if current_path:
        return redirect(url_for("browse", subpath=current_path))
    else:
        return redirect(url_for("browse"))


@app.route("/delete/<path:subpath>", methods=["POST"])
def delete_item(subpath):
    import shutil

    # Extrair o nome da raiz
    root_name = get_root_name_from_path(subpath)
    if not root_name:
        abort(404)

    ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
    if not ROOT_DIR:
        abort(404)

    relative_path = remove_root_from_path(subpath)
    full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
    if not full_path.startswith(os.path.abspath(ROOT_DIR)):
        abort(403)

    if not os.path.exists(full_path):
        abort(404)

    try:
        if os.path.isdir(full_path):
            shutil.rmtree(full_path)
        else:
            os.remove(full_path)
    except Exception:
        abort(500)

    # Redirecionar de volta para o diretório pai
    parent_path = "/".join(subpath.split("/")[:-1])
    if parent_path:
        return redirect(url_for("browse", subpath=parent_path))
    else:
        return redirect(url_for("browse"))


@app.route("/view/<path:subpath>")
def view_file(subpath):
    # Extrair o nome da raiz
    root_name = get_root_name_from_path(subpath)
    if not root_name:
        abort(404)

    ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
    if not ROOT_DIR:
        abort(404)

    relative_path = remove_root_from_path(subpath)
    full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
    if not full_path.startswith(os.path.abspath(ROOT_DIR)):
        abort(403)

    if not os.path.exists(full_path) or os.path.isdir(full_path):
        abort(404)

    # Servir arquivo sem forçar download (inline)
    return send_from_directory(os.path.dirname(full_path), os.path.basename(full_path))


@app.route("/download/<path:subpath>")
def download_file(subpath):
    # Extrair o nome da raiz
    root_name = get_root_name_from_path(subpath)
    if not root_name:
        abort(404)

    ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
    if not ROOT_DIR:
        abort(404)

    relative_path = remove_root_from_path(subpath)
    full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
    if not full_path.startswith(os.path.abspath(ROOT_DIR)):
        abort(403)

    if not os.path.exists(full_path):
        abort(404)

    # Se for pasta, redirecionar para zipar
    if os.path.isdir(full_path):
        return redirect(url_for("download_zip", subpath=subpath))

    # Forçar download do arquivo
    return send_from_directory(
        os.path.dirname(full_path), os.path.basename(full_path), as_attachment=True
    )


@app.route("/api/search")
def api_search():
    from flask import jsonify

    query = request.args.get("q", "").lower().strip()

    if not query:
        return jsonify({"results": []})

    results = []
    max_results = 100  # Limitar resultados

    try:
        # Buscar em todas as pastas raiz
        for root_name, ROOT_DIR in ROOT_DIRS:
            if not os.path.exists(ROOT_DIR):
                continue

            for root, dirs, files in os.walk(ROOT_DIR):
                # Verificar se ainda está dentro do ROOT_DIR
                if not os.path.abspath(root).startswith(os.path.abspath(ROOT_DIR)):
                    continue

                # Buscar em pastas
                for dir_name in dirs:
                    if len(results) >= max_results:
                        break
                    if query in dir_name.lower():
                        full_path = os.path.join(root, dir_name)
                        rel_path = os.path.relpath(full_path, ROOT_DIR).replace(
                            "\\", "/"
                        )
                        parent_path = os.path.relpath(root, ROOT_DIR).replace("\\", "/")
                        if parent_path == ".":
                            parent_path = ""

                        # Adicionar o nome da raiz ao path
                        if parent_path:
                            full_rel_path = f"{root_name}/{rel_path}"
                            full_parent_path = f"{root_name}/{parent_path}"
                        else:
                            full_rel_path = f"{root_name}/{rel_path}"
                            full_parent_path = root_name

                        results.append(
                            {
                                "name": dir_name,
                                "path": full_rel_path,
                                "parent_path": full_parent_path,
                                "icon": get_file_icon(dir_name, True),
                                "is_dir": True,
                            }
                        )

                # Buscar em arquivos
                for file_name in files:
                    if len(results) >= max_results:
                        break
                    if query in file_name.lower():
                        full_path = os.path.join(root, file_name)
                        rel_path = os.path.relpath(full_path, ROOT_DIR).replace(
                            "\\", "/"
                        )
                        parent_path = os.path.relpath(root, ROOT_DIR).replace("\\", "/")
                        if parent_path == ".":
                            parent_path = ""

                        # Adicionar o nome da raiz ao path
                        if parent_path:
                            full_rel_path = f"{root_name}/{rel_path}"
                            full_parent_path = f"{root_name}/{parent_path}"
                        else:
                            full_rel_path = f"{root_name}/{rel_path}"
                            full_parent_path = root_name

                        results.append(
                            {
                                "name": file_name,
                                "path": full_rel_path,
                                "parent_path": full_parent_path,
                                "icon": get_file_icon(file_name, False),
                                "is_dir": False,
                            }
                        )

                if len(results) >= max_results:
                    break

            if len(results) >= max_results:
                break

    except Exception as e:
        return jsonify({"results": [], "error": str(e)})

    return jsonify({"results": results})


@app.route("/download_selected", methods=["POST"])
def download_selected():

    selected_items = request.form.getlist("items[]")

    if not selected_items:
        abort(400)

    # Se apenas 1 item e é arquivo, baixar direto
    if len(selected_items) == 1:
        subpath = selected_items[0]

        # Extrair o nome da raiz
        root_name = get_root_name_from_path(subpath)
        if not root_name:
            abort(404)

        ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
        if not ROOT_DIR:
            abort(404)

        relative_path = remove_root_from_path(subpath)
        full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
        if not full_path.startswith(os.path.abspath(ROOT_DIR)):
            abort(403)

        if os.path.isfile(full_path):
            return send_from_directory(
                os.path.dirname(full_path),
                os.path.basename(full_path),
                as_attachment=True,
            )

    # Múltiplos items ou pasta: zipar
    z = zipstream.ZipFile(mode="w", compression=zipstream.ZIP_DEFLATED)

    # Determinar nome do arquivo ZIP
    if len(selected_items) == 1:
        zip_filename = os.path.basename(selected_items[0]) + ".zip"
    else:
        zip_filename = "download.zip"

    for subpath in selected_items:
        # Extrair o nome da raiz
        root_name = get_root_name_from_path(subpath)
        if not root_name:
            continue

        ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
        if not ROOT_DIR:
            continue

        relative_path = remove_root_from_path(subpath)
        full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
        if not full_path.startswith(os.path.abspath(ROOT_DIR)):
            continue

        if not os.path.exists(full_path):
            continue

        if os.path.isfile(full_path):
            arcname = os.path.basename(full_path)
            z.write(full_path, arcname)
        elif os.path.isdir(full_path):
            for root, dirs, files in os.walk(full_path):
                for file in files:
                    file_path = os.path.join(root, file)
                    arcname = os.path.join(
                        os.path.basename(full_path),
                        os.path.relpath(file_path, full_path),
                    )
                    z.write(file_path, arcname)

    response = Response(z, mimetype="application/zip")
    response.headers["Content-Disposition"] = f"attachment; filename={zip_filename}"
    return response


@app.route("/zip/<path:subpath>")
def download_zip(subpath):
    # Extrair o nome da raiz
    root_name = get_root_name_from_path(subpath)
    if not root_name:
        abort(404)

    ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
    if not ROOT_DIR:
        abort(404)

    relative_path = remove_root_from_path(subpath)
    full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
    if not full_path.startswith(os.path.abspath(ROOT_DIR)):
        abort(403)

    if not os.path.isdir(full_path):
        abort(400)

    # Criando o Stream de ZIP
    z = zipstream.ZipFile(mode="w", compression=zipstream.ZIP_DEFLATED)

    for root, dirs, files in os.walk(full_path):
        for file in files:
            file_path = os.path.join(root, file)
            arcname = os.path.relpath(file_path, full_path)
            z.write(file_path, arcname)

    filename = os.path.basename(full_path) or "download"
    response = Response(z, mimetype="application/zip")
    response.headers["Content-Disposition"] = f"attachment; filename={filename}.zip"
    return response
