from typing import List
from uuid import UUID

import inject
from sqlalchemy.sql.expression import desc
from sqlmodel import Session, between, select

from src.core.events import EventDescription
from src.core.helpers.exceptions import DataValidationError, NotAuthorizedError, NotFoundError
from src.core.models import (
    Client,
    Context,
    CreateBalance,
    CreateOrder,
    Item,
    Order,
    OrderDetail,
    QueryOrder,
    UpdateOrder,
    UpdateOrderStatus,
)
from src.core.services import Streamer

from . import balance


def get_all(session: Session, query_schema: QueryOrder, context: Context) -> List[Order]:
    args = []

    if query_schema.status is not None:
        args.append(Order.status == query_schema.status)

    if not context.user_is_super_user:
        args.append(Order.owner_id == context.user_id)

    if query_schema.start_date is not None and query_schema.end_date is not None:
        args.append(between(Order.date, query_schema.start_date, query_schema.end_date))

    return session.exec(select(Order).where(*args).order_by(desc(Order.date))).all()


def get_by_id(session: Session, order_id: UUID, context: Context) -> Order:
    order = session.exec(select(Order).where(Order.id == order_id)).first()

    if not order:
        raise NotFoundError("Não foi possível localizar a venda com ID: %s" % order_id)

    if not context.user_is_super_user and order.owner_id != context.user_id:
        raise NotAuthorizedError(f"Você não possui permissão para consultar a Venda {order_id}")

    return order


@inject.params(streamer=Streamer)
def register_sale(session: Session, schema: CreateOrder, context: Context, streamer: Streamer) -> Order:
    if not session.exec(select(Client).where(Client.id == schema.client_id)).first():
        raise NotFoundError(f"Não foi possível localizar o cliente com ID: {schema.client_id}")

    checked_ids = []

    for detail in schema.items:
        if detail.item_id in checked_ids:
            continue

        checked_ids.append(detail.item_id)
        item = session.exec(select(Item).where(Item.id == detail.item_id)).first()

        if not item:
            raise NotFoundError(f"Não foi possível salvar a venda, item com o ID {detail.item_id} não existe")

        if not item.avaliable:
            raise DataValidationError(f"O item {item.name} de ID {item.id} não está disponível!")

        total_required = sum(x.item_amount for x in schema.items if x.item_id == item.id)

        if item.amount < total_required:
            raise DataValidationError(
                "O item %(name)s não possui estoque suficiente, disponível: %(amount)s, solicitado: %(required)s"
                % {"name": item.name, "amount": item.amount, "required": total_required}
            )

        item.amount -= total_required
        session.add(item)

    order = Order(**schema.dict(exclude={"details": ...}), owner_id=context.user_id)
    session.add(order)

    for detail in schema.items:
        detail_obj = OrderDetail(**detail.dict(), order_id=order.id)
        session.add(detail_obj)

    balance_schema = CreateBalance(
        value=sum(detail.value for detail in schema.items),
        operation=schema.sale_type.value,
        description=schema.description,
    )
    balance_obj = balance.create(session, balance_schema, context=context)
    session.add(balance_obj)

    session.commit()
    streamer.send_event(description=EventDescription.CREATE_ORDER, context=context, order=order.dict())

    return order


@inject.params(streamer=Streamer)
def delete_by_id(session: Session, order_id: UUID, context: Context, streamer: Streamer) -> Order:
    order = session.exec(select(Order).where(Order.id == order_id)).first()

    if not order:
        raise NotFoundError(f"Não foi possível localizar a venda com o ID: {order_id}")

    if not context.user_is_super_user and order.owner_id != context.user_id:
        raise NotAuthorizedError(f"Você não possui permissão para excluir a Venda {order_id}")

    for detail in [detail for detail in order.items]:
        item = detail.item
        item.amount += detail.item_amount
        session.add(item)

    session.delete(order)
    session.commit()

    streamer.send_event(EventDescription.DELETE_ORDER, context=context, order=order.dict())

    return order


@inject.params(streamer=Streamer)
def update(session: Session, data: UpdateOrder, context: Context, streamer: Streamer) -> Client:
    order = session.exec(select(Order).where(Order.id == data.id)).first()

    if not order:
        raise NotFoundError(f"Não foi possível localizar a venda com ID: {data.id}")

    if not context.user_is_super_user and order.owner_id != context.user_id:
        raise NotAuthorizedError(f"Você não possui permissão para excluir a venda com ID: {data.id}")

    columns = order.__table__.columns.keys()

    for key, value in data.dict(exclude_defaults=True).items():
        if key not in columns:
            continue

        setattr(order, key, value)

    session.add(order)
    session.commit()

    streamer.send_event(
        description=EventDescription.UPDATE_ORDER,
        context=context,
        data={"order_data": order.dict(), "update_schema": data.dict()},
    )

    return order


