import base64
import os
import traceback

import bleach
import sqlalchemy
from flask import Blueprint
from flask import current_app as app
from flask import jsonify, request
from flask_babel import gettext
from flask_security import auth_required
from meshtastic import apponly_pb2, channel_pb2

from opentakserver.blueprints.ots_api.api import paginate, search
from opentakserver.extensions import db, logger
from opentakserver.models.Meshtastic import MeshtasticChannel

meshtastic_api_blueprint = Blueprint("meshtastic_api_blueprint", __name__)


@meshtastic_api_blueprint.route("/api/meshtastic/channel", methods=["POST"])
@auth_required()
def create_channel():
    channel_set = apponly_pb2.ChannelSet()
    channel_settings = channel_pb2.ChannelSettings()

    # Parse the settings from a Meshtastic URL
    if "url" in request.json.keys() and request.json.get("url"):
        try:
            settings = base64.urlsafe_b64decode(
                bleach.clean(request.json.get("url").split("#")[-1]) + "=="
            )
            channel_set.ParseFromString(settings)
            url = request.json.get("url")

        except BaseException as e:
            logger.error("Failed to parse Meshtastic  URL: {}".format(e))
            logger.error(traceback.format_exc())
            return jsonify({"success": False, "error": str(e)}), 400

    # Make the protobuf from settings in order to make the URL
    else:
        try:
            # channel_set.lora_config.use_preset = 'modem_preset' in request.json.keys()
            channel_set.lora_config.modem_preset = (
                bleach.clean(request.json.get("modem_preset"))
                if request.json.get("modem_preset")
                else None
            )
            channel_set.lora_config.region = (
                bleach.clean(request.json.get("lora_region"))
                if request.json.get("lora_region")
                else None
            )
            channel_set.lora_config.hop_limit = (
                request.json.get("lora_hop_limit") if request.json.get("lora_hop_limit") else None
            )
            channel_set.lora_config.tx_enabled = request.json.get("lora_tx_enabled")
            channel_set.lora_config.tx_power = (
                request.json.get("lora_tx_power") if request.json.get("lora_tx_power") else None
            )
            channel_set.lora_config.sx126x_rx_boosted_gain = request.json.get(
                "lora_sx126x_rx_boosted_gain"
            )
            channel_set.lora_config.use_preset = True

            if "psk" in request.json.keys() and request.json.get("psk"):
                channel_settings.psk = base64.urlsafe_b64decode(
                    bleach.clean(request.json.get("psk"))
                )
            else:
                # Zero bytes indicates no encryption
                channel_settings.psk = bytes(0)
            channel_settings.name = (
                bleach.clean(request.json.get("name")) if request.json.get("name") else None
            )
            channel_settings.uplink_enabled = request.json.get("uplink_enabled")
            channel_settings.downlink_enabled = request.json.get("downlink_enabled")
            channel_settings.module_settings.position_precision = (
                request.json.get("position_precision")
                if request.json.get("position_precision")
                else None
            )

            channel_set.settings.append(channel_settings)

        except BaseException as e:
            logger.error("Failed to save Meshtastic channel: {}".format(e))
            logger.error(traceback.format_exc())
            return jsonify({"success": False, "error": str(e)}), 400

    return save_channel(channel_set)


def save_channel(channel_set: apponly_pb2.ChannelSet):
    for channel_settings in channel_set.settings:
        # Make a unique URL for each channel in a channel set
        single_channel_set = apponly_pb2.ChannelSet()
        single_channel_set.lora_config.CopyFrom(channel_set.lora_config)
        single_channel_set.settings.append(channel_settings)
        url = "https://meshtastic.org/e/#" + base64.urlsafe_b64encode(
            single_channel_set.SerializeToString()
        ).decode("utf-8")

        if channel_settings.downlink_enabled and channel_settings.name not in app.config.get(
            "OTS_MESHTASTIC_DOWNLINK_CHANNELS"
        ):
            app.config.get("OTS_MESHTASTIC_DOWNLINK_CHANNELS")[
                channel_settings.name
            ] = channel_settings.psk
            logger.debug("Added {} to channels".format(channel_settings.name))

        meshtastic_channel_settings = MeshtasticChannel()
        meshtastic_channel_settings.psk = base64.urlsafe_b64encode(channel_settings.psk).decode(
            "ascii"
        )
        meshtastic_channel_settings.name = channel_settings.name
        meshtastic_channel_settings.uplink_enabled = channel_settings.uplink_enabled
        meshtastic_channel_settings.downlink_enabled = channel_settings.downlink_enabled
        meshtastic_channel_settings.position_precision = (
            channel_settings.module_settings.position_precision
        )
        meshtastic_channel_settings.lora_region = channel_set.lora_config.region
        meshtastic_channel_settings.lora_hop_limit = channel_set.lora_config.hop_limit
        meshtastic_channel_settings.lora_tx_enabled = channel_set.lora_config.tx_enabled
        meshtastic_channel_settings.lora_tx_power = channel_set.lora_config.tx_power
        meshtastic_channel_settings.lora_sx126x_rx_boosted_gain = (
            channel_set.lora_config.sx126x_rx_boosted_gain
        )
        meshtastic_channel_settings.modem_preset = channel_set.lora_config.modem_preset
        meshtastic_channel_settings.url = url

        try:
            db.session.add(meshtastic_channel_settings)
            db.session.commit()
        except sqlalchemy.exc.IntegrityError:
            logger.error("Channel with URL {} already exists".format(url))
            # return jsonify({'success': False, 'error': "Channel already exists"}), 400
            continue
        except BaseException as e:
            logger.error("Failed to save Meshtastic channel: {}".format(e))
            logger.error(traceback.format_exc())
            return jsonify({"success": False, "error": str(e)}), 400

    return jsonify({"success": True, "url": url})


@meshtastic_api_blueprint.route("/api/meshtastic/channel", methods=["GET"])
@auth_required()
def get_channel():
    query = db.session.query(MeshtasticChannel)
    query = search(query, MeshtasticChannel, "id")
    query = search(query, MeshtasticChannel, "name")
    query = search(query, MeshtasticChannel, "url")

    return paginate(query, MeshtasticChannel)


@meshtastic_api_blueprint.route("/api/meshtastic/channel", methods=["DELETE"])
@auth_required()
def delete_channel():
    if "url" not in request.args.keys():
        return (
            jsonify(
                {
                    "success": False,
                    "error": gettext("Please provide the URL of the channel to delete"),
                }
            ),
            400,
        )

    url = bleach.clean(request.args.get("url"))

    try:
        channel = db.session.query(MeshtasticChannel).filter(MeshtasticChannel.url == url).first()
        db.session.delete(channel)
        db.session.commit()

        if channel.name in app.config.get("OTS_MESHTASTIC_DOWNLINK_CHANNELS"):
            app.config.get("OTS_MESHTASTIC_DOWNLINK_CHANNELS").pop(channel.name)

    except BaseException as e:
        logger.error("Failed to delete Meshtastic channel: {}".format(e))
        logger.error(traceback.format_exc())
        return jsonify({"success": False, "error": str(e)}), 400

    return jsonify({"success": True})


@meshtastic_api_blueprint.route("/api/meshtastic/generate_psk")
@auth_required()
def generate_psk():
    return (
        jsonify({"success": True, "psk": base64.urlsafe_b64encode(os.urandom(32)).decode("ascii")}),
        200,
    )
