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(