"""
BillableTime service layer for business logic and database operations.

This module handles all database operations for billable time entries, providing a clean
separation between the API routes and data access layer.
"""

from typing import Optional
from uuid import UUID

from fastapi import HTTPException, status
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session

from api.models.billable_time import BillableTime as BillableTimeModel
from api.models.client import Client
from api.models.session import Session as SessionModel
from api.models.work_item import WorkItem
from api.schemas.billable_time import BillableTimeCreate, BillableTimeUpdate


def get_billable_time_entries(db: Session, skip: int = 0, limit: int = 100) -> tuple[list[BillableTimeModel], int]:
    """
    Retrieve a paginated list of billable time entries.

    Args:
        db: Database session
        skip: Number of records to skip (for pagination)
        limit: Maximum number of records to return

    Returns:
        tuple: (list of billable time entries, total count)

    Example:
        ```python
        entries, total = get_billable_time_entries(db, skip=0, limit=50)
        print(f"Retrieved {len(entries)} of {total} billable time entries")
        ```
    """
    # Get total count
    total = db.query(BillableTimeModel).count()

    # Get paginated results, ordered by start_time descending (newest first)
    entries = (
        db.query(BillableTimeModel)
        .order_by(BillableTimeModel.start_time.desc())
        .offset(skip)
        .limit(limit)
        .all()
    )

    return entries, total


def get_billable_time_by_id(db: Session, billable_time_id: UUID) -> BillableTimeModel:
    """
    Retrieve a single billable time entry by its ID.

    Args:
        db: Database session
        billable_time_id: UUID of the billable time entry to retrieve

    Returns:
        BillableTimeModel: The billable time entry object

    Raises:
        HTTPException: 404 if billable time entry not found

    Example:
        ```python
        entry = get_billable_time_by_id(db, billable_time_id)
        print(f"Found entry: {entry.description}")
        ```
    """
    entry = db.query(BillableTimeModel).filter(BillableTimeModel.id == str(billable_time_id)).first()

    if not entry:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Billable time entry with ID {billable_time_id} not found"
        )

    return entry


def get_billable_time_by_session(db: Session, session_id: UUID, skip: int = 0, limit: int = 100) -> tuple[list[BillableTimeModel], int]:
    """
    Retrieve billable time entries for a specific session.

    Args:
        db: Database session
        session_id: UUID of the session
        skip: Number of records to skip (for pagination)
        limit: Maximum number of records to return

    Returns:
        tuple: (list of billable time entries, total count)

    Example:
        ```python
        entries, total = get_billable_time_by_session(db, session_id)
        print(f"Found {total} billable time entries for session")
        ```
    """
    # Get total count
    total = db.query(BillableTimeModel).filter(BillableTimeModel.session_id == str(session_id)).count()

    # Get paginated results
    entries = (
        db.query(BillableTimeModel)
        .filter(BillableTimeModel.session_id == str(session_id))
        .order_by(BillableTimeModel.start_time.desc())
        .offset(skip)
        .limit(limit)
        .all()
    )

    return entries, total


def get_billable_time_by_work_item(db: Session, work_item_id: UUID, skip: int = 0, limit: int = 100) -> tuple[list[BillableTimeModel], int]:
    """
    Retrieve billable time entries for a specific work item.

    Args:
        db: Database session
        work_item_id: UUID of the work item
        skip: Number of records to skip (for pagination)
        limit: Maximum number of records to return

    Returns:
        tuple: (list of billable time entries, total count)

    Example:
        ```python
        entries, total = get_billable_time_by_work_item(db, work_item_id)
        print(f"Found {total} billable time entries for work item")
        ```
    """
    # Get total count
    total = db.query(BillableTimeModel).filter(BillableTimeModel.work_item_id == str(work_item_id)).count()

    # Get paginated results
    entries = (
        db.query(BillableTimeModel)
        .filter(BillableTimeModel.work_item_id == str(work_item_id))
        .order_by(BillableTimeModel.start_time.desc())
        .offset(skip)
        .limit(limit)
        .all()
    )

    return entries, total


