import json
import os
import shutil
import subprocess

import docker
import requests
from flask import Blueprint, jsonify, request, send_from_directory, abort, Response

from .auth import oidc
from .docker_utils import start_server, stop_server, restart_server, get_logs, delete_server, save_server_info, \
    DEFAULT_CONFIG

api = Blueprint('api', __name__)

client = docker.from_env()


# Server Deployment
@api.route('/setup', methods=['POST'])
@oidc.require_login
def setup_server():
    data = request.get_json()
    username = oidc.user_getfield('preferred_username')
    type_ = data['type']
    version = data['version']

    path = f"./servers/mc-{username}"
    os.makedirs(path, exist_ok=True)
    save_server_info(username, type_, version)

    return jsonify({'success': True})


@api.route('/delete', methods=['POST'])
@oidc.require_login
def delete():
    username = oidc.user_getfield('preferred_username')

    if not username:
        return jsonify({"error": "Brak nazwy użytkownika"}), 400

    result = delete_server(username)
    return jsonify({"message": result})


# Server Controls
@api.route('/start', methods=['POST'])
@oidc.require_login
def start():
    username = oidc.user_getfield('preferred_username')
    setup_file_path = f"./servers/mc-{username}/server_info.json"

    if not os.path.exists(setup_file_path):
        return jsonify({"error": "Server setup file not found."}), 400

    with open(setup_file_path, 'r') as file:
        server_info = json.load(file)

    server_type = server_info.get('type')
    server_version = server_info.get('version')

    if not server_type or not server_version:
        return jsonify({"error": "Invalid server info."}), 400

    start_server(username)

    return jsonify({"status": "started"})


@api.route('/stop', methods=['POST'])
@oidc.require_login
def stop():
    username = oidc.user_getfield('preferred_username')
    stop_server(username)
    return jsonify({"status": "stopped"})


@api.route('/restart', methods=['POST'])
@oidc.require_login
def restart():
    username = oidc.user_getfield('preferred_username')
    restart_server(username)
    return jsonify({"status": "restarted"})


@api.route('/logs', methods=['GET'])
@oidc.require_login
def logs():
    username = oidc.user_getfield('preferred_username')
    return jsonify({"logs": get_logs(username)})


@api.route('/command', methods=['POST'])
@oidc.require_login
def send_command():
    data = request.get_json()
    username = data['username']
    command = data['command']

    if username != oidc.user_getfield('preferred_username'):
        return jsonify({"error": "Unauthorized request."}), 403

    container_name = f"mc-{username}"

    try:
        subprocess.run(
            ["docker", "exec", container_name, "rcon-cli", command],
            check=True,
            capture_output=True
        )
        return jsonify(success=True)
    except subprocess.CalledProcessError as e:
        return jsonify(success=False, error=str(e)), 500


# Files APIs (Upload, download, delete)
@api.route('/files', methods=['GET'])
@oidc.require_login
def list_files():
    username = oidc.user_getfield('preferred_username')
    path = request.args.get('path', '')

    base_path = os.path.abspath(f'./servers/mc-{username}')
    requested_path = os.path.abspath(os.path.join(base_path, path))

    if not requested_path.startswith(base_path):
        return abort(403)  # Prevent directory traversal

    if not os.path.exists(requested_path):
        return abort(404)

    entries = []
    for item in os.listdir(requested_path):
        if item == "server_info.json":  # Hiding panel-specific files
            continue
        full_path = os.path.join(requested_path, item)
        entries.append({
            'name': item,
            'is_dir': os.path.isdir(full_path)
        })

    return jsonify(entries)


@api.route('/files/download', methods=['GET'])
@oidc.require_login
def download_file():
    username = oidc.user_getfield('preferred_username')
    path = request.args.get('path')

    base_path = os.path.abspath(f'./servers/mc-{username}')
    file_path = os.path.abspath(os.path.join(base_path, path))

    if not file_path.startswith(base_path) or not os.path.isfile(file_path):
        return abort(403)

    directory = os.path.dirname(file_path)
    filename = os.path.basename(file_path)
    return send_from_directory(directory, filename, as_attachment=True)


