import json
import operator
import os
import uuid
import logging
from functools import reduce

from flask import Blueprint, request
from flask_jwt_extended import jwt_required
from common.status import HTTP_200_OK, HTTP_500_INTERNAL_SERVER_ERROR, HTTP_204_NO_CONTENT
from median.database import mysql_db
from median.models import Magasin, Stock, CatalogType, CatalogVersion, EquipmentUnitStock, Adresse, MagasinFormat
from peewee import DoesNotExist, JOIN
from median.constant import PickingMachineType, CuttingMachineType, EtatAdresse, EcoType, EquipmentType

from ressources.equipments.acced.acceds import generate_acced
from ressources.equipments.astus import generate_astus
from ressources.equipments.riedls import generate_riedl
from ressources.equipments.store_utils import remove_equipment
from ressources.equipments.equipment import generate_equipment_topo
from ressources.equipments.aide import generate_aide
from median.utils import get_counter

from ressources.equipments.externe import generate_external

store_blueprint = Blueprint("store", __name__)
img_folder = os.environ.get("IMAGE_FOLDER", ".")
logger = logging.getLogger("median.webserver")


@store_blueprint.route("<string:pk>/avatar", methods=["PUT"])
@jwt_required()
def upload_avatar(pk):
    equipment = Magasin.get(Magasin.pk == pk)

    for fname in request.files:
        f = request.files.get(fname)

        file_name = None
        if f.mimetype == "image/webp":
            file_name = f"{uuid.uuid4()}.webp"
        if f.mimetype == "image/png":
            file_name = f"{uuid.uuid4()}.png"
        if f.mimetype == "image/jpeg":
            file_name = f"{uuid.uuid4()}.jpg"

        if file_name is not None:
            url_file = f"{img_folder}\\{equipment.avatar}"
            if equipment.avatar != "" and equipment.avatar is not None and os.path.exists(url_file):
                os.remove(url_file)
            equipment.avatar = file_name
            equipment.save()
            f.save(f"{img_folder}\\{file_name}")

        return {"avatar": file_name}, HTTP_200_OK


@store_blueprint.route("<string:pk>/avatar", methods=["DELETE"])
@jwt_required()
def remove_avatar(pk):
    equipment = Magasin.get(Magasin.pk == pk)
    url_file = f"{img_folder}\\{equipment.avatar}"
    if equipment.avatar != "" and equipment.avatar is not None and os.path.exists(url_file):
        os.remove(url_file)
    equipment.avatar = None
    equipment.save()
    return {}, HTTP_204_NO_CONTENT


def get_all_stores(data=None, eco_types=None):
    expr = True

    if eco_types is not None:
        expr = Magasin.eco_type << eco_types

    if data is not None:
        criterias = data["criterias"]

        if len(criterias) > 0:
            lst = list(map(lambda s: (Magasin.libelle.contains(s.strip())), criterias))
            search = reduce(operator.and_, lst)
            expr = reduce(operator.and_, [expr, search])

    mags = (
        Magasin.select(
            Magasin.pk,
            Magasin.mag,
            Magasin.type_mag,
            Magasin.eco_type,
            Magasin.libelle,
            Magasin.type_machine,
            Magasin.avatar,
            CatalogType.type,
            Magasin.dim_2,
            Magasin.dim_3,
            CatalogVersion.subVersion,
        )
        .join(
            CatalogVersion,
            JOIN.LEFT_OUTER,
            on=(CatalogVersion.numVersion == Magasin.type_machine)
            & ((Magasin.eco_type != EcoType.Astus.value) | (CatalogVersion.subVersion.cast("SIGNED") == Magasin.dim_3)),
        )
        .switch(Magasin)
        .join(CatalogType, JOIN.LEFT_OUTER, on=CatalogType.pk == CatalogVersion.type_pk)
        .where(expr)
        .order_by(Magasin.type_mag)
    )

    return [
        {
            "pk": m.pk,
            "mag": m.mag,
            "type_mag": m.type_mag,
            "eco_type": m.eco_type,
            "libelle": m.libelle,
            "nb_line": m.dim_2,
            "nb_col": m.dim_3,
            "subVersion": m.catalogversion.subVersion if hasattr(m, "catalogversion") else None,
            "type_machine": m.type_machine,
            "equipment_type": m.catalogtype.type if hasattr(m, "catalogtype") else None,
            "avatar": m.avatar,
        }
        for m in mags
    ]


