from datetime import datetime
from typing import Any, Dict, Optional

from bson import ObjectId
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer

from app.db.db import get_col
from app.models.category import Category
from app.utility.cloudinary_utils import (
    delete_image_from_cloudinary,
    extract_public_id_from_url,
    upload_image_to_cloudinary,
)
from app.utility.security import verify_jwt


router = APIRouter(prefix="/categories", tags=["categories"])
auth_scheme = HTTPBearer(auto_error=False)


def _ensure_object_id(id_str: str) -> ObjectId:
    try:
        return ObjectId(id_str)
    except Exception as exc:  # pragma: no cover - defensive
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid identifier") from exc


def _serialize_datetime(value: Any) -> Any:
    if isinstance(value, datetime):
        return value.isoformat()
    return value


def _require_admin(credentials: HTTPAuthorizationCredentials = Depends(auth_scheme)) -> Dict[str, Any]:
    if not credentials:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")
    ok, payload = verify_jwt(credentials.credentials)
    if not ok or not payload:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
    if payload.get("role") != "admin":
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")
    admins = get_col("admins")
    admin = admins.find_one({"_id": _ensure_object_id(payload["sub"])})
    if not admin:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Admin not found")
    return admin


def _serialize_category(doc: Dict[str, Any]) -> Dict[str, Any]:
    return {
        "id": str(doc["_id"]),
        "category_name": doc["category_name"],
        "category_image": doc.get("category_image"),
        "status": doc.get("status", True),
        "product_list": doc.get("product_list", []),
        "createdAt": _serialize_datetime(doc.get("createdAt")),
        "updatedAt": _serialize_datetime(doc.get("updatedAt")),
    }


@router.post("/create", status_code=status.HTTP_201_CREATED)
async def create_category(
    category_name: str = Form(...),
    category_image: UploadFile = File(...),
    status: bool = Form(True),
    _admin=Depends(_require_admin),
):
    categories = get_col("categories")
    if categories.find_one({"category_name": category_name}):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Category already exists")
    
    # Upload image to Cloudinary
    image_url = upload_image_to_cloudinary(category_image, folder="categories")
    
    now = datetime.utcnow()
    payload = {
        "category_name": category_name,
        "category_image": image_url,
        "status": status,
        "product_list": [],
        "createdAt": now,
        "updatedAt": now,
    }
    res = categories.insert_one(payload)
    payload["_id"] = res.inserted_id
    return _serialize_category(payload)


@router.get("/list", status_code=status.HTTP_200_OK)
def list_categories(status_filter: Optional[bool] = None):
    categories = get_col("categories")
    query: Dict[str, Any] = {}
    if status_filter is not None:
        query["status"] = status_filter
    return [_serialize_category(doc) for doc in categories.find(query)]


@router.get("/single/{category_id}", status_code=status.HTTP_200_OK)
def get_category(category_id: str):
    categories = get_col("categories")
    oid = _ensure_object_id(category_id)
    doc = categories.find_one({"_id": oid})
    if not doc:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Category not found")
    return _serialize_category(doc)


@router.put("/update/{category_id}", status_code=status.HTTP_200_OK)
async def update_category(
    category_id: str,
    category_name: Optional[str] = Form(None),
    category_image: Optional[UploadFile] = File(None),
    status: Optional[bool] = Form(None),
    _admin=Depends(_require_admin),
):
    categories = get_col("categories")
    oid = _ensure_object_id(category_id)
    doc = categories.find_one({"_id": oid})
    if not doc:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Category not found")
    
    payload: Dict[str, Any] = {}
    
    if category_name is not None:
        existing = categories.find_one({"category_name": category_name, "_id": {"$ne": oid}})
        if existing:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Category name already exists")
        payload["category_name"] = category_name
    if status is not None:
        payload["status"] = status
    if category_image is not None:
        # Delete old image from Cloudinary
        old_image_url = doc.get("category_image")
        if old_image_url:
            public_id = extract_public_id_from_url(old_image_url)
            if public_id:
                delete_image_from_cloudinary(public_id)
        
        # Upload new image
        payload["category_image"] = upload_image_to_cloudinary(category_image, folder="categories")
    
    if not payload:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="No fields to update")
    
    payload["updatedAt"] = datetime.utcnow()
    categories.update_one({"_id": oid}, {"$set": payload})
    updated_doc = categories.find_one({"_id": oid})
    return _serialize_category(updated_doc)


@router.delete("/delete/{category_id}", status_code=status.HTTP_200_OK)
def delete_category(category_id: str, _admin=Depends(_require_admin)):
    categories = get_col("categories")
    oid = _ensure_object_id(category_id)
    doc = categories.find_one({"_id": oid})
    if not doc:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Category not found")
    
    # Delete image from Cloudinary
    image_url = doc.get("category_image")
    if image_url:
        public_id = extract_public_id_from_url(image_url)
        if public_id:
            delete_image_from_cloudinary(public_id)
    
    categories.delete_one({"_id": oid})
    return {"message": "Category deleted successfully"}


