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

This module handles all database operations for sessions, 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.session import Session as SessionModel
from api.models.project import Project
from api.models.machine import Machine
from api.schemas.session import SessionCreate, SessionUpdate


def get_sessions(db: Session, skip: int = 0, limit: int = 100) -> tuple[list[SessionModel], int]:
    """
    Retrieve a paginated list of sessions.

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

    Returns:
        tuple: (list of sessions, total count)

    Example:
        ```python
        sessions, total = get_sessions(db, skip=0, limit=50)
        print(f"Retrieved {len(sessions)} of {total} sessions")
        ```
    """
    # Get total count
    total = db.query(SessionModel).count()

    # Get paginated results, ordered by session_date descending (newest first)
    sessions = (
        db.query(SessionModel)
        .order_by(SessionModel.session_date.desc())
        .offset(skip)
        .limit(limit)
        .all()
    )

    return sessions, total


def get_session_by_id(db: Session, session_id: UUID) -> SessionModel:
    """
    Retrieve a single session by its ID.

    Args:
        db: Database session
        session_id: UUID of the session to retrieve

    Returns:
        SessionModel: The session object

    Raises:
        HTTPException: 404 if session not found

    Example:
        ```python
        session = get_session_by_id(db, session_id)
        print(f"Found session: {session.session_title}")
        ```
    """
    session = db.query(SessionModel).filter(SessionModel.id == str(session_id)).first()

    if not session:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Session with ID {session_id} not found"
        )

    return session


def get_sessions_by_project(db: Session, project_id: UUID, skip: int = 0, limit: int = 100) -> tuple[list[SessionModel], int]:
    """
    Retrieve sessions for a specific project.

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

    Returns:
        tuple: (list of sessions, total count)

    Example:
        ```python
        sessions, total = get_sessions_by_project(db, project_id)
        print(f"Found {total} sessions for project")
        ```
    """
    # Get total count
    total = db.query(SessionModel).filter(SessionModel.project_id == str(project_id)).count()

    # Get paginated results
    sessions = (
        db.query(SessionModel)
        .filter(SessionModel.project_id == str(project_id))
        .order_by(SessionModel.session_date.desc())
        .offset(skip)
        .limit(limit)
        .all()
    )

    return sessions, total


def get_sessions_by_machine(db: Session, machine_id: UUID, skip: int = 0, limit: int = 100) -> tuple[list[SessionModel], int]:
    """
    Retrieve sessions for a specific machine.

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

    Returns:
        tuple: (list of sessions, total count)

    Example:
        ```python
        sessions, total = get_sessions_by_machine(db, machine_id)
        print(f"Found {total} sessions on this machine")
        ```
    """
    # Get total count
    total = db.query(SessionModel).filter(SessionModel.machine_id == str(machine_id)).count()

    # Get paginated results
    sessions = (
        db.query(SessionModel)
        .filter(SessionModel.machine_id == str(machine_id))
        .order_by(SessionModel.session_date.desc())
        .offset(skip)
        .limit(limit)
        .all()
    )

    return sessions, total


def create_session(db: Session, session_data: SessionCreate) -> SessionModel:
    """
    Create a new session.

    Args:
        db: Database session
        session_data: Session creation data

    Returns:
        SessionModel: The created session object

    Raises:
        HTTPException: 404 if referenced project or machine not found
        HTTPException: 422 if validation fails
        HTTPException: 500 if database error occurs

    Example:
        ```python
        session_data = SessionCreate(
            session_title="Database migration work",
            session_date=date.today(),
            project_id="123e4567-e89b-12d3-a456-426614174000"
        )
        session = create_session(db, session_data)
        print(f"Created session: {session.id}")
        ```
    """
    try:
        # Validate foreign keys if provided
        if session_data.project_id:
            project = db.query(Project).filter(Project.id == str(session_data.project_id)).first()
            if not project:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail=f"Project with ID {session_data.project_id} not found"
                )

        if session_data.machine_id:
            machine = db.query(Machine).filter(Machine.id == str(session_data.machine_id)).first()
            if not machine:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail=f"Machine with ID {session_data.machine_id} not found"
                )

        # Create new session instance
        db_session = SessionModel(**session_data.model_dump())

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

        return db_session

    except HTTPException:
        db.rollback()
        raise
    except IntegrityError as e:
        db.rollback()
        # Handle foreign key constraint violations
        if "project_id" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail=f"Invalid project_id: {session_data.project_id}"
            )
        elif "machine_id" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail=f"Invalid machine_id: {session_data.machine_id}"
            )
        elif "client_id" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail=f"Invalid client_id: {session_data.client_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 session: {str(e)}"
        )


def update_session(db: Session, session_id: UUID, session_data: SessionUpdate) -> SessionModel:
    """
    Update an existing session.

    Args:
        db: Database session
        session_id: UUID of the session to update
        session_data: Session update data (only provided fields will be updated)

    Returns:
        SessionModel: The updated session object

    Raises:
        HTTPException: 404 if session, project, or machine not found
        HTTPException: 422 if validation fails
        HTTPException: 500 if database error occurs

    Example:
        ```python
        update_data = SessionUpdate(
            status="completed",
            duration_minutes=120
        )
        session = update_session(db, session_id, update_data)
        print(f"Updated session: {session.session_title}")
        ```
    """
    # Get existing session
    session = get_session_by_id(db, session_id)

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

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

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

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

        db.commit()
        db.refresh(session)

        return session

    except HTTPException:
        db.rollback()
        raise
    except IntegrityError as e:
        db.rollback()
        if "project_id" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail="Invalid project_id"
            )
        elif "machine_id" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail="Invalid machine_id"
            )
        elif "client_id" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail="Invalid client_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 session: {str(e)}"
        )


def delete_session(db: Session, session_id: UUID) -> dict:
    """
    Delete a session by its ID.

    Args:
        db: Database session
        session_id: UUID of the session to delete

    Returns:
        dict: Success message

    Raises:
        HTTPException: 404 if session not found
        HTTPException: 500 if database error occurs

    Example:
        ```python
        result = delete_session(db, session_id)
        print(result["message"])  # "Session deleted successfully"
        ```
    """
    # Get existing session (raises 404 if not found)
    session = get_session_by_id(db, session_id)

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

        return {
            "message": "Session deleted successfully",
            "session_id": str(session_id)
        }

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