import os
import shutil
import traceback
from datetime import datetime, timezone

from flask import Blueprint
from flask import current_app as app
from flask import jsonify, request, send_from_directory
from flask_babel import gettext
from flask_security import auth_required
from sqlalchemy import update
from werkzeug.datastructures import ImmutableMultiDict

from opentakserver.blueprints.marti_api.data_package_marti_api import data_package_share
from opentakserver.blueprints.ots_api.api import paginate, search
from opentakserver.extensions import db, logger
from opentakserver.forms.data_package_form import DataPackageUpdateForm
from opentakserver.models.Certificate import Certificate
from opentakserver.models.DataPackage import DataPackage

data_package_api = Blueprint("data_package_api", __name__)


@data_package_api.route("/api/data_packages", methods=["PATCH"])
@auth_required()
def edit_data_package():
    form = DataPackageUpdateForm(formdata=ImmutableMultiDict(request.json))
    if not form.validate():
        return jsonify({"success": False, "errors": form.errors}), 400

    data_package = db.session.execute(
        db.session.query(DataPackage).filter_by(hash=form.hash.data)
    ).first()
    if not data_package:
        return (
            jsonify({"success": False, "error": f"Package with hash {form.hash.data} not found"}),
            404,
        )

    data_package = data_package[0]

    if data_package.filename.endswith("_CONFIG.zip"):
        return (
            jsonify(
                {
                    "success": False,
                    "error": gettext(
                        "Server connection data packages can't be installed on enrollment or connection"
                    ),
                }
            ),
            400,
        )

    if form.install_on_enrollment.data is not None:
        data_package.install_on_enrollment = form.install_on_enrollment.data
    if form.install_on_connection.data is not None:
        data_package.install_on_connection = form.install_on_connection.data

    data_package.submission_time = datetime.now(timezone.utc)

    db.session.execute(
        update(DataPackage)
        .filter(DataPackage.hash == data_package.hash)
        .values(**data_package.serialize())
    )
    db.session.commit()

    return jsonify({"success": True})


@data_package_api.route("/api/data_packages", methods=["DELETE"])
@auth_required()
def delete_data_package():
    file_hash = request.args.get("hash")
    if not file_hash:
        return jsonify({"success": False, "error": "Please provide a file hash"}), 400

    query = db.session.query(DataPackage)
    query = search(query, DataPackage, "hash")
    data_package = db.session.execute(query).first()
    if not data_package:
        return jsonify({"success": False, "error": gettext("Invalid/unknown hash")}), 400

    try:
        logger.warning(
            "Deleting data package {} - {}".format(data_package[0].filename, data_package[0].hash)
        )
        db.session.delete(data_package[0])
        db.session.commit()
        os.remove(
            os.path.join(app.config.get("UPLOAD_FOLDER"), "{}.zip".format(data_package[0].hash))
        )

        if data_package[0].certificate:
            Certificate.query.filter_by(id=data_package[0].certificate.id).delete()
            db.session.commit()
            shutil.rmtree(
                os.path.join(
                    app.config.get("OTS_CA_FOLDER"),
                    "certs",
                    data_package[0].certificate.common_name,
                ),
                ignore_errors=True,
            )
    except BaseException as e:
        logger.error("Failed to delete data package")
        logger.error(traceback.format_exc())
        return jsonify({"success": False, "error": str(e)}), 500

    return jsonify({"success": True})


@data_package_api.route("/api/data_packages")
@auth_required()
def data_packages():
    query = db.session.query(DataPackage)
    query = search(query, DataPackage, "filename")
    query = search(query, DataPackage, "hash")
    query = search(query, DataPackage, "creator_uid")
    query = search(query, DataPackage, "keywords")
    query = search(query, DataPackage, "mime_type")
    query = search(query, DataPackage, "size")
    query = search(query, DataPackage, "tool")

    return paginate(query, DataPackage)


@data_package_api.route("/api/data_packages/download")
@auth_required()
def data_package_download():
    if "hash" not in request.args.keys():
        return (
            jsonify({"success": False, "error": gettext("Please provide a data package hash")}),
            400,
        )

    file_hash = request.args.get("hash")

    query = db.session.query(DataPackage)
    query = search(query, DataPackage, "hash")

    data_package = db.session.execute(query).first()

    if not data_package:
        return (
            jsonify(
                {
                    "success": False,
                    "error": gettext("Data package with hash '%(hash)s' not found", hash=file_hash),
                }
            ),
            404,
        )

    download_name = data_package[0].filename
    name, extension = os.path.splitext(download_name)

    return send_from_directory(
        app.config.get("UPLOAD_FOLDER"),
        f"{file_hash}{extension}",
        as_attachment=True,
        download_name=download_name,
    )


@data_package_api.route("/api/data_packages", methods=["POST"])
@auth_required()
def upload_data_package():
    return data_package_share()
