from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, scoped_session, Session
from typing import List


app = FastAPI()

# Database setup
DATABASE_URL = "sqlite:///./api.db"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


# Database models
class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True, index=True)
    username = Column(String, unique=True, index=True)
    hashed_password = Column(String)

class Product(Base):
    __tablename__ = "products"
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    description = Column(String)
    price = Column(Integer)

class Order(Base):
    __tablename__ = "orders"
    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey("users.id"))
    user = relationship("User", back_populates="orders")
    products = relationship("Product", secondary="order_products")

class OrderProduct(Base):
    __tablename__ = "order_products"
    order_id = Column(Integer, ForeignKey("orders.id"), primary_key=True)
    product_id = Column(Integer, ForeignKey("products.id"), primary_key=True)

User.orders = relationship("Order", back_populates="user")

# Pydantic models
class UserCreate(BaseModel):
    username: str
    password: str

class ProductCreate(BaseModel):
    name: str
    description: str
    price: int

class ProductUpdate(BaseModel):
    name: str = None
    description: str = None
    price: int = None

class OrderCreate(BaseModel):
    product_ids: List[int]

# Dependency
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

# Utility functions
def get_user(db: Session, username: str):
    return db.query(User).filter(User.username == username).first()

def create_user(db: Session, user: UserCreate):
    # You should use a proper password hashing function instead of storing plaintext passwords
    hashed_password = user.password
    db_user = User(username=user.username, hashed_password=hashed_password)
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return db_user

def get_product(db: Session, product_id: int):
    return db.query(Product).filter(Product.id == product_id).first()

def create_product(db: Session, product: ProductCreate):
    db_product = Product(**product.dict())
    db.add(db_product)
    db.commit()
    db.refresh(db_product)
    return db_product

def update_product(db: Session, product_id: int, product_update: ProductUpdate):
    db_product = get_product(db, product_id)
    if not db_product:
        return None
    for key, value in product_update.dict().items():
        if value is not None:
            setattr(db_product, key, value)
    db.commit()
    return db_product

def delete_product(db: Session, product_id: int):
    db_product = get_product(db, product_id)
    if not db_product:
        return None
    db.delete(db_product)
    db.commit()
    return db_product

def get_products(db: Session):
    return db.query(Product).all()

def create_order(db: Session, order_create: OrderCreate):
    db_order = Order()
    db.add(db_order)
    db.flush()
    for product_id in order_create.product_ids:
        db_order_product = OrderProduct(order_id=db_order.id, product_id=product_id)
        db.add(db_order_product)
    db.commit()
    db.refresh(db_order)
    return db_order

def delete_order(db: Session, order_id: int):
    db_order = db.query(Order).filter(Order.id == order_id).first()
    if not db_order:
        return None
    db.delete(db_order)
    db.commit()
    return db_order

# Routes
@app.post("/register")
def register(user: UserCreate, db: Session = Depends(get_db)):
    if get_user(db, user.username):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Username is already registered")
    return create_user(db, user)

@app.post("/token")
def login(user: UserCreate, db: Session = Depends(get_db)):
    # You should use a proper password verification function instead of comparing plaintext passwords
    db_user = get_user(db, user.username)
    if not db_user or db_user.hashed_password != user.password:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
    return {"access_token": "dummy_jwt_token", "token_type": "bearer"}

@app.post("/products")
def create_new_product(product: ProductCreate, db: Session = Depends(get_db)):
    return create_product(db, product)

@app.get("/products")
def read_products(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
    return get_products(db)[skip : skip + limit]

@app.patch("/products/{product_id}")
def update_existing_product(product_id: int, product_update: ProductUpdate, db: Session = Depends(get_db)):
    updated_product = update_product(db, product_id, product_update)
    if not updated_product:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Product not found")
    return updated_product

@app.delete("/products/{product_id}")
def remove_product(product_id: int, db: Session = Depends(get_db)):
    deleted_product = delete_product(db, product_id)
    if not deleted_product:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Product not found")
    return deleted_product

@app.post("/orders")
def create_new_order(order: OrderCreate, db: Session = Depends(get_db)):
    return create_order(db, order)

@app.delete("/orders/{order_id}")
def remove_order(order_id: int, db: Session = Depends(get_db)):
    deleted_order = delete_order(db, order_id)
    if not deleted_order:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Order not found")
    return deleted_order

if __name__ == "__main__":
    Base.metadata.create_all(bind=engine)
    import uvicorn
    uvicorn.run("BigProductsAPI:app", host="0.0.0.0", port=8000, reload=True)
