import random
from db.handlers.stock import StockHandler
from db.handlers.toolbox import ToolboxHandler
from db.handlers.toolkit import ToolkitHandler
from db.handlers.user import UserHandler
from db.handlers.access import AccessLevelHandler
from db.handlers.records import StocksRecordsHandler

from db.schemas.records import StocksRecords
from utils import logger


class StocksActions:

    async def registration(
        toolkit_id: int,
        toolbox_id: int,
        user_id: int,
        quantity: int,
        price: int,
        placement: str,
        reason: str,
    ):
        logger.info(f"Приход инструмента {toolkit_id} на складе {toolbox_id} ...")
        newStocks = await StockHandler.add(
            toolkit_id=toolkit_id,
            toolbox_id=toolbox_id,
            quantity=quantity,
            price=price,
            placement=placement,
        )
        if newStocks is None:
            logger.error(
                f"Приход инструмента {toolkit_id} на складе {toolbox_id} не удалось"
            )
            return False

        recorded = await StocksRecordsHandler.add(
            action="Приход",
            source_stock_id=None,
            target_stock_id=newStocks.get("id"),
            source_toolbox_id=None,
            target_toolbox_id=toolbox_id,
            toolkit_id=toolkit_id,
            init_user_id=user_id,
            reason=reason,
            quantity=quantity,
            price=price,
            return_record=True,
        )
        logger.info(
            f"Приход инструмента {toolkit_id} на складе {toolbox_id} прошло {'успешно' if recorded else 'не успешно'}"
        )
        if recorded:
            accepted = await StocksRecordsHandler.decide(
                recorded, user_id, None, quantity, price
            )
            if not accepted:
                logger.error(
                    f"Принятие записи о оприходовании инструмента {toolkit_id} на складе {toolbox_id} не удалось"
                )
            refillUpdated = await ToolkitHandler.updateRefillDate(toolkit_id)
            if not refillUpdated:
                logger.error(
                    f"Обновление даты последнего заполнения инструмента {toolkit_id} не удалось"
                )
            return accepted
        return False

    async def moving(
        action: str,
        source_toolbox_id: int,
        target_toolbox_id: int,
        toolkit_id: int,
        quantity: int,
        target_placement: str = None,
    ):
        logger.info(
            f"{action} инструмента {toolkit_id} со склада {source_toolbox_id} на склад {target_toolbox_id} ..."
        )
        availability = await StockHandler.getByToolboxIdAndToolkitId(
            source_toolbox_id, toolkit_id
        )
        if len(availability) == 0:
            logger.error(f"На складе {source_toolbox_id} нет инструмента {toolkit_id}")
            return False

        totalQuantity = sum([stock["quantity"] for stock in availability])
        if quantity > totalQuantity:
            logger.error(
                f"На складе {source_toolbox_id} нет в количестве {quantity} инструмента {toolkit_id}"
            )
            return False

        totalTakeQuantity = 0
        movementsList = []

        for stock in availability:
            if quantity == totalTakeQuantity:
                break
            takeQuantity = min(stock["quantity"], quantity - totalTakeQuantity)
            totalTakeQuantity += takeQuantity

            logger.info(
                f"Списание инструмента {toolkit_id} со склада {source_toolbox_id} на склад {target_toolbox_id} в количестве {takeQuantity} по цене {stock['price']} ..."
            )
            sourceEdit = await StockHandler.edit(
                stock["id"], quantity=stock["quantity"] - takeQuantity
            )
            if not sourceEdit:
                logger.error(
                    f"Ошибка обновления записи об остатках инструмента {toolkit_id} на складе {source_toolbox_id}"
                )
                return False

            logger.info(
                f"Списание инструмента {toolkit_id} со склада {source_toolbox_id} на склад {target_toolbox_id} в количестве {takeQuantity} по цене {stock['price']} успешно завершена"
            )

            movementsList.append(
                {
                    "id": sourceEdit["id"],
                    "quantity": takeQuantity,
                    "price": stock["price"],
                }
            )

            if not target_toolbox_id:
                logger.warning(
                    f"Производится списание инструмента {toolkit_id} со склада {source_toolbox_id} в количестве {takeQuantity} по цене {stock['price']}"
                )
                continue

            existing = await StockHandler.getByToolboxIdAndToolkitIdAndQPrice(
                target_toolbox_id, toolkit_id, stock["price"]
            )
            if len(existing) == 0:
                logger.info(
                    f"Добавление инструмента {toolkit_id} на склад {target_toolbox_id} по цене {stock['price']} ..."
                )
                targetStock = await StockHandler.add(
                    toolkit_id=toolkit_id,
                    toolbox_id=target_toolbox_id,
                    quantity=takeQuantity,
                    price=stock["price"],
                    placement=target_placement,
                )
                if not targetStock:
                    logger.error(
                        f"Ошибка добавления инструмента {toolkit_id} на склад {target_toolbox_id}"
                    )
                    return False
                logger.info(
                    f"Добавление инструмента {toolkit_id} на склад {target_toolbox_id} по цене {stock['price']} успешно завершено"
                )
            else:
                logger.info(
                    f"Изменение остатков инструмента {toolkit_id} на склад {target_toolbox_id} по цене {stock['price']} ..."
                )
                targetStock = await StockHandler.edit(
                    existing[0]["id"],
                    quantity=existing[0]["quantity"] + takeQuantity,
                )
                if not targetStock:
                    logger.error(
                        f"Ошибка обновления записи об остатках инструмента {toolkit_id} на складе {target_toolbox_id}"
                    )
                    return False
                logger.info(
                    f"Изменение остатков инструмента {toolkit_id} на склад {target_toolbox_id} по цене {stock['price']} успешно завершено"
                )

        logger.info(
            f"{action} инструмента {toolkit_id} со склада {source_toolbox_id} на склад {target_toolbox_id} прошло успешно"
        )
        moveUpdated = await ToolkitHandler.updateMovindDate(toolkit_id)
        if not moveUpdated:
            logger.error(f"Ошибка обновления даты движения инструмента {toolkit_id}")
            return False
        return movementsList

    async def movingRequest(
        action: str,
        toolkit_id: int,
        source_toolbox_id: int,
        target_toolbox_id: int,
        quantity: int,
        reason: str,
        user_id: int,
        price: int = 0,
        return_record: bool = False,
    ):
        logger.info(
            f"Запрос на {action} инструмента {toolkit_id} со склада {source_toolbox_id} в количестве {quantity} по цене {price} ..."
        )
        recorded = await StocksRecordsHandler.add(
            action=action,
            source_stock_id=None,
            target_stock_id=None,
            source_toolbox_id=source_toolbox_id,
            target_toolbox_id=target_toolbox_id,
            toolkit_id=toolkit_id,
            init_user_id=user_id,
            reason=reason,
            quantity=quantity,
            price=price,
            return_record=return_record,
        )
        logger.info(
            f"Запрос на {action} инструмента {toolkit_id} со склада {source_toolbox_id} в количестве {quantity} по цене {price} {'успешно создан' if recorded else 'завершен с ошибкой'}"
        )
        return recorded

    async def movingDecision(
        record_id: int,
        user_id: int,
        accepted: bool = True,
        record: StocksRecords = None,
    ):
        logger.info(
            f"{'Принятие' if accepted else 'Отклонение'} записи {record_id if not record else record.id} о движении инструмента {'' if not record else record.toolkit_id} ..."
        )
        movingRecord = (
            await StocksRecordsHandler.getById(record_id, True)
            if not record
            else record
        )

        if not movingRecord:
            logger.error(f"Запись {record_id} не найдена")
            return False
        if movingRecord.accepted is not None:
            logger.error(
                f"Запись {record_id} уже была {'принята' if movingRecord.accepted else 'отклонена'}"
            )
            return False
        if not accepted:
            return await StocksRecordsHandler.decide(
                movingRecord,
                user_id,
                movingRecord.source_stock_id,
                movingRecord.quantity,
                movingRecord.price,
                accepted,
            )

        target_toolbox_id = (
            movingRecord.target_toolbox_id
            if movingRecord.action != "Списание"
            else None
        )
        logger.warning(f"{target_toolbox_id = }, {movingRecord.action = }")

        stocksMovements = await StocksActions.moving(
            action=movingRecord.action,
            source_toolbox_id=movingRecord.source_toolbox_id,
            target_toolbox_id=target_toolbox_id,
            toolkit_id=movingRecord.toolkit_id,
            quantity=movingRecord.quantity,
        )
        if not stocksMovements:
            logger.error(f"Ошибка при {movingRecord.action} инструмента")
            return False

        accept = await StocksRecordsHandler.decide(
            movingRecord,
            user_id,
            stocksMovements[0].get("id"),
            stocksMovements[0].get("quantity"),
            stocksMovements[0].get("price"),
        )
        if not accept:
            return accept

        totalRecordsIds = [record_id]

        if len(stocksMovements) > 1:
            for stock in stocksMovements[1:]:
                recorded = await StocksRecordsHandler.add(
                    action=movingRecord.action,
                    source_stock_id=stock.get("id"),
                    target_stock_id=None,
                    source_toolbox_id=movingRecord.source_toolbox_id,
                    target_toolbox_id=target_toolbox_id,
                    toolkit_id=movingRecord.toolkit_id,
                    init_user_id=movingRecord.init_user_id,
                    reason=movingRecord.reason,
                    quantity=stock.get("quantity"),
                    price=stock.get("price"),
                    return_record=True,
                )
                if not recorded:
                    return False
                accept = await StocksRecordsHandler.decide(
                    record=recorded,
                    decision_user_id=user_id,
                    source_stock_id=stock.get("id"),
                    quantity=stock.get("quantity"),
                    price=stock.get("price"),
                )
                if not accept:
                    return False
                totalRecordsIds.append(recorded)

        logger.info(
            f"Записи {', '.join(map(str, totalRecordsIds))} о {movingRecord.action} инструмента успешно приняты {user_id}"
        )
        return True

    async def takeToolkit(
        source_toolbox_id: int,
        target_toolbox_id: int,
        toolkit_id: int,
        quantity: int,
        reason: str,
        user_id: int,
        price: int = 0,
    ):
        logger.info(
            f"Формирование запроса на получение инструмента {toolkit_id} на склад {target_toolbox_id} со склада {source_toolbox_id} в количестве {quantity} ..."
        )
        takeRequest = await StocksActions.movingRequest(
            action="Получение",
            source_toolbox_id=source_toolbox_id,
            target_toolbox_id=target_toolbox_id,
            toolkit_id=toolkit_id,
            quantity=quantity,
            user_id=user_id,
            reason=reason,
            price=price,
            return_record=True,
        )
        if not takeRequest:
            logger.error(
                f"Формирование запроса на получение инструмента {toolkit_id} не удалось"
            )
            return False
        logger.info(
            f"Формирование запроса на получение инструмента {toolkit_id} успешно завершено"
        )
        logger.info(
            f"Принятие запроса {takeRequest.id} на получение инструмента {toolkit_id} ..."
        )
        accepted = await StocksActions.movingDecision(None, user_id, record=takeRequest)
        if not accepted:
            logger.error(
                f"Принятие запроса {takeRequest.id} на получение инструмента {toolkit_id} не удалось"
            )
            return False
        logger.info(
            f"Принятие запроса {takeRequest.id} на получение инструмента {toolkit_id} успешно завершено"
        )
        return True

    async def initialize():

        toolboxes = await ToolboxHandler.getAll()
        toolboxesDict = {
            toolbox.get("title"): toolbox.get("id") for toolbox in toolboxes
        }
        toolboxesOwners = {
            toolbox.get("owner_id"): toolbox.get("id")
            for toolbox in toolboxes
            if toolbox.get("owner_id")
        }

        users = await UserHandler.getAll()
        usersDict = {user.get("login"): user.get("id") for user in users}

        toolkits = await ToolkitHandler.getAll()

        logger.warning("Наполнение складов ...")
        for toolkit in toolkits:
            if "Сверло" in toolkit.get("title"):
                toolboxId = toolboxesDict.get("Шкаф")
                placement = None
            else:
                toolboxId = toolboxesDict.get("Стеллаж")
                placement = chr(65 + random.randint(0, 25)) + str(random.randint(1, 19))

            registryCount = 5

            logger.warning(
                f">> Наполнение склада {toolboxId} инструментом {toolkit.get('title')}"
            )

            for i in range(registryCount):
                quantity = random.randint(20, 30)
                price = round(300 + random.random() * 500, 2)

                logger.warning(f">>>> Количество: {quantity}, Цена: {price}")

                success = await StocksActions.registration(
                    toolkit_id=toolkit.get("id"),
                    toolbox_id=toolboxId,
                    user_id=usersDict.get("storekeeper"),
                    quantity=quantity,
                    price=price,
                    placement=placement,
                    reason=f"Приход инструмента {toolkit.get('title')}. Счёт-фактура № {random.randint(1000, 10000)}-{i+1}",
                )
                if not success:
                    logger.error(f"Приход инструмента {toolkit.get('title')} не удался")
                    return False

        logger.warning("Наполнение складов завершено")

        logger.warning("Получение инструментов из общих складов ...")

        accessLevels = await AccessLevelHandler.getAll()
        accessLevelsDict = {
            accessLevel.get("id"): accessLevel.get("available_own_toolbox")
            for accessLevel in accessLevels
        }

        users = await UserHandler.getAll()
        usersDict = {user.get("login"): user.get("id") for user in users}

        for user in users:
            userAccessLevelId = user.get("access_level_id")
            if not accessLevelsDict.get(userAccessLevelId):
                continue

            own_toolbox_id = toolboxesOwners.get(user.get("id"))

            logger.warning(
                f"Получение инструментов из общего склада пользователем {user.get('login')} ..."
            )

            for toolkit in toolkits:

                logger.warning(
                    f">> Выдача инструмента {toolkit.get('title')} пользователю {user.get('login')} ..."
                )

                if "Сверло" in toolkit.get("title"):
                    main_toolbox_id = toolboxesDict.get("Шкаф")
                else:
                    main_toolbox_id = toolboxesDict.get("Стеллаж")

                actionsCount = random.randint(3, 5)

                for i in range(actionsCount):
                    success = await StocksActions.takeToolkit(
                        source_toolbox_id=main_toolbox_id,
                        target_toolbox_id=own_toolbox_id,
                        toolkit_id=toolkit.get("id"),
                        quantity=random.randint(10, 19),
                        reason=f"Получение инструмента {toolkit.get('title')}. Для выполнения заказа № {random.randint(1000, 10000)}",
                        user_id=user.get("id"),
                    )
                    if not success:
                        logger.error(
                            f"Получение инструмента {toolkit.get('title')} пользователю {user.get('login')} не удалось"
                        )
                        return False

                logger.warning(
                    f">>>> Получение инструмента {toolkit.get('title')} пользователю {user.get('login')} успешно завершено"
                )

        logger.warning("Получение инструментов из общих складов завершено")

        logger.warning("Направление запросов на возврат и списание инструментов ...")

        requestsDict = {"Списание": [], "Возврат": []}

        for user in users:
            userAccessLevelId = user.get("access_level_id")
            if not accessLevelsDict.get(userAccessLevelId):
                continue

            own_toolbox_id = toolboxesOwners.get(user.get("id"))

            logger.warning(
                f"Направление запросов на возврат и списание инструмента от пользователя {user.get('login')} ..."
            )

            for action in requestsDict.keys():
                for toolkit in toolkits:
                    if action == "Списание":
                        main_toolbox_id = None
                    else:
                        if "Сверло" in toolkit.get("title"):
                            main_toolbox_id = toolboxesDict.get("Шкаф")
                        else:
                            main_toolbox_id = toolboxesDict.get("Стеллаж")

                    actionsCount = random.randint(1, 3)

                    for i in range(actionsCount):
                        success = await StocksActions.movingRequest(
                            action=action,
                            toolkit_id=toolkit.get("id"),
                            source_toolbox_id=own_toolbox_id,
                            target_toolbox_id=main_toolbox_id,
                            quantity=random.randint(3, 5),
                            reason=f"{action} инструмента {toolkit.get('title')}. После выполнения заказа",
                            user_id=user.get("id"),
                            return_record=True,
                        )
                        if not success:
                            logger.error(
                                f"{action} инструмента {toolkit.get('title')} пользователю {user.get('login')} не удалось"
                            )
                            return False

                        requestsDict.get(action).append(success)

                logger.warning(
                    f">>>> {action} инструмента пользователю {user.get('login')} успешно завершено"
                )

        logger.warning(
            "Направление запросов на возврат и списание инструментов завершено"
        )

        logger.warning("Обработка запросов на возврат и списание инструментов ...")

        decisionsDict = {
            "manager": {"accept": [], "reject": [], "ignore": []},
            "storekeeper": {"accept": [], "reject": [], "ignore": []},
        }
        logger.info("Разбивка списоков запросов ...")
        for recordsList in requestsDict.values():
            managerList, storekeeperList = (
                recordsList[: len(recordsList) // 2],
                recordsList[len(recordsList) // 2 :],
            )

            (
                decisionsDict["manager"]["accept"],
                decisionsDict["manager"]["reject"],
                decisionsDict["manager"]["ignore"],
            ) = (
                managerList[: len(managerList) // 3],
                managerList[len(managerList) // 3 : len(managerList) // 3 * 2],
                managerList[len(managerList) // 3 * 2 :],
            )

            (
                decisionsDict["storekeeper"]["accept"],
                decisionsDict["storekeeper"]["reject"],
                decisionsDict["storekeeper"]["ignore"],
            ) = (
                storekeeperList[: len(storekeeperList) // 3],
                storekeeperList[
                    len(storekeeperList) // 3 : len(storekeeperList) // 3 * 2
                ],
                storekeeperList[len(storekeeperList) // 3 * 2 :],
            )

        logger.warning("Применение решений ...")
        for role in decisionsDict.keys():
            logger.warning(f"Принятие записей пользователем {role} ...")
            user_id = usersDict.get(role)
            for decision in decisionsDict.get(role).keys():
                logger.warning(f"Принятие решения {decision} пользователем {role} ...")
                match decision:
                    case "accept":
                        accepted = True
                    case "reject":
                        accepted = False
                    case "ignore":
                        continue
                for record in decisionsDict.get(role).get(decision):
                    logger.info(
                        f"{'Принятие' if accepted else 'Отклонение'} записи {record.id} пользователем {role} ... "
                    )
                    success = await StocksActions.movingDecision(
                        record_id=None,
                        user_id=user_id,
                        accepted=accepted,
                        record=record,
                    )
                    if not success:
                        logger.error(
                            f"{'Принятие' if accepted else 'Отклонение'} записи {record.id} пользователем {role} не удалось"
                        )
                        return False

        logger.warning(
            "Обработка запросов на возврат и списание инструментов завершено"
        )

        return True