@api.route('/files/upload', methods=['POST'])
@oidc.require_login
def upload_file():
    username = oidc.user_getfield('preferred_username')
    path = request.form.get('path', '')
    file = request.files['files']
    base_path = os.path.abspath(f'./servers/mc-{username}')
    upload_path = os.path.abspath(os.path.join(base_path, path))

    if not upload_path.startswith(base_path):
        return abort(403)

    os.makedirs(upload_path, exist_ok=True)
    file.save(os.path.join(upload_path, file.filename))

    return jsonify({'success': True})


@api.route('/files/delete', methods=['POST'])
@oidc.require_login
def delete_file_or_folder():
    data = request.get_json()
    username = data.get('username')
    if username != oidc.user_getfield('preferred_username'):
        return jsonify({"error": "Unauthorized request."}), 403

    path = data.get('path')
    base_path = os.path.abspath(f'./servers/mc-{username}')
    target_path = os.path.abspath(os.path.join(base_path, path))

    if not target_path.startswith(base_path):
        return abort(403)
    if not os.path.exists(target_path):
        return jsonify({"error": "Nie znaleziono pliku lub folderu"}), 404
    try:
        if os.path.isdir(target_path):
            shutil.rmtree(target_path)
        else:
            os.remove(target_path)
        return jsonify({"success": True})
    except Exception as e:
        return jsonify({"error": str(e)}), 500


@api.route('/files/content', methods=['GET'])
@oidc.require_login
def get_file_content():
    username = oidc.user_getfield('preferred_username')
    path = request.args.get('path')

    base_path = os.path.abspath(f'./servers/mc-{username}')
    file_path = os.path.abspath(os.path.join(base_path, path))

    if not file_path.startswith(base_path) or not os.path.isfile(file_path):
        return abort(403)

    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()
        return jsonify({"success": True, "content": content})
    except Exception as e:
        return jsonify({"success": False, "error": str(e)}), 500


@api.route('/files/content', methods=['POST'])
@oidc.require_login
def save_file_content():
    data = request.get_json()
    username = data.get('username')
    if username != oidc.user_getfield('preferred_username'):
        return jsonify({"error": "Unauthorized request."}), 403

    path = data.get('path')
    content = data.get('content')

    base_path = os.path.abspath(f'./servers/mc-{username}')
    file_path = os.path.abspath(os.path.join(base_path, path))

    if not file_path.startswith(base_path) or not os.path.isfile(file_path):
        return abort(403)

    try:
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(content)
        return jsonify({"success": True})
    except Exception as e:
        return jsonify({"success": False, "error": str(e)}), 500


# Server config
@api.route('/config')
@oidc.require_login
def get_config():
    username = oidc.user_getfield('preferred_username')
    server_info_path = f'./servers/mc-{username}/server_info.json'
    if not os.path.exists(server_info_path):
        return jsonify({"success": False, "message": "Server config not found"})
    with open(server_info_path, 'r') as f:
        server_info = json.load(f)

    return jsonify({"success": True, "config": server_info["config"], "version": server_info["version"],
                    "type": server_info["type"]})


@api.route('/config', methods=['POST'])
@oidc.require_login
def update_config():
    data = request.json
    username = data.get('username')
    if username != oidc.user_getfield('preferred_username'):
        return jsonify({"error": "Unauthorized request."}), 403

    incoming_config = data.get('config', {})
    server_info_path = f'./servers/mc-{username}/server_info.json'
    if os.path.exists(server_info_path):
        with open(server_info_path, 'r') as f:
            server_info = json.load(f)
    else:
        server_info = DEFAULT_CONFIG.copy()

    for key in ["type", "version"]:
        if key in incoming_config:
            server_info[key] = incoming_config.pop(key)

    formatted_config = {key.replace('_', '-'): value for key, value in incoming_config.items()}
    server_info["config"] = formatted_config
    with open(server_info_path, 'w') as f:
        json.dump(server_info, f, indent=4)

    return jsonify({"success": True})


# Server stats
@api.route("/status")
def status():
    username = request.args.get("username")
    container_name = f"mc-{username}"
    try:
        container = client.containers.get(container_name)
        return jsonify(running=container.status == "running")
    except docker.errors.NotFound:
        return jsonify(running=False)