def create_billable_time(db: Session, billable_time_data: BillableTimeCreate) -> BillableTimeModel:
    """
    Create a new billable time entry.

    Args:
        db: Database session
        billable_time_data: Billable time creation data

    Returns:
        BillableTimeModel: The created billable time entry object

    Raises:
        HTTPException: 404 if referenced client, session, or work item not found
        HTTPException: 422 if validation fails
        HTTPException: 500 if database error occurs

    Example:
        ```python
        entry_data = BillableTimeCreate(
            client_id="123e4567-e89b-12d3-a456-426614174000",
            start_time=datetime.now(),
            duration_minutes=60,
            hourly_rate=150.00,
            total_amount=150.00,
            description="Database optimization",
            category="development"
        )
        entry = create_billable_time(db, entry_data)
        print(f"Created billable time entry: {entry.id}")
        ```
    """
    try:
        # Validate foreign keys
        # Client is required
        client = db.query(Client).filter(Client.id == str(billable_time_data.client_id)).first()
        if not client:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Client with ID {billable_time_data.client_id} not found"
            )

        # Session is optional
        if billable_time_data.session_id:
            session = db.query(SessionModel).filter(SessionModel.id == str(billable_time_data.session_id)).first()
            if not session:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail=f"Session with ID {billable_time_data.session_id} not found"
                )

        # Work item is optional
        if billable_time_data.work_item_id:
            work_item = db.query(WorkItem).filter(WorkItem.id == str(billable_time_data.work_item_id)).first()
            if not work_item:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail=f"Work item with ID {billable_time_data.work_item_id} not found"
                )

        # Create new billable time entry instance
        db_billable_time = BillableTimeModel(**billable_time_data.model_dump())

        # Add to database
        db.add(db_billable_time)
        db.commit()
        db.refresh(db_billable_time)

        return db_billable_time

    except HTTPException:
        db.rollback()
        raise
    except IntegrityError as e:
        db.rollback()
        # Handle foreign key constraint violations
        if "client_id" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail=f"Invalid client_id: {billable_time_data.client_id}"
            )
        elif "session_id" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail=f"Invalid session_id: {billable_time_data.session_id}"
            )
        elif "work_item_id" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail=f"Invalid work_item_id: {billable_time_data.work_item_id}"
            )
        else:
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail=f"Database error: {str(e)}"
            )
    except Exception as e:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to create billable time entry: {str(e)}"
        )


def update_billable_time(db: Session, billable_time_id: UUID, billable_time_data: BillableTimeUpdate) -> BillableTimeModel:
    """
    Update an existing billable time entry.

    Args:
        db: Database session
        billable_time_id: UUID of the billable time entry to update
        billable_time_data: Billable time update data (only provided fields will be updated)

    Returns:
        BillableTimeModel: The updated billable time entry object

    Raises:
        HTTPException: 404 if billable time entry, client, session, or work item not found
        HTTPException: 422 if validation fails
        HTTPException: 500 if database error occurs

    Example:
        ```python
        update_data = BillableTimeUpdate(
            duration_minutes=90,
            total_amount=225.00
        )
        entry = update_billable_time(db, billable_time_id, update_data)
        print(f"Updated billable time entry: {entry.description}")
        ```
    """
    # Get existing billable time entry
    entry = get_billable_time_by_id(db, billable_time_id)

    try:
        # Update only provided fields
        update_data = billable_time_data.model_dump(exclude_unset=True)

        # Validate foreign keys if being updated
        if "client_id" in update_data and update_data["client_id"]:
            client = db.query(Client).filter(Client.id == str(update_data["client_id"])).first()
            if not client:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail=f"Client with ID {update_data['client_id']} not found"
                )

        if "session_id" in update_data and update_data["session_id"]:
            session = db.query(SessionModel).filter(SessionModel.id == str(update_data["session_id"])).first()
            if not session:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail=f"Session with ID {update_data['session_id']} not found"
                )

        if "work_item_id" in update_data and update_data["work_item_id"]:
            work_item = db.query(WorkItem).filter(WorkItem.id == str(update_data["work_item_id"])).first()
            if not work_item:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail=f"Work item with ID {update_data['work_item_id']} not found"
                )

        # Validate end_time if being updated along with start_time
        if "end_time" in update_data and update_data["end_time"]:
            start_time = update_data.get("start_time", entry.start_time)
            if update_data["end_time"] < start_time:
                raise HTTPException(
                    status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                    detail="end_time must be after start_time"
                )

        # Apply updates
        for field, value in update_data.items():
            setattr(entry, field, value)

        db.commit()
        db.refresh(entry)

        return entry

    except HTTPException:
        db.rollback()
        raise
    except IntegrityError as e:
        db.rollback()
        if "client_id" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail="Invalid client_id"
            )
        elif "session_id" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail="Invalid session_id"
            )
        elif "work_item_id" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail="Invalid work_item_id"
            )
        else:
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail=f"Database error: {str(e)}"
            )
    except Exception as e:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to update billable time entry: {str(e)}"
        )


def delete_billable_time(db: Session, billable_time_id: UUID) -> dict:
    """
    Delete a billable time entry by its ID.

    Args:
        db: Database session
        billable_time_id: UUID of the billable time entry to delete

    Returns:
        dict: Success message

    Raises:
        HTTPException: 404 if billable time entry not found
        HTTPException: 500 if database error occurs

    Example:
        ```python
        result = delete_billable_time(db, billable_time_id)
        print(result["message"])  # "Billable time entry deleted successfully"
        ```
    """
    # Get existing billable time entry (raises 404 if not found)
    entry = get_billable_time_by_id(db, billable_time_id)

    try:
        db.delete(entry)
        db.commit()

        return {
            "message": "Billable time entry deleted successfully",
            "billable_time_id": str(billable_time_id)
        }

    except Exception as e:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to delete billable time entry: {str(e)}"
        )