@store_blueprint.route("/all", methods=["GET"])
@jwt_required()
def get_all():
    return {"data": get_all_stores()}, 200


@store_blueprint.route("/all", methods=["POST"])
@jwt_required()
def get_equipments_all():
    data = json.loads(request.data)

    return {"data": get_all_stores(data)}, HTTP_200_OK


@store_blueprint.route("/riedls", methods=["GET"])
@jwt_required()
def get_all_riedl():
    return {"list": get_all_stores(eco_types=[EcoType.Riedl.value])}, HTTP_200_OK


@store_blueprint.route("/<string:pk>/add", methods=["POST"])
@jwt_required()
def get_equipment_add_addresses(pk):
    data = json.loads(request.data)

    addresses = data.get("addresses", [])
    format_type = data.get("format", "")

    if not addresses:
        return {"message": "minimum 1 address must be provide!"}, 500

    if not format_type:
        return {"message": "address format must be provide!"}, 500

    equipment = Magasin.select(Magasin).where(Magasin.pk == pk).get()
    for adr in addresses:
        try:
            (
                Adresse.select(Adresse.adresse)
                .join(Magasin, on=Adresse.magasin == Magasin.mag)
                .where((Magasin.pk == pk) & (Adresse.adresse == adr))
            ).get()
        except DoesNotExist:
            with mysql_db.atomic() as transaction:
                try:
                    new_counter = str(get_counter("CONTENANT_PASS", 1, True)).zfill(9)

                    address = Adresse()
                    address.adresse = adr
                    address.magasin = equipment.mag
                    address.etat = EtatAdresse.Libre.value
                    address.nb_div = 1
                    address.dim_x = 1
                    address.dim_y = 1
                    address.princ = 1
                    address.format = format_type
                    address.contenant = new_counter
                    address.save()
                except Exception as e:
                    logger.error(f"Error adding address {adr} to equipment {pk}: {str(e)}")
                    transaction.rollback()
                    return {"message": "equipments.add.address.error"}, HTTP_500_INTERNAL_SERVER_ERROR

    return {}, HTTP_204_NO_CONTENT


@store_blueprint.route("/<string:pk>/exist", methods=["POST"])
@jwt_required()
def get_equipment_address_exist(pk):
    data = json.loads(request.data)

    addresses = data.get("addresses", [])

    a = (
        Adresse.select(Adresse.adresse)
        .join(Magasin, on=Adresse.magasin == Magasin.mag)
        .where((Magasin.pk == pk) & (Adresse.adresse << addresses))
    )

    return {"list": [{"address": item.adresse} for item in a]}, HTTP_200_OK


@store_blueprint.route("/<string:pk>/format", methods=["GET"])
@jwt_required()
def get_equipment_formats(pk):
    formats = (
        MagasinFormat.select(MagasinFormat.format, MagasinFormat.pk)
        .join(Magasin, on=Magasin.mag == MagasinFormat.magasin)
        .where(Magasin.pk == pk)
    )
    return {"list": [{"pk": item.pk, "format": item.format} for item in formats]}, HTTP_200_OK


@store_blueprint.route("/<string:pk>/description", methods=["GET"])
@jwt_required()
def get_equipment_description(pk):
    equipment = Magasin.select(Magasin).where(Magasin.pk == pk).get()
    return {
        "data": {
            "pk": equipment.pk,
            "nbDim": equipment.nb_dim,
            "lib2": equipment.lib_2,
            "lib3": equipment.lib_3,
            "lib4": equipment.lib_4,
            "lib5": equipment.lib_5,
        }
    }, HTTP_200_OK