@inject.params(streamer=Streamer)
def update_status(session: Session, schema: UpdateOrderStatus, context: Context, streamer: Streamer) -> None:
    order = session.exec(select(Order).where(Order.id == schema.order_id)).first()

    if not order:
        raise NotFoundError(f"Não foi possível localizar a venda com o ID: {schema.order_id}")

    if not context.user_is_super_user and order.owner_id != context.user_id:
        raise NotAuthorizedError(f"Você não possui permissão para alterar o status da venda {schema.order_id}")

    order.status = schema.status
    session.add(order)
    session.commit()

    streamer.send_event(EventDescription.UPDATE_ORDER, context=context, order=order.dict(), new_status=schema.status)

src.core.helpers.exceptions.NotFoundError src.core.helpers.exceptions.DataValidationError sqlmodel.select sqlmodel.between inject.params sqlalchemy.sql.expression.desc src.core.helpers.exceptions.NotAuthorizedError
[((1554, 1586), 'inject.params', 'inject.params', ([], {'streamer': 'Streamer'}), '(streamer=Streamer)\n', (1567, 1586), False, 'import inject\n'), ((3515, 3547), 'inject.params', 'inject.params', ([], {'streamer': 'Streamer'}), '(streamer=Streamer)\n', (3528, 3547), False, 'import inject\n'), ((4315, 4347), 'inject.params', 'inject.params', ([], {'streamer': 'Streamer'}), '(streamer=Streamer)\n', (4328, 4347), False, 'import inject\n'), ((5250, 5282), 'inject.params', 'inject.params', ([], {'streamer': 'Streamer'}), '(streamer=Streamer)\n', (5263, 5282), False, 'import inject\n'), ((1284, 1357), 'src.core.helpers.exceptions.NotFoundError', 'NotFoundError', (["('Não foi possível localizar a venda com ID: %s' % order_id)"], {}), "('Não foi possível localizar a venda com ID: %s' % order_id)\n", (1297, 1357), False, 'from src.core.helpers.exceptions import DataValidationError, NotAuthorizedError, NotFoundError\n'), ((1450, 1537), 'src.core.helpers.exceptions.NotAuthorizedError', 'NotAuthorizedError', (['f"""Você não possui permissão para consultar a Venda {order_id}"""'], {}), "(\n    f'Você não possui permissão para consultar a Venda {order_id}')\n", (1468, 1537), False, 'from src.core.helpers.exceptions import DataValidationError, NotAuthorizedError, NotFoundError\n'), ((1792, 1878), 'src.core.helpers.exceptions.NotFoundError', 'NotFoundError', (['f"""Não foi possível localizar o cliente com ID: {schema.client_id}"""'], {}), "(\n    f'Não foi possível localizar o cliente com ID: {schema.client_id}')\n", (1805, 1878), False, 'from src.core.helpers.exceptions import DataValidationError, NotAuthorizedError, NotFoundError\n'), ((3756, 3829), 'src.core.helpers.exceptions.NotFoundError', 'NotFoundError', (['f"""Não foi possível localizar a venda com o ID: {order_id}"""'], {}), "(f'Não foi possível localizar a venda com o ID: {order_id}')\n", (3769, 3829), False, 'from src.core.helpers.exceptions import DataValidationError, NotAuthorizedError, NotFoundError\n'), ((3922, 4007), 'src.core.helpers.exceptions.NotAuthorizedError', 'NotAuthorizedError', (['f"""Você não possui permissão para excluir a Venda {order_id}"""'], {}), "(f'Você não possui permissão para excluir a Venda {order_id}'\n    )\n", (3940, 4007), False, 'from src.core.helpers.exceptions import DataValidationError, NotAuthorizedError, NotFoundError\n'), ((4553, 4623), 'src.core.helpers.exceptions.NotFoundError', 'NotFoundError', (['f"""Não foi possível localizar a venda com ID: {data.id}"""'], {}), "(f'Não foi possível localizar a venda com ID: {data.id}')\n", (4566, 4623), False, 'from src.core.helpers.exceptions import DataValidationError, NotAuthorizedError, NotFoundError\n'), ((4716, 4808), 'src.core.helpers.exceptions.NotAuthorizedError', 'NotAuthorizedError', (['f"""Você não possui permissão para excluir a venda com ID: {data.id}"""'], {}), "(\n    f'Você não possui permissão para excluir a venda com ID: {data.id}')\n", (4734, 4808), False, 'from src.core.helpers.exceptions import DataValidationError, NotAuthorizedError, NotFoundError\n'), ((5509, 5594), 'src.core.helpers.exceptions.NotFoundError', 'NotFoundError', (['f"""Não foi possível localizar a venda com o ID: {schema.order_id}"""'], {}), "(f'Não foi possível localizar a venda com o ID: {schema.order_id}'\n    )\n", (5522, 5594), False, 'from src.core.helpers.exceptions import DataValidationError, NotAuthorizedError, NotFoundError\n'), ((5682, 5789), 'src.core.helpers.exceptions.NotAuthorizedError', 'NotAuthorizedError', (['f"""Você não possui permissão para alterar o status da venda {schema.order_id}"""'], {}), "(\n    f'Você não possui permissão para alterar o status da venda {schema.order_id}'\n    )\n", (5700, 5789), False, 'from src.core.helpers.exceptions import DataValidationError, NotAuthorizedError, NotFoundError\n'), ((942, 1009), 'sqlmodel.between', 'between', (['Order.date', 'query_schema.start_date', 'query_schema.end_date'], {}), '(Order.date, query_schema.start_date, query_schema.end_date)\n', (949, 1009), False, 'from sqlmodel import Session, between, select\n'), ((2159, 2261), 'src.core.helpers.exceptions.NotFoundError', 'NotFoundError', (['f"""Não foi possível salvar a venda, item com o ID {detail.item_id} não existe"""'], {}), "(\n    f'Não foi possível salvar a venda, item com o ID {detail.item_id} não existe'\n    )\n", (2172, 2261), False, 'from src.core.helpers.exceptions import DataValidationError, NotAuthorizedError, NotFoundError\n'), ((2302, 2381), 'src.core.helpers.exceptions.DataValidationError', 'DataValidationError', (['f"""O item {item.name} de ID {item.id} não está disponível!"""'], {}), "(f'O item {item.name} de ID {item.id} não está disponível!')\n", (2321, 2381), False, 'from src.core.helpers.exceptions import DataValidationError, NotAuthorizedError, NotFoundError\n'), ((2533, 2734), 'src.core.helpers.exceptions.DataValidationError', 'DataValidationError', (["('O item %(name)s não possui estoque suficiente, disponível: %(amount)s, solicitado: %(required)s'\n     % {'name': item.name, 'amount': item.amount, 'required': total_required})"], {}), "(\n    'O item %(name)s não possui estoque suficiente, disponível: %(amount)s, solicitado: %(required)s'\n     % {'name': item.name, 'amount': item.amount, 'required': total_required})\n", (2552, 2734), False, 'from src.core.helpers.exceptions import DataValidationError, NotAuthorizedError, NotFoundError\n'), ((1072, 1088), 'sqlalchemy.sql.expression.desc', 'desc', (['Order.date'], {}), '(Order.date)\n', (1076, 1088), False, 'from sqlalchemy.sql.expression import desc\n'), ((1200, 1213), 'sqlmodel.select', 'select', (['Order'], {}), '(Order)\n', (1206, 1213), False, 'from sqlmodel import Session, between, select\n'), ((3672, 3685), 'sqlmodel.select', 'select', (['Order'], {}), '(Order)\n', (3678, 3685), False, 'from sqlmodel import Session, between, select\n'), ((4470, 4483), 'sqlmodel.select', 'select', (['Order'], {}), '(Order)\n', (4476, 4483), False, 'from sqlmodel import Session, between, select\n'), ((5418, 5431), 'sqlmodel.select', 'select', (['Order'], {}), '(Order)\n', (5424, 5431), False, 'from sqlmodel import Session, between, select\n'), ((1716, 1730), 'sqlmodel.select', 'select', (['Client'], {}), '(Client)\n', (1722, 1730), False, 'from sqlmodel import Session, between, select\n'), ((2064, 2076), 'sqlmodel.select', 'select', (['Item'], {}), '(Item)\n', (2070, 2076), False, 'from sqlmodel import Session, between, select\n'), ((1036, 1049), 'sqlmodel.select', 'select', (['Order'], {}), '(Order)\n', (1042, 1049), False, 'from sqlmodel import Session, between, select\n')]