from fastapi import APIRouter, Depends, HTTPException, Query, Path
from sqlmodel import Session, select
from sqlalchemy.exc import IntegrityError
from typing import List, Any
import datetime as dt
from app.src.common.utils import profiling_api

from app.src.models.product import (
    Product,
    ProductRead,
    ProductCreate,
    ProductUpdate,
    ProductReadwithTypeAndTags,
)
from app.src.db.engine import get_session
from app.src.api.endpoints.product_type import get_producttype_or_404
from app.src.api.endpoints.tags import get_tag_or_404, get_tag_by_name_or_404
from app.src.common.security import get_current_user
from app.src.models.app_user import AppUser
from app.src.models.tag import Tag
from app.src.logger import logger


router = APIRouter()


async def get_product_or_404(
    *,
    session: Session = Depends(get_session),
    product_id: int = Path(..., ge=1),
    current_user: AppUser = Depends(get_current_user),
):
    start_time = dt.datetime.now()
    try:
        db_product = session.get(Product, product_id)
        if db_product:
            return {
                "db_product": db_product,
                "username": current_user.username,
                "start_time": start_time,
            }
        else:
            logger.error("Product not found")
            logger.exception("Product not found")
            raise HTTPException(status_code=404, detail="Product not found")
    except KeyError:
        logger.error("Product not found")
        logger.exception("KeyError: Product not found")
        raise HTTPException(status_code=400, detail="Product not found")


@router.get("/", response_model=List[ProductReadwithTypeAndTags])
async def read_all_products(
    session: Session = Depends(get_session),
    offset: int = 0,
    limit: int = Query(default=100, lte=100),
    current_user: AppUser = Depends(get_current_user),
) -> Any:
    """
    Retrieve all products
    """
    start_time = dt.datetime.now()
    products = session.exec(select(Product).offset(offset).limit(limit)).all()
    profiling_api("Product:get:all", start_time, current_user.username)
    return products


@router.get("/{product_id}", response_model=ProductReadwithTypeAndTags)
async def read_product(
    *, product_id: int, db_product: Product = Depends(get_product_or_404)
):
    """
    Get the product type by id
    """
    # Le righe commentate sotto, sostituite dalla nuova Depends
    # Nota: il parametro product_id a get_product_or_404 è preso dal path
    # p = session.get(Product, product_id)
    # if not p:
    #     raise HTTPException(
    #         status_code=404,
    #         detail="Product type not found"
    #         )
    profiling_api(
        f"Product:read:by_id:{product_id}",
        db_product["start_time"],
        db_product["username"],
    )
    return db_product["db_product"]


@router.post("/", response_model=ProductRead)
async def create_product(
    *,
    session: Session = Depends(get_session),
    product: ProductCreate,
    current_user: AppUser = Depends(get_current_user),
) -> Any:
    """
    Create a new single product
    """
    start_time = dt.datetime.now()
    # Controllo esistenza product type
    _ = await get_producttype_or_404(producttype_id=product.type_id, session=session)
    # Controllo integrità o altri errori
    try:
        db_product = Product.from_orm(product)
        session.add(db_product)
        session.commit()
        profiling_api("Product:insert:single", start_time, current_user.username)
    except IntegrityError:
        logger.error("Impossible to create product with same name")
        logger.exception("Integrity Error: Impossible to create product with same name")
        raise HTTPException(
            status_code=404, detail="Impossible to create product with same name"
        )
    session.refresh(db_product)
    return db_product


@router.patch("/update/{product_id}", response_model=ProductRead)
async def update_product_by_id(
    *,
    product_id: int,
    session: Session = Depends(get_session),
    product: ProductUpdate,
    db_product: Product = Depends(get_product_or_404),
):
    """
    Modify and existing product by id
    """
    # Le righe commentate sotto, sostituite dalla nuova Depends
    # Nota: il parametro product_id a get_product_or_404 è preso dal path
    # db_product = session.get(Product, product_id)
    # if not db_product:
    #     raise HTTPException(status_code=404, detail="Product not found")
    existing_product = db_product["db_product"]
    pr_data = product.dict(exclude_unset=True)

    for key, value in pr_data.items():
        setattr(existing_product, key, value)

    session.add(existing_product)
    session.commit()
    session.refresh(existing_product)

    profiling_api(
        f"Product:update:by_id:{product_id}",
        db_product["start_time"],
        db_product["username"],
    )
    return existing_product