@store_blueprint.route("/<string:pk>", methods=["GET"])
@jwt_required()
def get_equipment(pk):
    equipment = Magasin.select(Magasin).where(Magasin.pk == pk).get()

    return {
        "data": {
            "pk": equipment.pk,
            "mag": equipment.mag,
            "type_mag": equipment.type_mag,
            "eco_type": equipment.eco_type,
            "libelle": equipment.libelle,
            "type_machine": equipment.type_machine,
            "id_zone": equipment.id_zone,
            "id_robot": equipment.id_robot,
            "dest": equipment.dest,
            "avatar": equipment.avatar,
        }
    }, HTTP_200_OK


@store_blueprint.route("/<string:pk>", methods=["DELETE"])
@jwt_required()
def delete_equipment(pk):
    equipment = Magasin.select(Magasin).where(Magasin.pk == pk).get()

    stk = Stock.select(Stock.pk).where(equipment.mag == Stock.magasin).count()

    if stk == 0:
        remove_equipment(equipment)
        return {}, HTTP_204_NO_CONTENT
    else:
        return {"message": "equipments.delete.equipment.notempty"}, HTTP_500_INTERNAL_SERVER_ERROR


@store_blueprint.route("/validate", methods=["POST"])
@jwt_required()
def validate_equipment():
    data = json.loads(request.data)
    val = {}
    try:
        Magasin.get(Magasin.mag == data["mag"])
        return {"message": "equipments.validate.equipment.exist"}, HTTP_500_INTERNAL_SERVER_ERROR
    except DoesNotExist:
        val = {}

    return val, HTTP_204_NO_CONTENT


@store_blueprint.route("/init", methods=["POST"])
@jwt_required()
def init_equipment():
    data = json.loads(request.data)
    type = data["type"]
    version = data["version"]

    try:
        val = {}
        if type == EquipmentType.EXTERNE.value:
            count = Magasin.select(Magasin.pk).where(Magasin.eco_type == EcoType.Externe.value).count()
            val = {
                "mag": f"E{str(count + 1).zfill(2)}",
                "type_mag": f"EXTERNE_{str(count + 1)}",
                "libelle": f"Externe {str(count + 1)}",
            }

        elif type == EquipmentType.RIEDL.value:
            count = Magasin.select(Magasin.pk).where(Magasin.eco_type == EcoType.Riedl.value).count()
            val = {
                "mag": f"L{str(count + 1).zfill(2)}",
                "type_mag": f"RIEDL_{str(count + 1)}",
                "libelle": f"Riedl {str(count + 1)}",
            }
        elif type == EquipmentType.ACCED.value:
            count = (
                Magasin.select(Magasin.pk).where(Magasin.type_machine << [e.value for e in PickingMachineType]).count()
            )

            prefix = "ACCED" if type == EquipmentType.ACCED.value else "AIDE"
            val = {
                "mag": f"ST{str(count + 1)}",
                "type_mag": f"{prefix}_ST{str(count + 1)}",
                "libelle": f"Acced {str(count + 1)}",
            }
        elif type == EquipmentType.ASTUS.value:
            count = Magasin.select(Magasin.pk).where(Magasin.eco_type == EcoType.Astus.value).count()
            val = {
                "mag": f"U{str(count + 1).zfill(2)}",
                "type_mag": f"ASTUS_{str(count + 1)}",
                "libelle": f"ASTUS {str(count + 1)}",
            }
        elif type == EquipmentType.AIDE.value:
            prefix = "AIDE"
            if version == CuttingMachineType.Aide_Cut.value:
                count = (
                    Magasin.select(Magasin.pk)
                    .where(Magasin.type_machine << [e.value for e in CuttingMachineType])
                    .count()
                )
                mag = f"CU{str(count + 1)}"
                type_mag = f"{prefix}_CU{str(count + 1)}"
                libelle = f"Aide Cut {str(count + 1)}"
            else:
                count = (
                    Magasin.select(Magasin.pk)
                    .where(Magasin.type_machine << [e.value for e in PickingMachineType])
                    .count()
                )

                type_mag = f"{prefix}_ST{str(count + 1)}"
                mag = f"ST{str(count + 1)}"
                libelle = f"Aide {str(count + 1)}"

            val = {
                "mag": mag,
                "type_mag": type_mag,
                "libelle": libelle,
            }
    except DoesNotExist:
        return {"message": "equipments.validate.equipment.exist"}, HTTP_500_INTERNAL_SERVER_ERROR

    return {"data": val}, HTTP_200_OK