@api.route('/stats', methods=['GET'])
@oidc.require_login
def stats():
    username = oidc.user_getfield("preferred_username")
    container_name = f"mc-{username}"

    try:
        container = client.containers.get(container_name)
        stats = container.stats(stream=False)

        # RAM (MB)
        memory_usage = stats['memory_stats']['usage'] / (1024 * 1024)

        # CPU %
        cpu_delta = stats['cpu_stats']['cpu_usage']['total_usage'] - stats['precpu_stats']['cpu_usage']['total_usage']
        system_delta = stats['cpu_stats']['system_cpu_usage'] - stats['precpu_stats']['system_cpu_usage']
        percpu = stats['cpu_stats']['cpu_usage'].get('percpu_usage')
        cpu_count = len(percpu) if percpu else 1
        cpu_usage = (cpu_delta / system_delta) * cpu_count * 100 if system_delta > 0 else 0

        # Disk Usage (GB)
        server_path = f"./servers/{container_name}"
        total_size = sum(
            os.path.getsize(os.path.join(dp, f)) for dp, dn, filenames in os.walk(server_path) for f in filenames
        ) / (1024 * 1024 * 1024)
        disk_usage = min(total_size, 15)

        return jsonify({
            "cpu": round(cpu_usage, 2),
            "ram": round(memory_usage, 2),
            "disk": round(disk_usage, 2),
            "disk_max": 15
        })
    except docker.errors.NotFound:
        return jsonify({"error": "Container not found"}), 404


# Modrinth
@api.route('/modrinth/search')
def modrinth_search():
    query = request.args.get("query")
    server_type = request.args.get("type")  # 'fabric', 'paper', etc.
    version = request.args.get("version")

    categories = {
        'fabric': 'mod',
        'paper': 'plugin'
    }

    category = categories.get(server_type, 'mod')  # fallback
    response = requests.get(
        f"https://api.modrinth.com/v2/search",
        params={
            "query": query,
            "facets": f'[["project_type:{category}"],["categories:{server_type}"],["versions:{version}"]]',
            "limit": 10
        }
    )
    return jsonify(response.json())


@api.route('/modrinth/download', methods=['POST'])
@oidc.require_login
def download_mod():
    data = request.json
    project_id = data['project_id']
    username = data['username']
    server_type = data['type']
    server_version = data['version']

    base_path = f"./servers/mc-{username}/{'plugins' if server_type == 'paper' else 'mods'}"
    os.makedirs(base_path, exist_ok=True)

    downloaded_files = []
    visited_versions = set()

    def download_project(pid, version_id=None):
        if version_id:
            version_data = requests.get(f"https://api.modrinth.com/v2/version/{version_id}").json()
        else:
            all_versions = requests.get(f"https://api.modrinth.com/v2/project/{pid}/version").json()
            version_data = next(
                (v for v in all_versions
                 if server_version in v['game_versions']
                 and server_type in v['loaders']
                 and v.get('server_side') != 'unsupported'),
                None)
            if not version_data:
                print(f"[SKIP] No compatible version found for {pid}")
                return

        if version_data["id"] in visited_versions:
            return
        visited_versions.add(version_data["id"])

        if version_data.get('server_side') == 'unsupported':
            print(f"[SKIP] {version_data.get('name', 'Nieznana nazwa')} is client-only")
            return

        file = next((f for f in version_data['files'] if f.get('primary', True)), version_data['files'][0])
        file_path = os.path.join(base_path, file['filename'])

        if not os.path.exists(file_path):
            print(f"[DOWNLOAD] {file['filename']}")
            with requests.get(file['url'], stream=True) as r:
                with open(file_path, 'wb') as f:
                    for chunk in r.iter_content(chunk_size=8192):
                        f.write(chunk)

        downloaded_files.append({
            "filename": file['filename'],
            "name": version_data.get("name") or version_data.get("version_number") or "Nieznana wersja",
            "project_id": version_data["project_id"],
            "version_id": version_data["id"]
        })

        for dep in version_data.get('dependencies', []):
            if dep.get('version_id'):
                try:
                    download_project(dep['project_id'], dep['version_id'])
                except Exception as e:
                    print(f"[ERROR] Failed to fetch dep version {dep['version_id']}: {e}")
            elif dep.get('project_id'):
                try:
                    download_project(dep['project_id'])
                except Exception as e:
                    print(f"[ERROR] Failed to fetch dep project {dep['project_id']}: {e}")

    download_project(project_id)

    return jsonify({"success": True, "downloaded": downloaded_files})