@router.patch("/update/by_name/{product_name}", response_model=ProductRead)
async def update_product_by_name(
    *,
    session: Session = Depends(get_session),
    product_name: str,
    product: ProductUpdate,
    current_user: AppUser = Depends(get_current_user),
):
    """
    Modify an existing product by name
    """
    start_time = dt.datetime.now()
    db_product = session.exec(select(Product).where(Product.name == product_name)).one()
    if not db_product:
        raise HTTPException(
            status_code=404, detail="Product not found, impossible to update"
        )
    pr_data = product.dict(exclude_unset=True)  # to use the nullable data
    for key, value in pr_data.items():
        setattr(db_product, key, value)
    session.add(db_product)
    session.commit()
    session.refresh(db_product)
    profiling_api(
        f"Product:update:by_name:{product_name}",
        start_time,
        current_user.username,
    )
    return db_product


@router.patch(
    "/update/{product_id}/add_tag_by_id/{tag_id}",
    response_model=ProductReadwithTypeAndTags,
)
async def update_product_add_tag_by_id(
    *,
    product_id: int,
    session: Session = Depends(get_session),
    db_product: Product = Depends(get_product_or_404),
    db_tag: Tag = Depends(get_tag_or_404),
):
    """
    Add tag to product
    """
    existing_product = db_product["db_product"]
    existing_tag = db_tag["db_tag"]

    existing_product.tags.append(existing_tag)
    session.add(existing_product)
    session.commit()
    session.refresh(existing_product)

    profiling_api(
        f"Product:update:add_tag:by_id:{product_id}",
        db_product["start_time"],
        db_product["username"],
    )
    return existing_product


@router.patch(
    "/update/{product_id}/add_tag_by_name/{tag_name}",
    response_model=ProductReadwithTypeAndTags,
)
async def update_product_add_tag_by_name(
    *,
    product_id: int,
    session: Session = Depends(get_session),
    db_product: Product = Depends(get_product_or_404),
    db_tag: Tag = Depends(get_tag_by_name_or_404),
):
    """
    Add tag to product
    """
    existing_product = db_product["db_product"]
    existing_tag = db_tag["db_tag"]

    existing_product.tags.append(existing_tag)
    session.add(existing_product)
    session.commit()
    session.refresh(existing_product)

    profiling_api(
        f"Product:update:add_tag:by_name:{product_id}",
        db_product["start_time"],
        db_product["username"],
    )
    return existing_product


@router.patch(
    "/update/{product_id}/remove_tag_by_id/{tag_id}",
    response_model=ProductReadwithTypeAndTags,
)
async def update_product_remove_tag_by_id(
    *,
    product_id: int,
    session: Session = Depends(get_session),
    db_product: Product = Depends(get_product_or_404),
    db_tag: Tag = Depends(get_tag_or_404),
):
    """
    Remove tag from product
    """
    existing_product = db_product["db_product"]
    existing_tag = db_tag["db_tag"]
    try:
        existing_product.tags.remove(existing_tag)
        session.add(existing_product)
        session.commit()
        session.refresh(existing_product)

        profiling_api(
            f"Product:update:remove_tag:by_id:{product_id}",
            db_product["start_time"],
            db_product["username"],
        )
    except Exception as message:
        logger.error(message)
        logger.exception(message)
        raise HTTPException(
            status_code=404,
            detail="Impossible to remove the tag: product or tag not existing",
        )
    return existing_product


@router.patch(
    "/update/{product_id}/remove_tag_by_name/{tag_name}",
    response_model=ProductReadwithTypeAndTags,
)
async def update_product_remove_tag_by_name(
    *,
    product_id: int,
    session: Session = Depends(get_session),
    db_product: Product = Depends(get_product_or_404),
    db_tag: Tag = Depends(get_tag_by_name_or_404),
):
    """
    Remove tag from product
    """
    existing_db_product = db_product["db_product"]
    existing_db_tag = db_tag["db_tag"]

    db_product.tags.remove(existing_db_tag)
    session.add(existing_db_product)
    session.commit()
    session.refresh(existing_db_product)

    profiling_api(
        f"Product:update:remove_tag:by_name:{product_id}",
        db_product["start_time"],
        db_product["username"],
    )
    return db_product


