from datetime import datetime, timezone
from typing import Dict, List, Optional, Tuple
from uuid import UUID

from fastapi import HTTPException, status
from sqlalchemy import and_, func, or_
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session, aliased
from sqlalchemy.sql.expression import nullslast

from src.apps.base.models.country import Country
from src.apps.rollup.models.rollup import RollupLog
from src.apps.rollup.schemas.rollup import (
    RollupLogCreate,
    RollupLogUpdate,
    RollupLogResponse,
    RollupLogFilterSchema
)
from src.utils.enums import RollupType
from src.core.config import settings
from src.core.exceptions import APIException
from src.utils.constants import API_PREFIXES
from src.utils.pagination import QueryPaginator


async def create_rollup_log(
    db: Session,
    payload: RollupLogCreate,
) -> RollupLog:
    """
    Create a new rollup log entry in the database.
    """
    try:
        rollup_log = RollupLog(**payload.model_dump(exclude_unset=True))
        db.add(rollup_log)
        db.commit()
        db.refresh(rollup_log)
        return rollup_log
    except Exception as e:
        raise APIException(
            module="create_rollup_log",
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            error={"exception": str(e)},
            message="Failed to create rollup log"
        )
        
async def get_rollup_log_by_id(
    db: Session,
    rollup_log_id: int,
) -> any:
    """
    Retrieve a rollup log entry by its ID.
    """
    try:
        rollup_log = db.query(RollupLog).filter(
            RollupLog.id == rollup_log_id,
            RollupLog.deleted_at.is_(None)
            ).first()
        if not rollup_log:
            raise APIException(
                module="get_rollup_log_by_id",
                status_code=status.HTTP_404_NOT_FOUND,
                error={"rollup_log_id": rollup_log_id},
                message="Rollup log not found"
            )
        return rollup_log
    except Exception as e:
        raise APIException(
            module="get_rollup_log_by_id",
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            error={"exception": str(e)},
            message="Failed to retrieve rollup log"
        )
        
async def update_rollup_log(
    db: Session,
    rollup_log_id: int,
    payload: RollupLogUpdate,
) -> RollupLog:
    """
    Update an existing rollup log entry in the database.
    """
    try:
        rollup_log = await get_rollup_log_by_id(db, rollup_log_id)
        for key, value in payload.model_dump(exclude_unset=True).items():
            if key == "message" or value is not None:
                setattr(rollup_log, key, value)
        db.commit()
        db.refresh(rollup_log)
        return rollup_log
    except Exception as e:
        raise APIException(
            module="update_rollup_log",
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            error={"exception": str(e)},
            message="Failed to update rollup log"
        )
        
        
async def get_all_rollup_logs(
    db: Session,
    payload: RollupLogFilterSchema = None,
    page: int = 1,
    per_page: int = 10,
    paginated: bool = True,
    sort_by: List[str] = None,
) -> any:
    """
    Retrieve all rollup log entries with pagination.
    """
    try:
        offset = (page - 1) * per_page
        query = db.query(RollupLog).filter(
            RollupLog.deleted_at.is_(None)
            )
        
        if payload.rollup_type:
            query = query.filter(RollupLog.rollup_type == RollupType[payload.rollup_type].value)

        query = await apply_sorting_to_query(query, sort_by, RollupLog)

        if paginated:
            paginator = QueryPaginator(
                query=query, schema=RollupLogResponse, url="".join([str(settings.api_base_url()), API_PREFIXES.ROLLUP]), offset=offset, limit=per_page, use_orm=True
            )
            return paginator.paginate()
        return [RollupLogResponse.model_validate(rollup) for rollup in query.all()]
    except Exception as e:
        raise APIException(
            module="get_all_rollup_logs",
            error={"exception": str(e)},
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            message="Error retrieving rollup logs."
        )


async def apply_sorting_to_query(query, sort_by: List[str], model) -> any:
    """
    Apply sorting to a query based on the provided sort_by list.

    Args:
        query: The SQLAlchemy query object.
        sort_by: List of sorting criteria.
        model: The SQLAlchemy model to apply sorting on.

    Returns:
        The query with sorting applied.
    """
    sort_query = []
    if sort_by:
        for s in sort_by:
            try:
                col = s.replace("-", "")
                if col in [
                    "id",
                    "message",
                    "status",
                    "date_start",
                    "date_end",
                    "created_at",
                    "updated_at",
                ]:
                    sort_order = getattr(model, col)

                    if s.startswith("-"):
                        sort_order = sort_order.desc()

                    sort_query.append(nullslast(sort_order))
                else:
                    raise APIException(
                        module="apply_sorting_to_query",
                        error=f"Cannot sort with unidentified column '{s}'",
                        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                        message="Error applying sorting to query."
                    )
            except AttributeError:
                raise APIException(
                    module="apply_sorting_to_query",
                    error=f"Cannot sort with unidentified column '{s}'",
                    status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                    message="Error applying sorting to query."
                )

        if sort_query:
            query = query.order_by(*sort_query)

    return query