from flask import (
    Blueprint,
    request,
    current_app,
    render_template,
    jsonify,
    send_from_directory,
)
from escpos.printer import Network
from json import dumps, loads
from flask_cors import CORS
import ipaddress
import os
from time import sleep


from ..models.ticket_model import Ticket
from ..models.customer_ticket_model import CustomerTicket

bp = Blueprint("api", __name__, url_prefix="")
CORS(bp)


@bp.route("/", methods=["GET"])
def index():
    """
    Serve the homepage of the web application.
    """

    return render_template("index.html")


@bp.route("/<path:resource>", methods=["GET"])
def static(resource):
    """
    A route to serve static files from "app/static/".
    """
    mimetypes_dict = {
        ".js": "application/javascript",
        ".css": "text/css",
        ".png": "image/png",
        ".gif": "image/gif",
        ".jpeg": "image/jpeg",
        ".jpg": "image/jpeg",
        ".json": "image/json",
        ".svg": "image/svg+xml",
        # add more mappings here
    }

    mimetype = mimetypes_dict.get(
        os.path.splitext(resource)[1], "application/octet-stream"
    )

    staticPath = os.path.join(current_app.config["BASE_DIR"], "app/static/")
    current_app.logger.info(resource + " => " + mimetype)
    return send_from_directory(staticPath, resource, mimetype=mimetype)


@bp.route("/print", methods=["POST"])
def print():
    """
    A route to handle print request.
    """

    response = {}

    req = request.json

    try:
        for item in req:
            if "imprimanteId" not in item:
                raise Exception("imprimanteId field required but not found!")
            if "imprimanteIp" not in item:
                raise Exception("imprimanteIp field required but not found!")
            if "caisseIp" not in item:
                raise Exception("caisseIp field required but not found!")

            # Check IP address
            ipaddress.ip_address(item["imprimanteIp"])

            printerId = -1
            try:
                printerId = int(item["imprimanteId"])
            except Exception as ex:
                current_app.logger.error(ex)

            if printerId == -1:
                response["status"] = 200
            else:
                if handle_printer(dumps(item)):
                    response["status"] = 200
                else:
                    response["status"] = 400

    except Exception as ex:
        response["status"] = 400
        response["error"] = str(ex)
        current_app.logger.error(ex)

    # Sending response
    return jsonify(response), response["status"]


@bp.route("/status", methods=["POST"])
def status():
    """
    A route to handle status request.
    """

    response = {}

    if not isinstance(request.json, list):
        response["status"] = 400
        response["error"] = "Please provide a valide list of IP addresses !"
        return response, response["status"]

    req = request.json
    response["printers"] = []
    try:
        # todo: input must be array/list
        for printer_ip in req:
            current_app.logger.info(
                "Checking printer " + str(printer_ip) + " ..."
            )
            response["printers"].append(check_printer(printer_ip))

        response["status"] = 200

    except Exception as ex:
        response["status"] = 400
        response["error"] = str(ex)
        current_app.logger.error(ex)

    # Sending response
    return jsonify(response), response["status"]


@bp.route("/version", methods=["GET"])
def version():
    """
    A route to handle version request.
    """

    response = {}
    # response["version"] = current_app.config["APP_VERSION"]
    response["application"] = "Mini Print Server"
    response["version"] = "1.0"

    # Sending response
    return jsonify(response)


def handle_printer(msg):
    content = loads(msg)
    printer_ip = content["imprimanteIp"]
    printer = None
    p_err = None
    default_printer = None
    dp_err = None

    attempts = current_app.config["PRINTER_ATTEMPTS"] or 1
    while (printer is None) and attempts > 0:
        (printer, p_err) = connect_printer(printer_ip)
        attempts -= 1
        if printer is None:
            sleep(current_app.config["PRINTER_ATTEMPT_AFTER"])

    if printer is None:
        default_printer_ip = content["caisseIp"]
        (default_printer, dp_err) = connect_printer(default_printer_ip)

        if default_printer is None:
            return False
        else:
            default_printer.set(align="center")
            default_printer.text(
                "*** ERROR ON PRINTER: "
                + str(content["imprimanteId"])
                + "/"
                + str(content["imprimanteIp"])
                + " ***\n"
            )
            default_printer.text(p_err + "\n\n")
            return send_to_printer(default_printer, content)
    else:
        return send_to_printer(printer, content)


def connect_printer(printer_ip):
    """
    Establishes a network connection to the printer with the given IP address.

    Args:
        printer_ip (str): The IP address of the printer to connect to.

    Returns:
        tuple: A tuple containing a Network object representing the printer
        connection and a string, indicating that no error occurred during the
        connection process.

    """
    try:
        printer = Network(
            printer_ip,
            timeout=current_app.config["PRINTER_TIMEOUT"],
        )

        return (printer, "")

    except Exception as ex:
        current_app.logger.error(ex)
        return (None, str(ex))


def send_to_printer(printer, content):
    """
    Sends the specified content to the given printer for printing.

    Args:
        printer (Usb): An instance of the Usb class representing the printer.
        content (dict): A dictionary containing the content to be printed.

    Returns:
        A boolean indicating whether the printing was successful or not.
    """

    ticket = None
    try:
        if "format" in content:
            current_app.logger.info("Format = " + str(content["format"]))
            if content["format"] == "Customer":
                ticket = CustomerTicket()
                ticket.from_json(content)
                ticket.to_printer(printer)

        if ticket is None:
            current_app.logger.info("Format = " + str("Unknown"))
            ticket = Ticket()
            ticket.from_json(content)
            ticket.to_printer(printer)

        return True
    except Exception as ex:
        current_app.logger.error(ex)
        if ticket:
            current_app.logger.info(ticket.to_str())
        return False


def check_printer(printer_ip):
    """
    Checks if a printer is online and available at the given IP address.

    Args:
        printer_ip (str): The IP address of the printer to check.

    Returns:
        A dictionary with the following keys:
        - 'ip': The IP address of the printer.
        - 'online': A bool indicating if the printer is online and available.
        - 'error': A message if an error occurred, otherwise an empty string.
    """

    try:
        # Check if IP address is valid
        ipaddress.ip_address(printer_ip)
        # Connect to printer
        printer = Network(
            printer_ip,
            timeout=current_app.config["PRINTER_TIMEOUT"],
        )

        # Close the connection if it was opened
        printer.close()

        # Return result with no error message
        return {"ip": printer_ip, "online": True, "error": ""}

    except ipaddress.AddressValueError as ex:
        # Log the error and return result with error message
        current_app.logger.error(ex)
        return {"ip": printer_ip, "online": False, "error": str(ex)}

    except Exception as ex:
        # Log the error and return result with error message
        current_app.logger.error(ex)
        return {"ip": printer_ip, "online": False, "error": str(ex)}