@router.delete("/{product_id}")
async def delete_product(
    *,
    product_id: int,
    session: Session = Depends(get_session),
    db_product: Product = Depends(get_product_or_404),
):
    """
    Delete and remove an existing product by id; it must be >= 1
    """
    # Le righe commentate sotto, sostituite dalla nuova Depends
    # Nota: il parametro product_id a get_product_or_404 è preso dal path
    # product = session.get(Product, product_id)
    # if not product:
    #     raise HTTPException(
    #         status_code=404, detail="Product not found, impossible to remove"
    #     )
    existing_db_product = db_product["db_product"]
    session.delete(existing_db_product)
    session.commit()
    profiling_api(
        f"Product:update:add_tag:by_id:{product_id}",
        db_product["start_time"],
        db_product["username"],
    )
    return {"ok": True}

app.src.api.endpoints.product_type.get_producttype_or_404 app.src.logger.logger.exception app.src.logger.logger.error fastapi.HTTPException app.src.models.product.Product.from_orm sqlmodel.select datetime.datetime.now fastapi.APIRouter app.src.common.utils.profiling_api fastapi.Query fastapi.Path fastapi.Depends
[((751, 762), 'fastapi.APIRouter', 'APIRouter', ([], {}), '()\n', (760, 762), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((825, 845), 'fastapi.Depends', 'Depends', (['get_session'], {}), '(get_session)\n', (832, 845), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((869, 884), 'fastapi.Path', 'Path', (['...'], {'ge': '(1)'}), '(..., ge=1)\n', (873, 884), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((914, 939), 'fastapi.Depends', 'Depends', (['get_current_user'], {}), '(get_current_user)\n', (921, 939), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((961, 978), 'datetime.datetime.now', 'dt.datetime.now', ([], {}), '()\n', (976, 978), True, 'import datetime as dt\n'), ((1734, 1754), 'fastapi.Depends', 'Depends', (['get_session'], {}), '(get_session)\n', (1741, 1754), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((1794, 1821), 'fastapi.Query', 'Query', ([], {'default': '(100)', 'lte': '(100)'}), '(default=100, lte=100)\n', (1799, 1821), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((1851, 1876), 'fastapi.Depends', 'Depends', (['get_current_user'], {}), '(get_current_user)\n', (1858, 1876), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((1947, 1964), 'datetime.datetime.now', 'dt.datetime.now', ([], {}), '()\n', (1962, 1964), True, 'import datetime as dt\n'), ((2048, 2115), 'app.src.common.utils.profiling_api', 'profiling_api', (['"""Product:get:all"""', 'start_time', 'current_user.username'], {}), "('Product:get:all', start_time, current_user.username)\n", (2061, 2115), False, 'from app.src.common.utils import profiling_api\n'), ((2280, 2307), 'fastapi.Depends', 'Depends', (['get_product_or_404'], {}), '(get_product_or_404)\n', (2287, 2307), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((2683, 2786), 'app.src.common.utils.profiling_api', 'profiling_api', (['f"""Product:read:by_id:{product_id}"""', "db_product['start_time']", "db_product['username']"], {}), "(f'Product:read:by_id:{product_id}', db_product['start_time'],\n    db_product['username'])\n", (2696, 2786), False, 'from app.src.common.utils import profiling_api\n'), ((2954, 2974), 'fastapi.Depends', 'Depends', (['get_session'], {}), '(get_session)\n', (2961, 2974), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((3032, 3057), 'fastapi.Depends', 'Depends', (['get_current_user'], {}), '(get_current_user)\n', (3039, 3057), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((3134, 3151), 'datetime.datetime.now', 'dt.datetime.now', ([], {}), '()\n', (3149, 3151), True, 'import datetime as dt\n'), ((4023, 4043), 'fastapi.Depends', 'Depends', (['get_session'], {}), '(get_session)\n', (4030, 4043), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((4099, 4126), 'fastapi.Depends', 'Depends', (['get_product_or_404'], {}), '(get_product_or_404)\n', (4106, 4126), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((4755, 4861), 'app.src.common.utils.profiling_api', 'profiling_api', (['f"""Product:update:by_id:{product_id}"""', "db_product['start_time']", "db_product['username']"], {}), "(f'Product:update:by_id:{product_id}', db_product['start_time'\n    ], db_product['username'])\n", (4768, 4861), False, 'from app.src.common.utils import profiling_api\n'), ((5058, 5078), 'fastapi.Depends', 'Depends', (['get_session'], {}), '(get_session)\n', (5065, 5078), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((5159, 5184), 'fastapi.Depends', 'Depends', (['get_current_user'], {}), '(get_current_user)\n', (5166, 5184), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((5261, 5278), 'datetime.datetime.now', 'dt.datetime.now', ([], {}), '()\n', (5276, 5278), True, 'import datetime as dt\n'), ((5747, 5841), 'app.src.common.utils.profiling_api', 'profiling_api', (['f"""Product:update:by_name:{product_name}"""', 'start_time', 'current_user.username'], {}), "(f'Product:update:by_name:{product_name}', start_time,\n    current_user.username)\n", (5760, 5841), False, 'from app.src.common.utils import profiling_api\n'), ((6099, 6119), 'fastapi.Depends', 'Depends', (['get_session'], {}), '(get_session)\n', (6106, 6119), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((6147, 6174), 'fastapi.Depends', 'Depends', (['get_product_or_404'], {}), '(get_product_or_404)\n', (6154, 6174), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((6194, 6217), 'fastapi.Depends', 'Depends', (['get_tag_or_404'], {}), '(get_tag_or_404)\n', (6201, 6217), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((6491, 6605), 'app.src.common.utils.profiling_api', 'profiling_api', (['f"""Product:update:add_tag:by_id:{product_id}"""', "db_product['start_time']", "db_product['username']"], {}), "(f'Product:update:add_tag:by_id:{product_id}', db_product[\n    'start_time'], db_product['username'])\n", (6504, 6605), False, 'from app.src.common.utils import profiling_api\n'), ((6874, 6894), 'fastapi.Depends', 'Depends', (['get_session'], {}), '(get_session)\n', (6881, 6894), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((6922, 6949), 'fastapi.Depends', 'Depends', (['get_product_or_404'], {}), '(get_product_or_404)\n', (6929, 6949), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((6969, 7000), 'fastapi.Depends', 'Depends', (['get_tag_by_name_or_404'], {}), '(get_tag_by_name_or_404)\n', (6976, 7000), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((7274, 7390), 'app.src.common.utils.profiling_api', 'profiling_api', (['f"""Product:update:add_tag:by_name:{product_id}"""', "db_product['start_time']", "db_product['username']"], {}), "(f'Product:update:add_tag:by_name:{product_id}', db_product[\n    'start_time'], db_product['username'])\n", (7287, 7390), False, 'from app.src.common.utils import profiling_api\n'), ((7659, 7679), 'fastapi.Depends', 'Depends', (['get_session'], {}), '(get_session)\n', (7666, 7679), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((7707, 7734), 'fastapi.Depends', 'Depends', (['get_product_or_404'], {}), '(get_product_or_404)\n', (7714, 7734), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((7754, 7777), 'fastapi.Depends', 'Depends', (['get_tag_or_404'], {}), '(get_tag_or_404)\n', (7761, 7777), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((8737, 8757), 'fastapi.Depends', 'Depends', (['get_session'], {}), '(get_session)\n', (8744, 8757), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((8785, 8812), 'fastapi.Depends', 'Depends', (['get_product_or_404'], {}), '(get_product_or_404)\n', (8792, 8812), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((8832, 8863), 'fastapi.Depends', 'Depends', (['get_tag_by_name_or_404'], {}), '(get_tag_by_name_or_404)\n', (8839, 8863), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((9151, 9270), 'app.src.common.utils.profiling_api', 'profiling_api', (['f"""Product:update:remove_tag:by_name:{product_id}"""', "db_product['start_time']", "db_product['username']"], {}), "(f'Product:update:remove_tag:by_name:{product_id}', db_product\n    ['start_time'], db_product['username'])\n", (9164, 9270), False, 'from app.src.common.utils import profiling_api\n'), ((9430, 9450), 'fastapi.Depends', 'Depends', (['get_session'], {}), '(get_session)\n', (9437, 9450), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((9478, 9505), 'fastapi.Depends', 'Depends', (['get_product_or_404'], {}), '(get_product_or_404)\n', (9485, 9505), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((10039, 10153), 'app.src.common.utils.profiling_api', 'profiling_api', (['f"""Product:update:add_tag:by_id:{product_id}"""', "db_product['start_time']", "db_product['username']"], {}), "(f'Product:update:add_tag:by_id:{product_id}', db_product[\n    'start_time'], db_product['username'])\n", (10052, 10153), False, 'from app.src.common.utils import profiling_api\n'), ((3205, 3276), 'app.src.api.endpoints.product_type.get_producttype_or_404', 'get_producttype_or_404', ([], {'producttype_id': 'product.type_id', 'session': 'session'}), '(producttype_id=product.type_id, session=session)\n', (3227, 3276), False, 'from app.src.api.endpoints.product_type import get_producttype_or_404\n'), ((3348, 3373), 'app.src.models.product.Product.from_orm', 'Product.from_orm', (['product'], {}), '(product)\n', (3364, 3373), False, 'from app.src.models.product import Product, ProductRead, ProductCreate, ProductUpdate, ProductReadwithTypeAndTags\n'), ((3439, 3512), 'app.src.common.utils.profiling_api', 'profiling_api', (['"""Product:insert:single"""', 'start_time', 'current_user.username'], {}), "('Product:insert:single', start_time, current_user.username)\n", (3452, 3512), False, 'from app.src.common.utils import profiling_api\n'), ((5405, 5490), 'fastapi.HTTPException', 'HTTPException', ([], {'status_code': '(404)', 'detail': '"""Product not found, impossible to update"""'}), "(status_code=404, detail='Product not found, impossible to update'\n    )\n", (5418, 5490), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((8084, 8201), 'app.src.common.utils.profiling_api', 'profiling_api', (['f"""Product:update:remove_tag:by_id:{product_id}"""', "db_product['start_time']", "db_product['username']"], {}), "(f'Product:update:remove_tag:by_id:{product_id}', db_product[\n    'start_time'], db_product['username'])\n", (8097, 8201), False, 'from app.src.common.utils import profiling_api\n'), ((1261, 1294), 'app.src.logger.logger.error', 'logger.error', (['"""Product not found"""'], {}), "('Product not found')\n", (1273, 1294), False, 'from app.src.logger import logger\n'), ((1307, 1344), 'app.src.logger.logger.exception', 'logger.exception', (['"""Product not found"""'], {}), "('Product not found')\n", (1323, 1344), False, 'from app.src.logger import logger\n'), ((1363, 1421), 'fastapi.HTTPException', 'HTTPException', ([], {'status_code': '(404)', 'detail': '"""Product not found"""'}), "(status_code=404, detail='Product not found')\n", (1376, 1421), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((1451, 1484), 'app.src.logger.logger.error', 'logger.error', (['"""Product not found"""'], {}), "('Product not found')\n", (1463, 1484), False, 'from app.src.logger import logger\n'), ((1493, 1540), 'app.src.logger.logger.exception', 'logger.exception', (['"""KeyError: Product not found"""'], {}), "('KeyError: Product not found')\n", (1509, 1540), False, 'from app.src.logger import logger\n'), ((1555, 1613), 'fastapi.HTTPException', 'HTTPException', ([], {'status_code': '(400)', 'detail': '"""Product not found"""'}), "(status_code=400, detail='Product not found')\n", (1568, 1613), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((3548, 3607), 'app.src.logger.logger.error', 'logger.error', (['"""Impossible to create product with same name"""'], {}), "('Impossible to create product with same name')\n", (3560, 3607), False, 'from app.src.logger import logger\n'), ((3616, 3701), 'app.src.logger.logger.exception', 'logger.exception', (['"""Integrity Error: Impossible to create product with same name"""'], {}), "('Integrity Error: Impossible to create product with same name'\n    )\n", (3632, 3701), False, 'from app.src.logger import logger\n'), ((3711, 3800), 'fastapi.HTTPException', 'HTTPException', ([], {'status_code': '(404)', 'detail': '"""Impossible to create product with same name"""'}), "(status_code=404, detail=\n    'Impossible to create product with same name')\n", (3724, 3800), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((8285, 8306), 'app.src.logger.logger.error', 'logger.error', (['message'], {}), '(message)\n', (8297, 8306), False, 'from app.src.logger import logger\n'), ((8315, 8340), 'app.src.logger.logger.exception', 'logger.exception', (['message'], {}), '(message)\n', (8331, 8340), False, 'from app.src.logger import logger\n'), ((8355, 8458), 'fastapi.HTTPException', 'HTTPException', ([], {'status_code': '(404)', 'detail': '"""Impossible to remove the tag: product or tag not existing"""'}), "(status_code=404, detail=\n    'Impossible to remove the tag: product or tag not existing')\n", (8368, 8458), False, 'from fastapi import APIRouter, Depends, HTTPException, Query, Path\n'), ((5309, 5324), 'sqlmodel.select', 'select', (['Product'], {}), '(Product)\n', (5315, 5324), False, 'from sqlmodel import Session, select\n'), ((1993, 2008), 'sqlmodel.select', 'select', (['Product'], {}), '(Product)\n', (1999, 2008), False, 'from sqlmodel import Session, select\n')]