@store_blueprint.route("/categories", methods=["GET"])
def get_eco_type():
    return {"list": [{"code": e.name, "value": e.value} for e in EcoType]}, HTTP_200_OK


@store_blueprint.route("<string:pk>", methods=["PUT"])
@jwt_required()
def save_equipment(pk):
    data = json.loads(request.data)
    version = data.get("version", None)

    equipment = Magasin.get(Magasin.pk == pk)
    equipment.eco_type = data["eco_type"]
    equipment.id_robot = data["id_robot"]
    equipment.id_zone = data["id_zone"]
    equipment.libelle = data["libelle"]

    if version is not None:
        eco_type = (
            CatalogType.select(CatalogType.eco_type)
            .join(CatalogVersion, on=CatalogVersion.type_pk == CatalogType.pk)
            .where(CatalogVersion.pk == version["pk"])
        ).get()
        equipment.type_machine = version["numversion"]
        equipment.eco_type = eco_type.eco_type

    equipment.save()
    return {}, HTTP_204_NO_CONTENT


@store_blueprint.route("/<string:type_mag>/unitstock", methods=["GET"])
@jwt_required()
def equipment_unitstock(type_mag):
    unit_stocks = (
        EquipmentUnitStock.select(EquipmentUnitStock.pk)
        .join(Magasin, on=EquipmentUnitStock.equipment_pk == Magasin.pk)
        .where(Magasin.type_mag == type_mag)
    )
    return {"count": unit_stocks.count()}, HTTP_200_OK


@store_blueprint.route("/generate", methods=["POST"])
@jwt_required()
def generate_equipment():
    try:
        data = json.loads(request.data)
        equipment_type = data["type"]

        if equipment_type == EquipmentType.RIEDL.value:
            equipment = generate_riedl(data)
            return {"data": get_equipment_obj(equipment=equipment, equipment_type=equipment_type)}, HTTP_200_OK
        elif equipment_type == EquipmentType.ACCED.value:
            equipment = generate_acced(data)
            return {"data": get_equipment_obj(equipment=equipment, equipment_type=equipment_type)}, HTTP_200_OK
        elif equipment_type == EquipmentType.ASTUS.value:
            equipment = generate_astus(data)
            if equipment:
                return {"data": get_equipment_obj(equipment=equipment, equipment_type=equipment_type)}, HTTP_200_OK
            else:
                raise Exception("Equipment not created")
        elif equipment_type == EquipmentType.AIDE.value:
            equipment = generate_aide(data)
            return {"data": get_equipment_obj(equipment=equipment, equipment_type=equipment_type)}, HTTP_200_OK
        elif equipment_type == EquipmentType.EXTERNE.value:
            equipment = generate_external(data)
            return {"data": get_equipment_obj(equipment=equipment, equipment_type=equipment_type)}, HTTP_200_OK

        logger.error("Generate Equipment : Unknown equipment")
        return {"message": "dashboard.equipments.generate.unknown"}, HTTP_500_INTERNAL_SERVER_ERROR
    except Exception as e:
        logger.error("Generate Equipment : %s" % str(e))
        return {"message": "dashboard.equipments.generate.error"}, HTTP_500_INTERNAL_SERVER_ERROR


def get_equipment_obj(equipment, equipment_type):
    return {
        "pk": equipment.pk,
        "mag": equipment.mag,
        "type_mag": equipment.type_mag,
        "eco_type": equipment.eco_type,
        "libelle": equipment.libelle,
        "type_machine": equipment.type_machine,
        "id_zone": equipment.id_zone,
        "id_robot": equipment.id_robot,
        "dest": equipment.dest,
        "equipment_type": equipment_type,
    }


@store_blueprint.route("<string:type_mag>/catalog/detail", methods=["GET"])
def get_catalog_detail(type_mag):
    res = generate_equipment_topo(type_mag=type_mag)

    return {"list": res}, HTTP_200_OK
